This is an automated email from the ASF dual-hosted git repository.

kezhenxu94 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking-nodejs.git


The following commit(s) were added to refs/heads/master by this push:
     new 2d85ed5  fix: add destroy methods to prevent memory leaks in protocol 
clients (#129)
2d85ed5 is described below

commit 2d85ed5ec59123274d7a4a6fcdafd12e3c6f1851
Author: wolfsilver <[email protected]>
AuthorDate: Thu Jan 29 21:11:21 2026 +0800

    fix: add destroy methods to prevent memory leaks in protocol clients (#129)
    
    Co-authored-by: wolfsilver <[email protected]>
---
 src/agent/protocol/Protocol.ts                     |  2 ++
 src/agent/protocol/grpc/GrpcProtocol.ts            |  6 ++++
 src/agent/protocol/grpc/clients/Client.ts          |  2 ++
 src/agent/protocol/grpc/clients/HeartbeatClient.ts | 10 ++++++
 .../protocol/grpc/clients/TraceReportClient.ts     | 37 ++++++++++++++++++++--
 src/index.ts                                       | 14 ++++++++
 6 files changed, 69 insertions(+), 2 deletions(-)

diff --git a/src/agent/protocol/Protocol.ts b/src/agent/protocol/Protocol.ts
index 842f3bc..062318a 100644
--- a/src/agent/protocol/Protocol.ts
+++ b/src/agent/protocol/Protocol.ts
@@ -28,4 +28,6 @@ export default interface Protocol {
   report(): this;
 
   flush(): Promise<any> | null;
+
+  destroy?(): void;
 }
diff --git a/src/agent/protocol/grpc/GrpcProtocol.ts 
b/src/agent/protocol/grpc/GrpcProtocol.ts
index 08ffa63..f151141 100644
--- a/src/agent/protocol/grpc/GrpcProtocol.ts
+++ b/src/agent/protocol/grpc/GrpcProtocol.ts
@@ -47,4 +47,10 @@ export default class GrpcProtocol implements Protocol {
   flush(): Promise<any> | null {
     return this.traceReportClient.flush();
   }
+
+  destroy(): void {
+    // Clean up both clients to prevent memory leaks
+    this.heartbeatClient.destroy?.();
+    this.traceReportClient.destroy?.();
+  }
 }
diff --git a/src/agent/protocol/grpc/clients/Client.ts 
b/src/agent/protocol/grpc/clients/Client.ts
index e9916ab..8d8fd06 100644
--- a/src/agent/protocol/grpc/clients/Client.ts
+++ b/src/agent/protocol/grpc/clients/Client.ts
@@ -23,4 +23,6 @@ export default interface Client {
   start(): void;
 
   flush(): Promise<any> | null;
+
+  destroy?(): void;
 }
diff --git a/src/agent/protocol/grpc/clients/HeartbeatClient.ts 
b/src/agent/protocol/grpc/clients/HeartbeatClient.ts
index b92d6e3..91cda52 100755
--- a/src/agent/protocol/grpc/clients/HeartbeatClient.ts
+++ b/src/agent/protocol/grpc/clients/HeartbeatClient.ts
@@ -89,4 +89,14 @@ export default class HeartbeatClient implements Client {
     logger.warn('HeartbeatClient does not need flush().');
     return null;
   }
+
+  destroy(): void {
+    // Clear heartbeat timer to prevent memory leak
+    if (this.heartbeatTimer) {
+      clearInterval(this.heartbeatTimer);
+      this.heartbeatTimer = undefined;
+    }
+
+    logger.info('HeartbeatClient destroyed and resources cleaned up');
+  }
 }
diff --git a/src/agent/protocol/grpc/clients/TraceReportClient.ts 
b/src/agent/protocol/grpc/clients/TraceReportClient.ts
index 3f6f102..8352604 100755
--- a/src/agent/protocol/grpc/clients/TraceReportClient.ts
+++ b/src/agent/protocol/grpc/clients/TraceReportClient.ts
@@ -34,16 +34,31 @@ export default class TraceReportClient implements Client {
   private readonly reporterClient: TraceSegmentReportServiceClient;
   private readonly buffer: Segment[] = [];
   private timeout?: NodeJS.Timeout;
+  private segmentFinishedListener: (segment: Segment) => void;
 
   constructor() {
     this.reporterClient = new TraceSegmentReportServiceClient(
       config.collectorAddress,
       config.secure ? grpc.credentials.createSsl() : 
grpc.credentials.createInsecure(),
     );
-    emitter.on('segment-finished', (segment) => {
+
+    // Store listener reference for cleanup
+    this.segmentFinishedListener = (segment: Segment) => {
+      // Limit buffer size to prevent memory leak during network issues
+      if (this.buffer.length >= config.maxBufferSize) {
+        logger.warn(
+          `Trace buffer reached maximum size (${config.maxBufferSize}). ` +
+          `Discarding oldest segment to prevent memory leak. ` +
+          `This may indicate network connectivity issues with the collector.`
+        );
+        this.buffer.shift(); // Remove oldest segment
+      }
+
       this.buffer.push(segment);
       this.timeout?.ref();
-    });
+    };
+
+    emitter.on('segment-finished', this.segmentFinishedListener);
   }
 
   get isConnected(): boolean {
@@ -107,4 +122,22 @@ export default class TraceReportClient implements Client {
           this.reportFunction(resolve);
         });
   }
+
+  destroy(): void {
+    // Clean up event listener to prevent memory leak
+    if (this.segmentFinishedListener) {
+      emitter.off('segment-finished', this.segmentFinishedListener);
+    }
+
+    // Clear timeout
+    if (this.timeout) {
+      clearTimeout(this.timeout);
+      this.timeout = undefined;
+    }
+
+    // Clear buffer
+    this.buffer.length = 0;
+
+    logger.info('TraceReportClient destroyed and resources cleaned up');
+  }
 }
diff --git a/src/index.ts b/src/index.ts
index a228620..ae2c494 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -72,6 +72,20 @@ class Agent {
       });
     });
   }
+
+  destroy(): void {
+    if (this.protocol === null) {
+      logger.warn('Trying to destroy() SkyWalking agent which is not 
started.');
+      return;
+    }
+
+    logger.info('Destroying SkyWalking agent and cleaning up resources');
+
+    // Clean up protocol resources
+    this.protocol.destroy?.();
+    this.protocol = null;
+    this.started = false;
+  }
 }
 
 export default new Agent();

Reply via email to