taist 0.2.12 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -35,6 +35,8 @@ export class TraceReporter extends EventEmitter {
35
35
  this.workerId = options.workerId || process.pid;
36
36
  this.closed = false;
37
37
  this.shuttingDown = false; // Prevents race between shutdown signal and SIGTERM
38
+ this.pendingWrites = 0; // Track writes that have been initiated but callback hasn't fired
39
+ this.pendingWriteResolvers = []; // Resolvers to call when pendingWrites reaches 0
38
40
 
39
41
  logger.debug("[reporter] Created with socketPath:", this.socketPath, "flushImmediate:", this.flushImmediate);
40
42
 
@@ -162,50 +164,84 @@ export class TraceReporter extends EventEmitter {
162
164
 
163
165
  /**
164
166
  * Handle shutdown signal from collector.
165
- * Flushes buffer and waits for data to be sent before closing.
167
+ * Waits for pending writes and flushes buffer before closing.
166
168
  */
167
169
  _handleShutdown() {
168
170
  if (this.closed || this.shuttingDown) return;
169
171
 
172
+ const lifecycleDebug = process.env.TAIST_TRACE_LIFECYCLE === 'true';
173
+
170
174
  // Set flag immediately to prevent exit handlers from interfering
171
175
  this.shuttingDown = true;
172
176
  this._stopFlushTimer();
173
177
 
174
- // Flush and wait for data to be sent
175
- if (this.buffer.length > 0 && this.socket && this.connected) {
176
- const traces = this.buffer.splice(0, this.buffer.length);
177
- const message = JSON.stringify({
178
- type: "batch",
179
- workerId: this.workerId,
180
- data: traces,
178
+ if (lifecycleDebug) {
179
+ console.log(`[LIFECYCLE reporter] [${Date.now()}] _handleShutdown called, pendingWrites:`, this.pendingWrites, 'buffer:', this.buffer.length);
180
+ }
181
+
182
+ // Wait for any pending writes to complete first
183
+ const waitForPendingWrites = () => {
184
+ return new Promise((resolve) => {
185
+ if (this.pendingWrites === 0) {
186
+ resolve();
187
+ } else {
188
+ if (lifecycleDebug) {
189
+ console.log('[LIFECYCLE reporter] Waiting for', this.pendingWrites, 'pending writes...');
190
+ }
191
+ this.pendingWriteResolvers.push(resolve);
192
+ // Safety timeout - don't wait forever
193
+ setTimeout(() => {
194
+ if (lifecycleDebug && this.pendingWrites > 0) {
195
+ console.log('[LIFECYCLE reporter] Timeout waiting for pending writes, still have:', this.pendingWrites);
196
+ }
197
+ resolve();
198
+ }, 1000);
199
+ }
181
200
  });
201
+ };
182
202
 
183
- logger.debug("[reporter] Shutdown flushing", traces.length, "traces");
203
+ // Wait for pending writes, then flush any remaining buffer, then close
204
+ waitForPendingWrites().then(() => {
205
+ if (lifecycleDebug) {
206
+ console.log('[LIFECYCLE reporter] Pending writes done, flushing remaining buffer:', this.buffer.length);
207
+ }
184
208
 
185
- try {
186
- // Write data
187
- const flushed = this.socket.write(message + "\n");
188
-
189
- if (flushed) {
190
- // Data was written to kernel buffer, use setImmediate to allow
191
- // the event loop to process and send the data before closing
192
- setImmediate(() => {
193
- this._gracefulClose();
194
- });
195
- } else {
196
- // Buffer is full, wait for drain
197
- this.socket.once('drain', () => {
198
- logger.debug("[reporter] Drained, closing");
199
- this._gracefulClose();
200
- });
209
+ // Flush any remaining buffer
210
+ if (this.buffer.length > 0 && this.socket && this.connected) {
211
+ const traces = this.buffer.splice(0, this.buffer.length);
212
+ const message = JSON.stringify({
213
+ type: "batch",
214
+ workerId: this.workerId,
215
+ data: traces,
216
+ });
217
+
218
+ logger.debug("[reporter] Shutdown flushing", traces.length, "traces");
219
+
220
+ try {
221
+ // Write data
222
+ const flushed = this.socket.write(message + "\n");
223
+
224
+ if (flushed) {
225
+ // Data was written to kernel buffer, use setImmediate to allow
226
+ // the event loop to process and send the data before closing
227
+ setImmediate(() => {
228
+ this._gracefulClose();
229
+ });
230
+ } else {
231
+ // Buffer is full, wait for drain
232
+ this.socket.once('drain', () => {
233
+ logger.debug("[reporter] Drained, closing");
234
+ this._gracefulClose();
235
+ });
236
+ }
237
+ } catch (err) {
238
+ logger.debug("[reporter] Write error:", err.message);
239
+ this.close();
201
240
  }
202
- } catch (err) {
203
- logger.debug("[reporter] Write error:", err.message);
204
- this.close();
241
+ } else {
242
+ this._gracefulClose();
205
243
  }
206
- } else {
207
- this._gracefulClose();
208
- }
244
+ });
209
245
  }
210
246
 
211
247
  /**
@@ -335,8 +371,31 @@ export class TraceReporter extends EventEmitter {
335
371
  data: traces,
336
372
  });
337
373
 
374
+ // Track pending write
375
+ this.pendingWrites++;
376
+ if (lifecycleDebug) {
377
+ console.log(`[LIFECYCLE reporter] [${Date.now()}] pendingWrites++, now:`, this.pendingWrites);
378
+ }
379
+
338
380
  return new Promise((resolve, reject) => {
339
381
  const writeResult = this.socket.write(message + "\n", (err) => {
382
+ // Write completed (success or error)
383
+ this.pendingWrites--;
384
+ if (lifecycleDebug) {
385
+ console.log(`[LIFECYCLE reporter] [${Date.now()}] pendingWrites--, now:`, this.pendingWrites);
386
+ }
387
+
388
+ // If shutdown is waiting for pending writes, notify it
389
+ if (this.pendingWrites === 0 && this.pendingWriteResolvers.length > 0) {
390
+ if (lifecycleDebug) {
391
+ console.log('[LIFECYCLE reporter] All pending writes complete, notifying shutdown');
392
+ }
393
+ const resolvers = this.pendingWriteResolvers.splice(0);
394
+ for (const resolver of resolvers) {
395
+ resolver();
396
+ }
397
+ }
398
+
340
399
  if (err) {
341
400
  if (lifecycleDebug) console.log('[LIFECYCLE reporter] WRITE CALLBACK error:', err.message);
342
401
  // Put traces back in buffer on failure
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taist",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "description": "Token-Optimized Testing Framework for AI-Assisted Development",
5
5
  "main": "index.js",
6
6
  "type": "module",