@@ -17,40 +17,88 @@ type ClientFrame =
1717
1818type ServerFrame = { type : 'event' ; topic : string ; event : Event } | { type : 'subscribed' ; topic : string } ;
1919
20+ type UnixSocketPubSubOptions = {
21+ maxRemoteClientQueuedBytes ?: number ;
22+ } ;
23+
2024type BrokerClient = {
2125 socket : net . Socket ;
2226 subscriptions : Set < string > ;
27+ writeChain : Promise < void > ;
28+ queuedBytes : number ;
2329} ;
2430
2531type SubscribeWaiter = {
2632 resolve : ( ) => void ;
2733 reject : ( error : Error ) => void ;
2834} ;
2935
30- function writeFrame ( socket : net . Socket , frame : ClientFrame | ServerFrame ) : Promise < void > {
36+ const DEFAULT_MAX_REMOTE_CLIENT_QUEUED_BYTES = 64 * 1024 * 1024 ;
37+
38+ function serializeFrame ( frame : ClientFrame | ServerFrame ) : string {
39+ return `${ JSON . stringify ( frame ) } \n` ;
40+ }
41+
42+ function writeSerializedFrame ( socket : net . Socket , serializedFrame : string ) : Promise < void > {
3143 return new Promise ( ( resolve , reject ) => {
32- const onError = ( error : Error ) => {
44+ let writeCompleted = false ;
45+ let drainCompleted = true ;
46+ let settled = false ;
47+
48+ const cleanup = ( ) => {
49+ socket . off ( 'error' , onError ) ;
50+ socket . off ( 'close' , onClose ) ;
3351 socket . off ( 'drain' , onDrain ) ;
34- reject ( error ) ;
3552 } ;
36- const onDrain = ( ) => {
37- socket . off ( 'error' , onError ) ;
53+ const settle = ( error ?: Error ) => {
54+ if ( settled ) return ;
55+ settled = true ;
56+ cleanup ( ) ;
57+ if ( error ) {
58+ reject ( error ) ;
59+ return ;
60+ }
3861 resolve ( ) ;
3962 } ;
63+ const maybeResolve = ( ) => {
64+ if ( writeCompleted && drainCompleted ) {
65+ settle ( ) ;
66+ }
67+ } ;
68+ const onError = ( error : Error ) => settle ( error ) ;
69+ const onClose = ( ) => settle ( new Error ( 'UnixSocketPubSub socket closed before write completed' ) ) ;
70+ const onDrain = ( ) => {
71+ drainCompleted = true ;
72+ maybeResolve ( ) ;
73+ } ;
4074
4175 socket . once ( 'error' , onError ) ;
42- const drained = socket . write ( `${ JSON . stringify ( frame ) } \n` , ( ) => {
43- if ( drained ) {
44- socket . off ( 'error' , onError ) ;
45- resolve ( ) ;
46- }
47- } ) ;
76+ socket . once ( 'close' , onClose ) ;
77+ let drained : boolean ;
78+ try {
79+ drained = socket . write ( serializedFrame , error => {
80+ if ( error ) {
81+ settle ( error ) ;
82+ return ;
83+ }
84+ writeCompleted = true ;
85+ maybeResolve ( ) ;
86+ } ) ;
87+ } catch ( error ) {
88+ settle ( error as Error ) ;
89+ return ;
90+ }
4891 if ( ! drained ) {
92+ drainCompleted = false ;
4993 socket . once ( 'drain' , onDrain ) ;
5094 }
5195 } ) ;
5296}
5397
98+ function writeFrame ( socket : net . Socket , frame : ClientFrame | ServerFrame ) : Promise < void > {
99+ return writeSerializedFrame ( socket , serializeFrame ( frame ) ) ;
100+ }
101+
54102function nextTick ( ) : Promise < void > {
55103 return new Promise ( resolve => setImmediate ( resolve ) ) ;
56104}
@@ -85,12 +133,14 @@ export class UnixSocketPubSub extends PubSub {
85133 #callbacks = new Map < string , Set < EventCallback > > ( ) ;
86134 #subscribeWaiters = new Map < string , SubscribeWaiter [ ] > ( ) ;
87135 #brokerClients = new Map < net . Socket , BrokerClient > ( ) ;
88- #pendingWrites: Promise < void > [ ] = [ ] ;
136+ #pendingWrites = new Set < Promise < void > > ( ) ;
89137 #recovering?: Promise < void > ;
138+ #maxRemoteClientQueuedBytes: number ;
90139
91- constructor ( socketPath : string ) {
140+ constructor ( socketPath : string , options : UnixSocketPubSubOptions = { } ) {
92141 super ( ) ;
93142 this . socketPath = socketPath ;
143+ this . #maxRemoteClientQueuedBytes = options . maxRemoteClientQueuedBytes ?? DEFAULT_MAX_REMOTE_CLIENT_QUEUED_BYTES ;
94144 }
95145
96146 override get supportedModes ( ) : ReadonlyArray < PubSubDeliveryMode > {
@@ -160,8 +210,7 @@ export class UnixSocketPubSub extends PubSub {
160210 }
161211
162212 async flush ( ) : Promise < void > {
163- await Promise . allSettled ( this . #pendingWrites) ;
164- this . #pendingWrites = [ ] ;
213+ await Promise . allSettled ( [ ...this . #pendingWrites] ) ;
165214 }
166215
167216 async close ( ) : Promise < void > {
@@ -172,10 +221,9 @@ export class UnixSocketPubSub extends PubSub {
172221 this . #clientSocket = undefined ;
173222 this . #rejectSubscribeWaiters( new Error ( 'UnixSocketPubSub is closed' ) ) ;
174223
175- for ( const client of this . #brokerClients. values ( ) ) {
176- client . socket . destroy ( ) ;
224+ for ( const client of [ ... this . #brokerClients. values ( ) ] ) {
225+ this . #removeBrokerClient ( client ) ;
177226 }
178- this . #brokerClients. clear ( ) ;
179227
180228 if ( this . #server) {
181229 await new Promise < void > ( resolve => this . #server?. close ( ( ) => resolve ( ) ) ) ;
@@ -436,21 +484,67 @@ export class UnixSocketPubSub extends PubSub {
436484 }
437485
438486 #handleBrokerClient( socket : net . Socket ) {
439- const client : BrokerClient = { socket, subscriptions : new Set ( ) } ;
487+ const client : BrokerClient = {
488+ socket,
489+ subscriptions : new Set ( ) ,
490+ writeChain : Promise . resolve ( ) ,
491+ queuedBytes : 0 ,
492+ } ;
440493 this . #brokerClients. set ( socket , client ) ;
441494 readFrames ( socket , frame => {
442495 const clientFrame = frame as ClientFrame ;
443496 if ( clientFrame . type === 'subscribe' ) {
444497 client . subscriptions . add ( clientFrame . topic ) ;
445- void writeFrame ( socket , { type : 'subscribed' , topic : clientFrame . topic } ) . catch ( ( ) => { } ) ;
498+ this . #enqueueBrokerClientWrite ( client , { type : 'subscribed' , topic : clientFrame . topic } ) ;
446499 } else if ( clientFrame . type === 'unsubscribe' ) {
447500 client . subscriptions . delete ( clientFrame . topic ) ;
448501 } else if ( clientFrame . type === 'publish' ) {
449502 void this . #publishFromBroker( clientFrame . topic , clientFrame . event ) ;
450503 }
451504 } ) ;
452- socket . on ( 'close' , ( ) => this . #brokerClients. delete ( socket ) ) ;
453- socket . on ( 'error' , ( ) => this . #brokerClients. delete ( socket ) ) ;
505+ socket . on ( 'close' , ( ) => this . #removeBrokerClient( client ) ) ;
506+ socket . on ( 'error' , ( ) => this . #removeBrokerClient( client ) ) ;
507+ }
508+
509+ #enqueueBrokerClientWrite( client : BrokerClient , frame : ServerFrame ) {
510+ if ( this . #brokerClients. get ( client . socket ) !== client || client . socket . destroyed ) return ;
511+
512+ const serializedFrame = serializeFrame ( frame ) ;
513+ const queuedBytes = Buffer . byteLength ( serializedFrame ) ;
514+ if ( client . queuedBytes + queuedBytes > this . #maxRemoteClientQueuedBytes) {
515+ this . #removeBrokerClient( client ) ;
516+ return ;
517+ }
518+
519+ client . queuedBytes += queuedBytes ;
520+
521+ const write = client . writeChain
522+ . catch ( ( ) => { } )
523+ . then ( async ( ) => {
524+ if ( this . #brokerClients. get ( client . socket ) !== client || client . socket . destroyed ) return ;
525+ await writeSerializedFrame ( client . socket , serializedFrame ) ;
526+ } )
527+ . catch ( ( ) => {
528+ this . #removeBrokerClient( client ) ;
529+ } )
530+ . finally ( ( ) => {
531+ client . queuedBytes = Math . max ( 0 , client . queuedBytes - queuedBytes ) ;
532+ } ) ;
533+
534+ client . writeChain = write ;
535+ this . #pendingWrites. add ( write ) ;
536+ void write . finally ( ( ) => this . #pendingWrites. delete ( write ) ) ;
537+ }
538+
539+ #removeBrokerClient( client : BrokerClient ) {
540+ if ( this . #brokerClients. get ( client . socket ) !== client ) return ;
541+ this . #brokerClients. delete ( client . socket ) ;
542+ client . subscriptions . clear ( ) ;
543+ client . queuedBytes = 0 ;
544+ client . writeChain = Promise . resolve ( ) ;
545+ if ( ! client . socket . destroyed ) {
546+ client . socket . destroy ( ) ;
547+ }
454548 }
455549
456550 #handleServerFrame( frame : ServerFrame ) {
@@ -484,10 +578,8 @@ export class UnixSocketPubSub extends PubSub {
484578 if ( ! client . subscriptions . has ( topic ) || client . socket . destroyed ) continue ;
485579 // Lazily build the frame only when we know at least one client needs it.
486580 frame ??= { type : 'event' , topic, event : brokerEvent } ;
487- const write = writeFrame ( client . socket , frame ) . catch ( ( ) => { } ) ;
488- this . #pendingWrites. push ( write ) ;
581+ this . #enqueueBrokerClientWrite( client , frame ) ;
489582 }
490- if ( frame ) await this . flush ( ) ;
491583 }
492584
493585 #deliverLocal( topic : string , event : Event ) {
@@ -510,14 +602,39 @@ export class UnixSocketPubSub extends PubSub {
510602 }
511603
512604 async #sendToBroker( frame : ClientFrame ) {
605+ try {
606+ await this . #sendToActiveBroker( frame ) ;
607+ } catch ( error ) {
608+ if ( this . #closed) throw error ;
609+ const failedSocket = this . #clientSocket;
610+ this . #clientSocket = undefined ;
611+ failedSocket ?. destroy ( ) ;
612+ await this . #ensureStarted( true ) ;
613+ await this . #sendToActiveBroker( frame ) ;
614+ }
615+ }
616+
617+ async #sendToActiveBroker( frame : ClientFrame ) {
513618 const socket = this . #clientSocket;
514619 if ( ! socket || socket . destroyed ) {
515620 await this . #ensureStarted( true ) ;
516621 }
622+ if ( this . #isBroker) {
623+ await this . #handlePromotedBrokerFrame( frame ) ;
624+ return ;
625+ }
517626 const activeSocket = this . #clientSocket;
518627 if ( ! activeSocket || activeSocket . destroyed ) {
519628 throw new Error ( 'UnixSocketPubSub is not connected to a broker' ) ;
520629 }
521630 await writeFrame ( activeSocket , frame ) ;
522631 }
632+
633+ async #handlePromotedBrokerFrame( frame : ClientFrame ) {
634+ if ( frame . type === 'subscribe' ) {
635+ this . #settleSubscribeWaiters( frame . topic ) ;
636+ } else if ( frame . type === 'publish' ) {
637+ await this . #publishFromBroker( frame . topic , frame . event ) ;
638+ }
639+ }
523640}
0 commit comments