@@ -45,14 +45,18 @@ export enum MessageType {
45
45
SERVER_SIDE_EMIT_RESPONSE ,
46
46
BROADCAST_CLIENT_COUNT ,
47
47
BROADCAST_ACK ,
48
+ ADAPTER_CLOSE ,
48
49
}
49
50
50
51
export type ClusterMessage = {
51
52
uid : string ;
52
53
nsp : string ;
53
54
} & (
54
55
| {
55
- type : MessageType . INITIAL_HEARTBEAT | MessageType . HEARTBEAT ;
56
+ type :
57
+ | MessageType . INITIAL_HEARTBEAT
58
+ | MessageType . HEARTBEAT
59
+ | MessageType . ADAPTER_CLOSE ;
56
60
}
57
61
| {
58
62
type : MessageType . BROADCAST ;
@@ -643,11 +647,21 @@ export abstract class ClusterAdapter extends Adapter {
643
647
) ;
644
648
}
645
649
650
+ interface CustomClusterRequest {
651
+ type : MessageType ;
652
+ resolve : Function ;
653
+ timeout : NodeJS . Timeout ;
654
+ missingUids : Set < string > ;
655
+ responses : any [ ] ;
656
+ }
657
+
646
658
export abstract class ClusterAdapterWithHeartbeat extends ClusterAdapter {
647
659
private readonly _opts : Required < ClusterAdapterOptions > ;
648
660
649
661
private heartbeatTimer : NodeJS . Timeout ;
650
662
private nodesMap : Map < string , number > = new Map ( ) ; // uid => timestamp of last message
663
+ private readonly cleanupTimer : NodeJS . Timeout | undefined ;
664
+ private customRequests : Map < string , CustomClusterRequest > = new Map ( ) ;
651
665
652
666
protected constructor ( nsp , opts : ClusterAdapterOptions ) {
653
667
super ( nsp ) ;
@@ -658,9 +672,19 @@ export abstract class ClusterAdapterWithHeartbeat extends ClusterAdapter {
658
672
} ,
659
673
opts
660
674
) ;
675
+ this . cleanupTimer = setInterval ( ( ) => {
676
+ const now = Date . now ( ) ;
677
+ this . nodesMap . forEach ( ( lastSeen , uid ) => {
678
+ const nodeSeemsDown = now - lastSeen > this . _opts . heartbeatTimeout ;
679
+ if ( nodeSeemsDown ) {
680
+ debug ( "node %s seems down" , uid ) ;
681
+ this . removeNode ( uid ) ;
682
+ }
683
+ } ) ;
684
+ } , 1_000 ) ;
661
685
}
662
686
663
- override init ( ) : Promise < void > | void {
687
+ override init ( ) {
664
688
this . publish ( {
665
689
type : MessageType . INITIAL_HEARTBEAT ,
666
690
} ) ;
@@ -677,8 +701,14 @@ export abstract class ClusterAdapterWithHeartbeat extends ClusterAdapter {
677
701
} , this . _opts . heartbeatInterval ) ;
678
702
}
679
703
680
- override close ( ) : Promise < void > | void {
704
+ override close ( ) {
705
+ this . publish ( {
706
+ type : MessageType . ADAPTER_CLOSE ,
707
+ } ) ;
681
708
clearTimeout ( this . heartbeatTimer ) ;
709
+ if ( this . cleanupTimer ) {
710
+ clearInterval ( this . cleanupTimer ) ;
711
+ }
682
712
}
683
713
684
714
override async onMessage ( message : ClusterMessage , offset ?: string ) {
@@ -700,6 +730,9 @@ export abstract class ClusterAdapterWithHeartbeat extends ClusterAdapter {
700
730
case MessageType . HEARTBEAT :
701
731
// nothing to do
702
732
break ;
733
+ case MessageType . ADAPTER_CLOSE :
734
+ this . removeNode ( message . uid ) ;
735
+ break ;
703
736
default :
704
737
super . onMessage ( message , offset ) ;
705
738
}
@@ -722,4 +755,178 @@ export abstract class ClusterAdapterWithHeartbeat extends ClusterAdapter {
722
755
723
756
return super . publish ( message ) ;
724
757
}
758
+
759
+ override async serverSideEmit ( packet : any [ ] ) {
760
+ const withAck = typeof packet [ packet . length - 1 ] === "function" ;
761
+
762
+ if ( ! withAck ) {
763
+ return this . publish ( {
764
+ type : MessageType . SERVER_SIDE_EMIT ,
765
+ data : {
766
+ packet,
767
+ } ,
768
+ } ) ;
769
+ }
770
+
771
+ const ack = packet . pop ( ) ;
772
+ const expectedResponseCount = this . nodesMap . size ;
773
+
774
+ debug (
775
+ 'waiting for %d responses to "serverSideEmit" request' ,
776
+ expectedResponseCount
777
+ ) ;
778
+
779
+ if ( expectedResponseCount <= 0 ) {
780
+ return ack ( null , [ ] ) ;
781
+ }
782
+
783
+ const requestId = randomId ( ) ;
784
+
785
+ const timeout = setTimeout ( ( ) => {
786
+ const storedRequest = this . customRequests . get ( requestId ) ;
787
+ if ( storedRequest ) {
788
+ ack (
789
+ new Error (
790
+ `timeout reached: missing ${ storedRequest . missingUids . size } responses`
791
+ ) ,
792
+ storedRequest . responses
793
+ ) ;
794
+ this . customRequests . delete ( requestId ) ;
795
+ }
796
+ } , DEFAULT_TIMEOUT ) ;
797
+
798
+ const storedRequest = {
799
+ type : MessageType . SERVER_SIDE_EMIT ,
800
+ resolve : ack ,
801
+ timeout,
802
+ missingUids : new Set ( [ ...this . nodesMap . keys ( ) ] ) ,
803
+ responses : [ ] ,
804
+ } ;
805
+ this . customRequests . set ( requestId , storedRequest ) ;
806
+
807
+ this . publish ( {
808
+ type : MessageType . SERVER_SIDE_EMIT ,
809
+ data : {
810
+ requestId, // the presence of this attribute defines whether an acknowledgement is needed
811
+ packet,
812
+ } ,
813
+ } ) ;
814
+ }
815
+
816
+ override async fetchSockets ( opts : BroadcastOptions ) : Promise < any [ ] > {
817
+ const [ localSockets , serverCount ] = await Promise . all ( [
818
+ super . fetchSockets ( {
819
+ rooms : opts . rooms ,
820
+ except : opts . except ,
821
+ flags : {
822
+ local : true ,
823
+ } ,
824
+ } ) ,
825
+ this . serverCount ( ) ,
826
+ ] ) ;
827
+ const expectedResponseCount = serverCount - 1 ;
828
+
829
+ if ( opts . flags ?. local || expectedResponseCount <= 0 ) {
830
+ return localSockets as any [ ] ;
831
+ }
832
+
833
+ const requestId = randomId ( ) ;
834
+
835
+ return new Promise < any [ ] > ( ( resolve , reject ) => {
836
+ const timeout = setTimeout ( ( ) => {
837
+ const storedRequest = this . customRequests . get ( requestId ) ;
838
+ if ( storedRequest ) {
839
+ reject (
840
+ new Error (
841
+ `timeout reached: missing ${ storedRequest . missingUids . size } responses`
842
+ )
843
+ ) ;
844
+ this . customRequests . delete ( requestId ) ;
845
+ }
846
+ } , opts . flags . timeout || DEFAULT_TIMEOUT ) ;
847
+
848
+ const storedRequest = {
849
+ type : MessageType . FETCH_SOCKETS ,
850
+ resolve,
851
+ timeout,
852
+ missingUids : new Set ( [ ...this . nodesMap . keys ( ) ] ) ,
853
+ responses : localSockets as any [ ] ,
854
+ } ;
855
+ this . customRequests . set ( requestId , storedRequest ) ;
856
+
857
+ this . publish ( {
858
+ type : MessageType . FETCH_SOCKETS ,
859
+ data : {
860
+ opts : encodeOptions ( opts ) ,
861
+ requestId,
862
+ } ,
863
+ } ) ;
864
+ } ) ;
865
+ }
866
+
867
+ override onResponse ( response : ClusterResponse ) {
868
+ const requestId = response . data . requestId ;
869
+
870
+ debug ( "received response %s to request %s" , response . type , requestId ) ;
871
+
872
+ switch ( response . type ) {
873
+ case MessageType . FETCH_SOCKETS_RESPONSE : {
874
+ const request = this . customRequests . get ( requestId ) ;
875
+
876
+ if ( ! request ) {
877
+ return ;
878
+ }
879
+
880
+ ( response . data . sockets as any [ ] ) . forEach ( ( socket ) =>
881
+ request . responses . push ( socket )
882
+ ) ;
883
+
884
+ request . missingUids . delete ( response . uid ) ;
885
+ if ( request . missingUids . size === 0 ) {
886
+ clearTimeout ( request . timeout ) ;
887
+ request . resolve ( request . responses ) ;
888
+ this . customRequests . delete ( requestId ) ;
889
+ }
890
+ break ;
891
+ }
892
+
893
+ case MessageType . SERVER_SIDE_EMIT_RESPONSE : {
894
+ const request = this . customRequests . get ( requestId ) ;
895
+
896
+ if ( ! request ) {
897
+ return ;
898
+ }
899
+
900
+ request . responses . push ( response . data . packet ) ;
901
+
902
+ request . missingUids . delete ( response . uid ) ;
903
+ if ( request . missingUids . size === 0 ) {
904
+ clearTimeout ( request . timeout ) ;
905
+ request . resolve ( null , request . responses ) ;
906
+ this . customRequests . delete ( requestId ) ;
907
+ }
908
+ break ;
909
+ }
910
+
911
+ default :
912
+ super . onResponse ( response ) ;
913
+ }
914
+ }
915
+
916
+ private removeNode ( uid : string ) {
917
+ this . customRequests . forEach ( ( request , requestId ) => {
918
+ request . missingUids . delete ( uid ) ;
919
+ if ( request . missingUids . size === 0 ) {
920
+ clearTimeout ( request . timeout ) ;
921
+ if ( request . type === MessageType . FETCH_SOCKETS ) {
922
+ request . resolve ( request . responses ) ;
923
+ } else if ( request . type === MessageType . SERVER_SIDE_EMIT ) {
924
+ request . resolve ( null , request . responses ) ;
925
+ }
926
+ this . customRequests . delete ( requestId ) ;
927
+ }
928
+ } ) ;
929
+
930
+ this . nodesMap . delete ( uid ) ;
931
+ }
725
932
}
0 commit comments