@@ -130,10 +130,12 @@ def __init__(self, **kwargs):
130
130
self .kernel_id = None
131
131
self .ws = None
132
132
self .ws_future = Future ()
133
- self .ws_future_cancelled = False
133
+ self .disconnected = False
134
134
135
135
@gen .coroutine
136
136
def _connect (self , kernel_id ):
137
+ # websocket is initialized before connection
138
+ self .ws = None
137
139
self .kernel_id = kernel_id
138
140
ws_url = url_path_join (
139
141
GatewayClient .instance ().ws_url ,
@@ -148,40 +150,48 @@ def _connect(self, kernel_id):
148
150
self .ws_future .add_done_callback (self ._connection_done )
149
151
150
152
def _connection_done (self , fut ):
151
- if not self .ws_future_cancelled : # prevent concurrent.futures._base.CancelledError
153
+ if not self .disconnected and fut . exception () is None : # prevent concurrent.futures._base.CancelledError
152
154
self .ws = fut .result ()
153
155
self .log .debug ("Connection is ready: ws: {}" .format (self .ws ))
154
156
else :
155
- self .log .warning ("Websocket connection has been cancelled via client disconnect before its establishment . "
157
+ self .log .warning ("Websocket connection has been closed via client disconnect or due to error . "
156
158
"Kernel with ID '{}' may not be terminated on GatewayClient: {}" .
157
159
format (self .kernel_id , GatewayClient .instance ().url ))
158
160
159
161
def _disconnect (self ):
162
+ self .disconnected = True
160
163
if self .ws is not None :
161
164
# Close connection
162
165
self .ws .close ()
163
166
elif not self .ws_future .done ():
164
167
# Cancel pending connection. Since future.cancel() is a noop on tornado, we'll track cancellation locally
165
168
self .ws_future .cancel ()
166
- self .ws_future_cancelled = True
167
- self .log .debug ("_disconnect: ws_future_cancelled: {}" .format (self .ws_future_cancelled ))
169
+ self .log .debug ("_disconnect: future cancelled, disconnected: {}" .format (self .disconnected ))
168
170
169
171
@gen .coroutine
170
172
def _read_messages (self , callback ):
171
173
"""Read messages from gateway server."""
172
- while True :
174
+ while self . ws is not None :
173
175
message = None
174
- if not self .ws_future_cancelled :
176
+ if not self .disconnected :
175
177
try :
176
178
message = yield self .ws .read_message ()
177
179
except Exception as e :
178
180
self .log .error ("Exception reading message from websocket: {}" .format (e )) # , exc_info=True)
179
181
if message is None :
182
+ if not self .disconnected :
183
+ self .log .warning ("Lost connection to Gateway: {}" .format (self .kernel_id ))
180
184
break
181
185
callback (message ) # pass back to notebook client (see self.on_open and WebSocketChannelsHandler.open)
182
186
else : # ws cancelled - stop reading
183
187
break
184
188
189
+ if not self .disconnected : # if websocket is not disconnected by client, attept to reconnect to Gateway
190
+ self .log .info ("Attempting to re-establish the connection to Gateway: {}" .format (self .kernel_id ))
191
+ self ._connect (self .kernel_id )
192
+ loop = IOLoop .current ()
193
+ loop .add_future (self .ws_future , lambda future : self ._read_messages (callback ))
194
+
185
195
def on_open (self , kernel_id , message_callback , ** kwargs ):
186
196
"""Web socket connection open against gateway server."""
187
197
self ._connect (kernel_id )
@@ -205,7 +215,7 @@ def on_message(self, message):
205
215
def _write_message (self , message ):
206
216
"""Send message to gateway server."""
207
217
try :
208
- if not self .ws_future_cancelled :
218
+ if not self .disconnected and self . ws is not None :
209
219
self .ws .write_message (message )
210
220
except Exception as e :
211
221
self .log .error ("Exception writing message to websocket: {}" .format (e )) # , exc_info=True)
0 commit comments