@@ -12,6 +12,8 @@ import {
1212} from "@cocalc/conat/service/terminal" ;
1313import { until } from "@cocalc/util/async-utils" ;
1414
15+ const DEFAULT_HEARTBEAT_INTERVAL = 15_000 ;
16+
1517type State = "disconnected" | "init" | "running" | "closed" ;
1618
1719export class ConatTerminal extends EventEmitter {
@@ -29,6 +31,7 @@ export class ConatTerminal extends EventEmitter {
2931 private writeQueue : string = "" ;
3032 private ephemeral ?: boolean ;
3133 private computeServers ?;
34+ private heartbeatInterval : number ;
3235
3336 constructor ( {
3437 project_id,
@@ -40,6 +43,7 @@ export class ConatTerminal extends EventEmitter {
4043 options,
4144 measureSize,
4245 ephemeral,
46+ heartbeatInterval = DEFAULT_HEARTBEAT_INTERVAL ,
4347 } : {
4448 project_id : string ;
4549 path : string ;
@@ -50,6 +54,7 @@ export class ConatTerminal extends EventEmitter {
5054 options ?;
5155 measureSize ?;
5256 ephemeral ?: boolean ;
57+ heartbeatInterval ?: number ;
5358 } ) {
5459 super ( ) ;
5560 this . ephemeral = ephemeral ;
@@ -63,13 +68,35 @@ export class ConatTerminal extends EventEmitter {
6368 this . terminalResize = terminalResize ;
6469 this . openPaths = openPaths ;
6570 this . closePaths = closePaths ;
71+ this . heartbeatInterval = heartbeatInterval ;
6672 webapp_client . conat_client . on ( "connected" , this . clearWriteQueue ) ;
6773 this . computeServers = webapp_client . project_client . computeServers (
6874 this . project_id ,
6975 ) ;
7076 this . computeServers ?. on ( "change" , this . handleComputeServersChange ) ;
7177 }
7278
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+
73100 clearWriteQueue = ( ) => {
74101 if ( this . writeQueue ) {
75102 this . write ( "" ) ;
@@ -147,6 +174,8 @@ export class ConatTerminal extends EventEmitter {
147174 }
148175 } ;
149176
177+ isClosed = ( ) => this . state == "closed" ;
178+
150179 close = ( ) => {
151180 webapp_client . conat_client . removeListener (
152181 "connected" ,
@@ -239,6 +268,7 @@ export class ConatTerminal extends EventEmitter {
239268 init = reuseInFlight ( async ( ) => {
240269 await Promise . all ( [ this . start ( ) , this . getStream ( ) ] ) ;
241270 await this . setReady ( ) ;
271+ this . heartbeat ( ) ;
242272 } ) ;
243273
244274 private handleStreamData = ( data ) => {
0 commit comments