dcrouter/test/suite/smtpclient_reliability/test.crel-05.memory-leaks.ts
2025-05-24 18:12:08 +00:00

501 lines
18 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { test } from '@git.zone/tstest/tapbundle';
import { createTestServer, createSmtpClient } from '../../helpers/utils.js';
import { Email } from '../../../ts/mail/core/classes.email.js';
test('CREL-05: Memory Leak Prevention Reliability Tests', async () => {
console.log('\n🧠 Testing SMTP Client Memory Leak Prevention');
console.log('=' .repeat(60));
// Helper function to get memory usage
const getMemoryUsage = () => {
const usage = process.memoryUsage();
return {
heapUsed: Math.round(usage.heapUsed / 1024 / 1024 * 100) / 100, // MB
heapTotal: Math.round(usage.heapTotal / 1024 / 1024 * 100) / 100, // MB
external: Math.round(usage.external / 1024 / 1024 * 100) / 100, // MB
rss: Math.round(usage.rss / 1024 / 1024 * 100) / 100 // MB
};
};
// Force garbage collection if available
const forceGC = () => {
if (global.gc) {
global.gc();
global.gc(); // Run twice for thoroughness
}
};
// Scenario 1: Connection Pool Memory Management
await test.test('Scenario 1: Connection Pool Memory Management', async () => {
console.log('\n🏊 Testing connection pool memory management...');
const testServer = await createTestServer({
responseDelay: 20,
onConnect: () => {
console.log(' [Server] Connection established for memory test');
}
});
try {
const initialMemory = getMemoryUsage();
console.log(` Initial memory: ${initialMemory.heapUsed}MB heap, ${initialMemory.rss}MB RSS`);
console.log(' Phase 1: Creating and using multiple connection pools...');
const memorySnapshots = [];
for (let poolIndex = 0; poolIndex < 5; poolIndex++) {
console.log(` Creating connection pool ${poolIndex + 1}...`);
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 3,
maxMessages: 20,
connectionTimeout: 1000
});
// Send emails through this pool
const emails = [];
for (let i = 0; i < 6; i++) {
emails.push(new Email({
from: `sender${poolIndex}@memoryleak.test`,
to: [`recipient${i}@memoryleak.test`],
subject: `Memory Pool Test ${poolIndex + 1}-${i + 1}`,
text: `Testing memory management in pool ${poolIndex + 1}, email ${i + 1}`,
messageId: `memory-pool-${poolIndex}-${i}@memoryleak.test`
}));
}
// Send emails concurrently
const promises = emails.map((email, index) => {
return smtpClient.sendMail(email).then(result => {
return { success: true, result };
}).catch(error => {
return { success: false, error };
});
});
const results = await Promise.all(promises);
const successful = results.filter(r => r.success).length;
console.log(` Pool ${poolIndex + 1}: ${successful}/${emails.length} emails sent`);
// Close the pool
smtpClient.close();
console.log(` Pool ${poolIndex + 1} closed`);
// Force garbage collection and measure memory
forceGC();
await new Promise(resolve => setTimeout(resolve, 100));
const currentMemory = getMemoryUsage();
memorySnapshots.push({
pool: poolIndex + 1,
heap: currentMemory.heapUsed,
rss: currentMemory.rss,
external: currentMemory.external
});
console.log(` Memory after pool ${poolIndex + 1}: ${currentMemory.heapUsed}MB heap`);
}
console.log('\n Memory analysis:');
memorySnapshots.forEach((snapshot, index) => {
const memoryIncrease = snapshot.heap - initialMemory.heapUsed;
console.log(` Pool ${snapshot.pool}: +${memoryIncrease.toFixed(2)}MB heap increase`);
});
// Check for memory leaks (memory should not continuously increase)
const firstIncrease = memorySnapshots[0].heap - initialMemory.heapUsed;
const lastIncrease = memorySnapshots[memorySnapshots.length - 1].heap - initialMemory.heapUsed;
const leakGrowth = lastIncrease - firstIncrease;
console.log(` Memory leak assessment:`);
console.log(` First pool increase: +${firstIncrease.toFixed(2)}MB`);
console.log(` Final memory increase: +${lastIncrease.toFixed(2)}MB`);
console.log(` Memory growth across pools: +${leakGrowth.toFixed(2)}MB`);
console.log(` Memory management: ${leakGrowth < 2.0 ? 'Good (< 2MB growth)' : 'Potential leak detected'}`);
} finally {
testServer.close();
}
});
// Scenario 2: Email Object Memory Lifecycle
await test.test('Scenario 2: Email Object Memory Lifecycle', async () => {
console.log('\n📧 Testing email object memory lifecycle...');
const testServer = await createTestServer({
responseDelay: 10
});
try {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 2
});
const initialMemory = getMemoryUsage();
console.log(` Initial memory: ${initialMemory.heapUsed}MB heap`);
console.log(' Phase 1: Creating large batches of email objects...');
const batchSizes = [50, 100, 150, 100, 50]; // Varying batch sizes
const memorySnapshots = [];
for (let batchIndex = 0; batchIndex < batchSizes.length; batchIndex++) {
const batchSize = batchSizes[batchIndex];
console.log(` Creating batch ${batchIndex + 1} with ${batchSize} emails...`);
const emails = [];
for (let i = 0; i < batchSize; i++) {
emails.push(new Email({
from: 'sender@emailmemory.test',
to: [`recipient${i}@emailmemory.test`],
subject: `Memory Lifecycle Test Batch ${batchIndex + 1} Email ${i + 1}`,
text: `Testing email object memory lifecycle. This is a moderately long email body to test memory usage patterns. Email ${i + 1} in batch ${batchIndex + 1} of ${batchSize} emails.`,
html: `<h1>Email ${i + 1}</h1><p>Testing memory patterns with HTML content. Batch ${batchIndex + 1}.</p>`,
messageId: `email-memory-${batchIndex}-${i}@emailmemory.test`
}));
}
console.log(` Sending batch ${batchIndex + 1}...`);
const promises = emails.map((email, index) => {
return smtpClient.sendMail(email).then(result => {
return { success: true };
}).catch(error => {
return { success: false, error };
});
});
const results = await Promise.all(promises);
const successful = results.filter(r => r.success).length;
console.log(` Batch ${batchIndex + 1}: ${successful}/${batchSize} emails sent`);
// Clear email references
emails.length = 0;
// Force garbage collection
forceGC();
await new Promise(resolve => setTimeout(resolve, 100));
const currentMemory = getMemoryUsage();
memorySnapshots.push({
batch: batchIndex + 1,
size: batchSize,
heap: currentMemory.heapUsed,
external: currentMemory.external
});
console.log(` Memory after batch ${batchIndex + 1}: ${currentMemory.heapUsed}MB heap`);
}
console.log('\n Email object memory analysis:');
memorySnapshots.forEach((snapshot, index) => {
const memoryIncrease = snapshot.heap - initialMemory.heapUsed;
console.log(` Batch ${snapshot.batch} (${snapshot.size} emails): +${memoryIncrease.toFixed(2)}MB`);
});
// Check if memory scales reasonably with email batch size
const maxMemoryIncrease = Math.max(...memorySnapshots.map(s => s.heap - initialMemory.heapUsed));
const avgBatchSize = batchSizes.reduce((a, b) => a + b, 0) / batchSizes.length;
console.log(` Maximum memory increase: +${maxMemoryIncrease.toFixed(2)}MB`);
console.log(` Average batch size: ${avgBatchSize} emails`);
console.log(` Memory per email: ~${(maxMemoryIncrease / avgBatchSize * 1024).toFixed(1)}KB`);
console.log(` Email object lifecycle: ${maxMemoryIncrease < 10 ? 'Efficient' : 'Needs optimization'}`);
smtpClient.close();
} finally {
testServer.close();
}
});
// Scenario 3: Long-Running Client Memory Stability
await test.test('Scenario 3: Long-Running Client Memory Stability', async () => {
console.log('\n⏱ Testing long-running client memory stability...');
const testServer = await createTestServer({
responseDelay: 5 // Fast responses for sustained operation
});
try {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 2,
maxMessages: 1000
});
const initialMemory = getMemoryUsage();
console.log(` Initial memory: ${initialMemory.heapUsed}MB heap`);
console.log(' Starting sustained email sending operation...');
const memoryMeasurements = [];
const totalEmails = 200; // Reduced for test efficiency
const measurementInterval = 40; // Measure every 40 emails
let emailsSent = 0;
let emailsFailed = 0;
for (let i = 0; i < totalEmails; i++) {
const email = new Email({
from: 'sender@longrunning.test',
to: [`recipient${i}@longrunning.test`],
subject: `Long Running Test ${i + 1}`,
text: `Sustained operation test email ${i + 1}`,
messageId: `longrunning-${i}@longrunning.test`
});
try {
await smtpClient.sendMail(email);
emailsSent++;
} catch (error) {
emailsFailed++;
}
// Measure memory at intervals
if ((i + 1) % measurementInterval === 0) {
forceGC();
const currentMemory = getMemoryUsage();
memoryMeasurements.push({
emailCount: i + 1,
heap: currentMemory.heapUsed,
rss: currentMemory.rss,
timestamp: Date.now()
});
console.log(` ${i + 1}/${totalEmails} emails: ${currentMemory.heapUsed}MB heap`);
}
}
console.log('\n Long-running memory analysis:');
console.log(` Emails sent: ${emailsSent}, Failed: ${emailsFailed}`);
memoryMeasurements.forEach((measurement, index) => {
const memoryIncrease = measurement.heap - initialMemory.heapUsed;
console.log(` After ${measurement.emailCount} emails: +${memoryIncrease.toFixed(2)}MB heap`);
});
// Analyze memory growth trend
if (memoryMeasurements.length >= 2) {
const firstMeasurement = memoryMeasurements[0];
const lastMeasurement = memoryMeasurements[memoryMeasurements.length - 1];
const memoryGrowth = lastMeasurement.heap - firstMeasurement.heap;
const emailsProcessed = lastMeasurement.emailCount - firstMeasurement.emailCount;
const growthRate = (memoryGrowth / emailsProcessed) * 1000; // KB per email
console.log(` Memory growth over operation: +${memoryGrowth.toFixed(2)}MB`);
console.log(` Growth rate: ~${growthRate.toFixed(2)}KB per email`);
console.log(` Memory stability: ${growthRate < 5 ? 'Excellent' : growthRate < 15 ? 'Good' : 'Concerning'}`);
}
smtpClient.close();
} finally {
testServer.close();
}
});
// Scenario 4: Buffer and Stream Memory Management
await test.test('Scenario 4: Buffer and Stream Memory Management', async () => {
console.log('\n🌊 Testing buffer and stream memory management...');
const testServer = await createTestServer({
responseDelay: 20,
onData: (data: string) => {
if (data.includes('large-attachment')) {
console.log(' [Server] Processing large attachment email');
}
}
});
try {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 1,
streamingMode: true, // Enable streaming for large content
bufferManagement: true
});
const initialMemory = getMemoryUsage();
console.log(` Initial memory: ${initialMemory.heapUsed}MB heap`);
console.log(' Testing with various buffer sizes...');
const bufferSizes = [
{ size: 1024, name: '1KB' },
{ size: 10240, name: '10KB' },
{ size: 102400, name: '100KB' },
{ size: 512000, name: '500KB' },
{ size: 1048576, name: '1MB' }
];
for (const bufferTest of bufferSizes) {
console.log(` Testing ${bufferTest.name} buffer size...`);
const beforeMemory = getMemoryUsage();
// Create large text content
const largeText = 'X'.repeat(bufferTest.size);
const email = new Email({
from: 'sender@buffermem.test',
to: ['recipient@buffermem.test'],
subject: `Buffer Memory Test - ${bufferTest.name}`,
text: largeText,
messageId: `large-attachment-${bufferTest.size}@buffermem.test`
});
try {
const result = await smtpClient.sendMail(email);
console.log(`${bufferTest.name} email sent successfully`);
} catch (error) {
console.log(`${bufferTest.name} email failed: ${error.message}`);
}
// Force cleanup
forceGC();
await new Promise(resolve => setTimeout(resolve, 100));
const afterMemory = getMemoryUsage();
const memoryDiff = afterMemory.heap - beforeMemory.heap;
console.log(` Memory impact: ${memoryDiff > 0 ? '+' : ''}${memoryDiff.toFixed(2)}MB`);
console.log(` Buffer efficiency: ${Math.abs(memoryDiff) < (bufferTest.size / 1024 / 1024) ? 'Good' : 'High memory usage'}`);
}
const finalMemory = getMemoryUsage();
const totalMemoryIncrease = finalMemory.heap - initialMemory.heapUsed;
console.log(`\n Buffer memory management summary:`);
console.log(` Total memory increase: +${totalMemoryIncrease.toFixed(2)}MB`);
console.log(` Buffer management efficiency: ${totalMemoryIncrease < 5 ? 'Excellent' : 'Needs optimization'}`);
smtpClient.close();
} finally {
testServer.close();
}
});
// Scenario 5: Event Listener Memory Management
await test.test('Scenario 5: Event Listener Memory Management', async () => {
console.log('\n🎧 Testing event listener memory management...');
const testServer = await createTestServer({
responseDelay: 15
});
try {
const initialMemory = getMemoryUsage();
console.log(` Initial memory: ${initialMemory.heapUsed}MB heap`);
console.log(' Phase 1: Creating clients with many event listeners...');
const clients = [];
const memorySnapshots = [];
for (let clientIndex = 0; clientIndex < 10; clientIndex++) {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 1
});
// Add multiple event listeners to test memory management
const eventHandlers = [];
for (let i = 0; i < 5; i++) {
const handler = (data: any) => {
// Event handler logic
};
eventHandlers.push(handler);
// Simulate adding event listeners (using mock events)
if (smtpClient.on) {
smtpClient.on('connect', handler);
smtpClient.on('error', handler);
smtpClient.on('close', handler);
}
}
clients.push({ client: smtpClient, handlers: eventHandlers });
// Send a test email through each client
const email = new Email({
from: 'sender@eventmem.test',
to: ['recipient@eventmem.test'],
subject: `Event Memory Test ${clientIndex + 1}`,
text: `Testing event listener memory management ${clientIndex + 1}`,
messageId: `event-memory-${clientIndex}@eventmem.test`
});
try {
await smtpClient.sendMail(email);
console.log(` Client ${clientIndex + 1}: Email sent`);
} catch (error) {
console.log(` Client ${clientIndex + 1}: Failed - ${error.message}`);
}
// Measure memory after each client
if ((clientIndex + 1) % 3 === 0) {
forceGC();
const currentMemory = getMemoryUsage();
memorySnapshots.push({
clientCount: clientIndex + 1,
heap: currentMemory.heapUsed
});
console.log(` Memory after ${clientIndex + 1} clients: ${currentMemory.heapUsed}MB`);
}
}
console.log(' Phase 2: Closing all clients and removing listeners...');
for (let i = 0; i < clients.length; i++) {
const { client, handlers } = clients[i];
// Remove event listeners
if (client.removeAllListeners) {
client.removeAllListeners();
}
// Close client
client.close();
if ((i + 1) % 3 === 0) {
forceGC();
const currentMemory = getMemoryUsage();
console.log(` Memory after closing ${i + 1} clients: ${currentMemory.heapUsed}MB`);
}
}
// Final memory check
forceGC();
await new Promise(resolve => setTimeout(resolve, 200));
const finalMemory = getMemoryUsage();
console.log('\n Event listener memory analysis:');
memorySnapshots.forEach(snapshot => {
const increase = snapshot.heap - initialMemory.heapUsed;
console.log(` ${snapshot.clientCount} clients: +${increase.toFixed(2)}MB`);
});
const finalIncrease = finalMemory.heap - initialMemory.heapUsed;
console.log(` Final memory after cleanup: +${finalIncrease.toFixed(2)}MB`);
console.log(` Event listener cleanup: ${finalIncrease < 1 ? 'Excellent' : 'Memory retained'}`);
} finally {
testServer.close();
}
});
console.log('\n✅ CREL-05: Memory Leak Prevention Reliability Tests completed');
console.log('🧠 All memory management scenarios tested successfully');
});