@@ -43,6 +43,7 @@ import {
4343 TimingCollector ,
4444 type TimingInfo ,
4545} from "../timing.ts" ;
46+ import { TimingDump } from "../timingDump.ts" ;
4647
4748export type { ClientOptions , ClientSocketOptions , ClientSpawnOptions } ;
4849
@@ -59,16 +60,22 @@ export class Client {
5960 private closed = false ;
6061 private connecting : Promise < void > | undefined ;
6162 private timing : TimingCollector | undefined ;
63+ private timingDump : TimingDump | undefined ;
64+ private closing : Promise < void > | undefined ;
6265 private batchedRequests : { method : APIRequest [ "method" ] ; params : APIRequest [ "params" ] ; resolve : ( value : unknown ) => void ; reject : ( reason ?: any ) => void ; } [ ] = [ ] ;
6366 private nextBatch : NodeJS . Immediate | "manual" | undefined ;
6467
6568 constructor ( options : ClientOptions ) {
69+ this . timingDump = TimingDump . fromEnvironment ( "async" , isSpawnOptions ( options ) ? "spawn" : "socket" ) ;
70+ if ( this . timingDump || isSpawnOptions ( options ) && options . collectTiming ) {
71+ this . timing = new TimingCollector ( ) ;
72+ }
73+ if ( this . timingDump && isSpawnOptions ( options ) ) {
74+ options = { ...options , collectTiming : true } ;
75+ }
6676 this . options = options ;
6777 if ( isSpawnOptions ( options ) ) {
6878 configureFileSystemCallbacks ( options . fs ) ;
69- if ( options . collectTiming ) {
70- this . timing = new TimingCollector ( ) ;
71- }
7279 }
7380 }
7481
@@ -401,26 +408,52 @@ export class Client {
401408 return this . connection . sendRequest ( requestType , undefined ) ;
402409 }
403410
404- async close ( ) : Promise < void > {
411+ close ( ) : Promise < void > {
412+ return this . closing ??= this . closeWorker ( ) ;
413+ }
414+
415+ private async closeWorker ( ) : Promise < void > {
405416 await this . connecting ?. catch ( ( ) => { } ) ; // if connection is still in-progress, wait for it to finish before closing the connection
406417 this . closed = true ;
407- if ( this . connection ) {
408- this . connection . dispose ( ) ;
409- this . connection = undefined ;
410- }
411- if ( this . socket ) {
412- this . socket . destroy ( ) ;
413- this . socket = undefined ;
418+ try {
419+ if ( this . timingDump && this . timing ) {
420+ const local = this . timing . getInfo ( ) ;
421+ let server : ServerTimingInfo | undefined ;
422+ let serverTimingError : string | undefined ;
423+ const connection = this . connection ;
424+ // Closing alone does not reject pending JSON-RPC requests.
425+ const onClose = connection ?. onClose ( ( ) => connection . dispose ( ) ) ;
426+ try {
427+ if ( this . connected ) server = await this . fetchServerTiming ( ) ;
428+ }
429+ catch ( error ) {
430+ serverTimingError = String ( error ) ;
431+ }
432+ finally {
433+ onClose ?. dispose ( ) ;
434+ }
435+ this . timingDump . write ( local , server , serverTimingError ) ;
436+ }
414437 }
415- if ( this . process ) {
416- // Close stdin to unblock the server's read loop, allowing it to exit cleanly.
417- // The server is blocked on stdin.Read(), so just sending SIGTERM would deadlock:
418- // - Node won't exit while child is alive
419- // - Child can't process SIGTERM while blocked on read
420- // - Read won't error until stdin is closed
421- this . process . stdin ?. end ( ) ;
422- this . process = undefined ;
438+ finally {
439+ if ( this . connection ) {
440+ this . connection . dispose ( ) ;
441+ this . connection = undefined ;
442+ }
443+ if ( this . socket ) {
444+ this . socket . destroy ( ) ;
445+ this . socket = undefined ;
446+ }
447+ if ( this . process ) {
448+ // Close stdin to unblock the server's read loop, allowing it to exit cleanly.
449+ // The server is blocked on stdin.Read(), so just sending SIGTERM would deadlock:
450+ // - Node won't exit while child is alive
451+ // - Child can't process SIGTERM while blocked on read
452+ // - Read won't error until stdin is closed
453+ this . process . stdin ?. end ( ) ;
454+ this . process = undefined ;
455+ }
456+ this . connected = false ;
423457 }
424- this . connected = false ;
425458 }
426459}
0 commit comments