@@ -8,12 +8,23 @@ import { changefeed, renew } from "@cocalc/nats/changefeed/client";
8
8
import { delay } from "awaiting" ;
9
9
10
10
const HEARTBEAT = 15000 ;
11
+ const HEARTBEAT_MISS_THRESH = 5000 ;
11
12
12
13
// this should be significantly shorter than HEARTBEAT.
13
14
// if user closes browser and comes back, then this is the time they may have to wait
14
15
// for their changefeeds to reconnect, since clock jumps forward...
15
16
const HEARTBEAT_CHECK_DELAY = 3000 ;
16
17
18
+ const MAX_CHANGEFEED_LIFETIME = 1000 * 60 * 60 * 8 ;
19
+
20
+ // low level debugging of changefeeds
21
+ const LOW_LEVEL_DEBUG = false ;
22
+ const log = LOW_LEVEL_DEBUG
23
+ ? ( ...args ) => {
24
+ console . log ( "changefeed: " , ...args ) ;
25
+ }
26
+ : ( ..._args ) => { } ;
27
+
17
28
export class NatsChangefeed extends EventEmitter {
18
29
private account_id : string ;
19
30
private query ;
@@ -40,33 +51,34 @@ export class NatsChangefeed extends EventEmitter {
40
51
}
41
52
42
53
connect = async ( ) => {
54
+ log ( "creating new changefeed" , this . query ) ;
43
55
if ( this . state == "closed" ) return ;
44
56
this . natsSynctable = await changefeed ( {
45
57
account_id : this . account_id ,
46
58
query : this . query ,
47
59
options : this . options ,
48
60
heartbeat : HEARTBEAT ,
61
+ maxActualLifetime : MAX_CHANGEFEED_LIFETIME ,
49
62
} ) ;
50
- this . last_hb = Date . now ( ) ;
51
63
// @ts -ignore
52
64
if ( this . state == "closed" ) return ;
65
+ this . last_hb = Date . now ( ) ;
66
+ this . startHeartbeatMonitor ( ) ;
53
67
this . state = "connected" ;
54
68
const {
55
69
value : { id, lifetime } ,
56
70
} = await this . natsSynctable . next ( ) ;
57
71
this . id = id ;
58
72
this . lifetime = lifetime ;
59
- // console. log("got changefeed", { id, lifetime, query: this.query });
73
+ log ( "got changefeed" , { id, lifetime, query : this . query } ) ;
60
74
this . startRenewLoop ( ) ;
61
75
62
76
// @ts -ignore
63
77
while ( this . state != "closed" ) {
64
78
const { value } = await this . natsSynctable . next ( ) ;
65
79
this . last_hb = Date . now ( ) ;
66
80
if ( value ) {
67
- // got first non-heartbeat value (the first query might take LONGER than heartbeats)
68
81
this . startWatch ( ) ;
69
- this . startHeartbeatMonitor ( ) ;
70
82
return value [ Object . keys ( value ) [ 0 ] ] ;
71
83
}
72
84
}
@@ -97,7 +109,10 @@ export class NatsChangefeed extends EventEmitter {
97
109
}
98
110
this . last_hb = Date . now ( ) ;
99
111
if ( x ) {
112
+ log ( "got message " , this . query , x ) ;
100
113
this . emit ( "update" , x ) ;
114
+ } else {
115
+ log ( "got heartbeat" , this . query ) ;
101
116
}
102
117
}
103
118
} catch {
@@ -107,11 +122,19 @@ export class NatsChangefeed extends EventEmitter {
107
122
108
123
private startHeartbeatMonitor = async ( ) => {
109
124
while ( this . state != "closed" ) {
110
- if ( this . last_hb && Date . now ( ) - this . last_hb > 2 * HEARTBEAT ) {
125
+ await delay ( HEARTBEAT_CHECK_DELAY ) ;
126
+ if (
127
+ this . last_hb &&
128
+ Date . now ( ) - this . last_hb > HEARTBEAT + HEARTBEAT_MISS_THRESH
129
+ ) {
130
+ log ( "heartbeat failed" , this . query , {
131
+ last_hb : this . last_hb ,
132
+ diff : Date . now ( ) - this . last_hb ,
133
+ thresh : HEARTBEAT + HEARTBEAT_MISS_THRESH ,
134
+ } ) ;
111
135
this . close ( ) ;
112
136
return ;
113
137
}
114
- await delay ( HEARTBEAT_CHECK_DELAY ) ;
115
138
}
116
139
} ;
117
140
@@ -130,7 +153,9 @@ export class NatsChangefeed extends EventEmitter {
130
153
131
154
private startRenewLoop = async ( ) => {
132
155
while ( this . state != "closed" && this . lifetime && this . id ) {
133
- await delay ( this . lifetime / 3 ) ;
156
+ // max to avoid weird situation bombarding server or infinite loop
157
+ await delay ( Math . max ( 7500 , this . lifetime / 3 ) ) ;
158
+ log ( "renewing with lifetime " , this . lifetime , this . query ) ;
134
159
try {
135
160
await renew ( {
136
161
account_id : this . account_id ,
0 commit comments