77import subprocess
88import re
99import html
10+ import signal
1011
1112from collections import deque
1213
@@ -136,6 +137,34 @@ def out_stream(self, s):
136137 }
137138self .send_response (self .iopub_socket ,'stream' ,_content )
138139
140+ def cleanup_dyalog (self ):
141+ try :
142+ if DYALOG_HOST == '127.0.0.1' :
143+ if self .connected and hasattr (self ,'dyalogTCP' ):
144+ self .ride_send (["Exit" , {"code" :0 }])
145+ if self .dyalog_subprocess :
146+ # Graceful termination
147+ self .dyalog_subprocess .terminate ()
148+ try :
149+ self .dyalog_subprocess .wait (timeout = 3 )
150+ except subprocess .TimeoutExpired :
151+ # Force kill if graceful termination fails
152+ self .dyalog_subprocess .kill ()
153+ self .dyalog_subprocess .wait ()
154+
155+ if hasattr (self ,'dyalogTCP' ):
156+ self .dyalogTCP .close ()
157+
158+ self .connected = False
159+
160+ except Exception as e :
161+ debug (f"Error in cleanup_dyalog:{ e } " )
162+
163+ def signal_handler (self ,signum ,frame ):
164+ debug (f"Received signal{ signum } , cleaning up..." )
165+ self .cleanup_dyalog ()
166+ sys .exit (0 )
167+
139168def dyalog_ride_connect (self ):
140169
141170timeout = time .time ()+ RIDE_INIT_CONNECT_TIME_OUT
@@ -196,7 +225,11 @@ def __init__(self, **kwargs):
196225#from ipykernel import get_connection_file
197226#s = get_connection_file()
198227# debug("########## " + str(s))
199-
228+
229+ # Register signal handlers and cleanup
230+ signal .signal (signal .SIGTERM ,self .signal_handler )
231+ signal .signal (signal .SIGINT ,self .signal_handler )
232+
200233self ._port = DYALOG_PORT
201234# lets find an available port
202235# this makes sense only if Dyalog APL and Jupyter executables are on the same host (localhost)
@@ -229,15 +262,15 @@ def __init__(self, **kwargs):
229262CloseKey (dyalogKey )
230263CloseKey (lastKey )
231264self .dyalog_subprocess = subprocess .Popen ([dyalogPath ,"RIDE_SPAWNED=1" ,"DYALOGQUIETUCMDBUILD=1" ,"Dyalog_LineEditor_Mode=1" ,'RIDE_INIT=SERVE::' + str (
232- self ._port ).strip (),'LOG_FILE=nul ' ,"DYALOGJUPYFOLDER=" + os .path .dirname (os .path .abspath (__file__ )),"LOAD=" + os .path .dirname (os .path .abspath (__file__ ))+ "\\ init.aplf" ])
265+ self ._port ).strip (),'LOG_FILE_INUSE=0 ' ,"DYALOGJUPYFOLDER=" + os .path .dirname (os .path .abspath (__file__ )),"LOAD=" + os .path .dirname (os .path .abspath (__file__ ))+ "\\ init.aplf" ])
233266else :
234267# linux, darwin... etc
235268dyalog_env = os .environ .copy ()
236269dyalog_env ['RIDE_INIT' ]= 'SERVE:*:' + str (self ._port ).strip ()
237270dyalog_env ['RIDE_SPAWNED' ]= '1'
238271dyalog_env ['DYALOGQUIETUCMDBUILD' ]= '1'
239272dyalog_env ['ENABLE_CEF' ]= '0'
240- dyalog_env ['LOG_FILE ' ]= '/dev/null '
273+ dyalog_env ['LOG_FILE_INUSE ' ]= '0 '
241274dyalog_env ['DYALOG_LINEEDITOR_MODE' ]= '1'
242275dyalog_env ['DYALOGJUPYFOLDER' ]= os .path .dirname (os .path .abspath (__file__ ))
243276if shutil .which ('mapl' ):
@@ -498,14 +531,5 @@ def define_function(self, lines):
498531
499532def do_shutdown (self ,restart ):
500533# shutdown Dyalog executable only if Jupyter kernel has started it.
501-
502- if DYALOG_HOST == '127.0.0.1' :
503- if self .connected :
504- self .ride_send (["Exit" , {"code" :0 }])
505- # time.sleep(2)
506- # if self.dyalog_subprocess:
507- # self.dyalog_subprocess.kill()
508-
509- self .dyalogTCP .close ()
510- self .connected = False
511- return {'status' :'ok' ,'restart' :restart }
534+ self .cleanup_dyalog ()
535+ return {'status' :'ok' ,'restart' :restart }