@@ -12,6 +12,8 @@ import {
12
12
} from "@cocalc/conat/service/terminal" ;
13
13
import { until } from "@cocalc/util/async-utils" ;
14
14
15
+ const DEFAULT_HEARTBEAT_INTERVAL = 15_000 ;
16
+
15
17
type State = "disconnected" | "init" | "running" | "closed" ;
16
18
17
19
export class ConatTerminal extends EventEmitter {
@@ -29,6 +31,7 @@ export class ConatTerminal extends EventEmitter {
29
31
private writeQueue : string = "" ;
30
32
private ephemeral ?: boolean ;
31
33
private computeServers ?;
34
+ private heartbeatInterval : number ;
32
35
33
36
constructor ( {
34
37
project_id,
@@ -40,6 +43,7 @@ export class ConatTerminal extends EventEmitter {
40
43
options,
41
44
measureSize,
42
45
ephemeral,
46
+ heartbeatInterval = DEFAULT_HEARTBEAT_INTERVAL ,
43
47
} : {
44
48
project_id : string ;
45
49
path : string ;
@@ -50,6 +54,7 @@ export class ConatTerminal extends EventEmitter {
50
54
options ?;
51
55
measureSize ?;
52
56
ephemeral ?: boolean ;
57
+ heartbeatInterval ?: number ;
53
58
} ) {
54
59
super ( ) ;
55
60
this . ephemeral = ephemeral ;
@@ -63,13 +68,35 @@ export class ConatTerminal extends EventEmitter {
63
68
this . terminalResize = terminalResize ;
64
69
this . openPaths = openPaths ;
65
70
this . closePaths = closePaths ;
71
+ this . heartbeatInterval = heartbeatInterval ;
66
72
webapp_client . conat_client . on ( "connected" , this . clearWriteQueue ) ;
67
73
this . computeServers = webapp_client . project_client . computeServers (
68
74
this . project_id ,
69
75
) ;
70
76
this . computeServers ?. on ( "change" , this . handleComputeServersChange ) ;
71
77
}
72
78
79
+ // ping server periodically -- if failure, closes conenction immediately
80
+ // so it can be fixed when user comes back, instead of having to react to
81
+ // a write failing (which also handles the same issue)
82
+ private heartbeat = reuseInFlight ( async ( ) => {
83
+ await until (
84
+ async ( ) => {
85
+ if ( this . isClosed ( ) ) {
86
+ return true ;
87
+ }
88
+ try {
89
+ await this . api . conat . ping ( { maxWait : 5000 } ) ;
90
+ return false ;
91
+ } catch {
92
+ this . close ( ) ;
93
+ return true ;
94
+ }
95
+ } ,
96
+ { min : this . heartbeatInterval , max : this . heartbeatInterval } ,
97
+ ) ;
98
+ } ) ;
99
+
73
100
clearWriteQueue = ( ) => {
74
101
if ( this . writeQueue ) {
75
102
this . write ( "" ) ;
@@ -147,6 +174,8 @@ export class ConatTerminal extends EventEmitter {
147
174
}
148
175
} ;
149
176
177
+ isClosed = ( ) => this . state == "closed" ;
178
+
150
179
close = ( ) => {
151
180
webapp_client . conat_client . removeListener (
152
181
"connected" ,
@@ -239,6 +268,7 @@ export class ConatTerminal extends EventEmitter {
239
268
init = reuseInFlight ( async ( ) => {
240
269
await Promise . all ( [ this . start ( ) , this . getStream ( ) ] ) ;
241
270
await this . setReady ( ) ;
271
+ this . heartbeat ( ) ;
242
272
} ) ;
243
273
244
274
private handleStreamData = ( data ) => {
0 commit comments