taist 0.1.4 → 0.1.6
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.
- package/lib/trace-collector.js +51 -14
- package/lib/trace-reporter.js +101 -0
- package/lib/vitest-reporter.js +4 -5
- package/package.json +1 -1
package/lib/trace-collector.js
CHANGED
|
@@ -157,34 +157,71 @@ export class TraceCollector extends EventEmitter {
|
|
|
157
157
|
this.traceIds.clear();
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Stop the collector gracefully.
|
|
162
|
+
* Sends shutdown signal to workers and waits for them to flush before closing.
|
|
163
|
+
* @param {number} timeout - Max time to wait for workers to disconnect (default: 2000ms)
|
|
164
|
+
*/
|
|
165
|
+
async stop(timeout = 2000) {
|
|
161
166
|
if (!this.started) {
|
|
162
167
|
return;
|
|
163
168
|
}
|
|
164
169
|
|
|
170
|
+
// Send shutdown signal to all connected workers
|
|
171
|
+
const shutdownMessage = JSON.stringify({ type: 'shutdown' }) + '\n';
|
|
172
|
+
for (const socket of this.connections) {
|
|
173
|
+
try {
|
|
174
|
+
socket.write(shutdownMessage);
|
|
175
|
+
} catch {
|
|
176
|
+
// Socket may already be closed
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Wait for connections to close gracefully, or timeout
|
|
181
|
+
const startTime = Date.now();
|
|
182
|
+
while (this.connections.size > 0 && (Date.now() - startTime) < timeout) {
|
|
183
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
184
|
+
}
|
|
185
|
+
|
|
165
186
|
return new Promise((resolve) => {
|
|
166
|
-
//
|
|
187
|
+
// Gracefully close any remaining connections (allows pending data to be read)
|
|
167
188
|
for (const socket of this.connections) {
|
|
168
|
-
|
|
189
|
+
try {
|
|
190
|
+
socket.end();
|
|
191
|
+
} catch {
|
|
192
|
+
try { socket.destroy(); } catch { /* ignore */ }
|
|
193
|
+
}
|
|
169
194
|
}
|
|
170
|
-
this.connections.clear();
|
|
171
|
-
|
|
172
|
-
// Close server
|
|
173
|
-
this.server.close(() => {
|
|
174
|
-
this.started = false;
|
|
175
195
|
|
|
176
|
-
|
|
177
|
-
|
|
196
|
+
// Give a moment for final data to arrive before destroying
|
|
197
|
+
setTimeout(() => {
|
|
198
|
+
// Force destroy any sockets that didn't close gracefully
|
|
199
|
+
for (const socket of this.connections) {
|
|
178
200
|
try {
|
|
179
|
-
|
|
201
|
+
socket.destroy();
|
|
180
202
|
} catch {
|
|
181
203
|
// Ignore
|
|
182
204
|
}
|
|
183
205
|
}
|
|
206
|
+
this.connections.clear();
|
|
207
|
+
|
|
208
|
+
// Close server
|
|
209
|
+
this.server.close(() => {
|
|
210
|
+
this.started = false;
|
|
211
|
+
|
|
212
|
+
// Clean up socket file
|
|
213
|
+
if (process.platform !== "win32") {
|
|
214
|
+
try {
|
|
215
|
+
fs.unlinkSync(this.socketPath);
|
|
216
|
+
} catch {
|
|
217
|
+
// Ignore
|
|
218
|
+
}
|
|
219
|
+
}
|
|
184
220
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
221
|
+
this.emit("stopped");
|
|
222
|
+
resolve();
|
|
223
|
+
});
|
|
224
|
+
}, 100); // 100ms grace period for data to be read
|
|
188
225
|
});
|
|
189
226
|
}
|
|
190
227
|
|
package/lib/trace-reporter.js
CHANGED
|
@@ -79,6 +79,28 @@ export class TraceReporter extends EventEmitter {
|
|
|
79
79
|
// Don't keep the process alive just for tracing
|
|
80
80
|
this.socket.unref();
|
|
81
81
|
|
|
82
|
+
// Handle incoming messages from collector (e.g., shutdown signal)
|
|
83
|
+
let dataBuffer = '';
|
|
84
|
+
this.socket.on("data", (chunk) => {
|
|
85
|
+
dataBuffer += chunk.toString();
|
|
86
|
+
const lines = dataBuffer.split('\n');
|
|
87
|
+
dataBuffer = lines.pop(); // Keep incomplete line
|
|
88
|
+
|
|
89
|
+
for (const line of lines) {
|
|
90
|
+
if (line.trim()) {
|
|
91
|
+
try {
|
|
92
|
+
const message = JSON.parse(line);
|
|
93
|
+
if (message.type === 'shutdown') {
|
|
94
|
+
logger.debug("[reporter] Received shutdown signal, flushing...");
|
|
95
|
+
this._handleShutdown();
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
// Ignore parse errors
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
82
104
|
this.socket.on("error", (err) => {
|
|
83
105
|
this.connected = false;
|
|
84
106
|
this.connecting = false;
|
|
@@ -128,6 +150,85 @@ export class TraceReporter extends EventEmitter {
|
|
|
128
150
|
}
|
|
129
151
|
}
|
|
130
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Handle shutdown signal from collector.
|
|
155
|
+
* Flushes buffer and waits for data to be sent before closing.
|
|
156
|
+
*/
|
|
157
|
+
_handleShutdown() {
|
|
158
|
+
if (this.closed) return;
|
|
159
|
+
|
|
160
|
+
this._stopFlushTimer();
|
|
161
|
+
|
|
162
|
+
// Flush and wait for data to be sent
|
|
163
|
+
if (this.buffer.length > 0 && this.socket && this.connected) {
|
|
164
|
+
const traces = this.buffer.splice(0, this.buffer.length);
|
|
165
|
+
const message = JSON.stringify({
|
|
166
|
+
type: "batch",
|
|
167
|
+
workerId: this.workerId,
|
|
168
|
+
data: traces,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
logger.debug("[reporter] Shutdown flushing", traces.length, "traces");
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
// Write data
|
|
175
|
+
const flushed = this.socket.write(message + "\n");
|
|
176
|
+
|
|
177
|
+
if (flushed) {
|
|
178
|
+
// Data was written to kernel buffer, use setImmediate to allow
|
|
179
|
+
// the event loop to process and send the data before closing
|
|
180
|
+
setImmediate(() => {
|
|
181
|
+
this._gracefulClose();
|
|
182
|
+
});
|
|
183
|
+
} else {
|
|
184
|
+
// Buffer is full, wait for drain
|
|
185
|
+
this.socket.once('drain', () => {
|
|
186
|
+
logger.debug("[reporter] Drained, closing");
|
|
187
|
+
this._gracefulClose();
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
} catch (err) {
|
|
191
|
+
logger.debug("[reporter] Write error:", err.message);
|
|
192
|
+
this.close();
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
this._gracefulClose();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Close connection gracefully, allowing pending data to be sent.
|
|
201
|
+
*/
|
|
202
|
+
_gracefulClose() {
|
|
203
|
+
if (!this.socket) {
|
|
204
|
+
this.close();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Use socket.end() for graceful TCP close (sends FIN, allows pending data)
|
|
209
|
+
// Add a small delay to allow the collector to read the data
|
|
210
|
+
this.socket.once('close', () => {
|
|
211
|
+
logger.debug("[reporter] Socket closed gracefully");
|
|
212
|
+
this.closed = true;
|
|
213
|
+
this.connected = false;
|
|
214
|
+
this.socket = null;
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Set a timeout in case close doesn't happen
|
|
218
|
+
const closeTimeout = setTimeout(() => {
|
|
219
|
+
logger.debug("[reporter] Close timeout, forcing");
|
|
220
|
+
this.close();
|
|
221
|
+
}, 500);
|
|
222
|
+
closeTimeout.unref();
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
this.socket.end();
|
|
226
|
+
} catch {
|
|
227
|
+
clearTimeout(closeTimeout);
|
|
228
|
+
this.close();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
131
232
|
/**
|
|
132
233
|
* Report a single trace event
|
|
133
234
|
*/
|
package/lib/vitest-reporter.js
CHANGED
|
@@ -148,16 +148,15 @@ export class TaistReporter {
|
|
|
148
148
|
await this.collectorReady;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
//
|
|
152
|
-
|
|
151
|
+
// Stop the collector gracefully - this sends shutdown signal to workers
|
|
152
|
+
// and waits for them to flush their traces before closing (up to 2s timeout)
|
|
153
|
+
await this.collector.stop();
|
|
153
154
|
|
|
154
|
-
// Get collected traces
|
|
155
|
+
// Get collected traces (after workers have flushed)
|
|
155
156
|
if (this.options.showTrace) {
|
|
156
157
|
this.results.trace = this.collector.getTraces();
|
|
157
158
|
}
|
|
158
159
|
|
|
159
|
-
// Stop the collector
|
|
160
|
-
await this.collector.stop();
|
|
161
160
|
this.collector = null;
|
|
162
161
|
}
|
|
163
162
|
|