Skip to content

Commit 797c3f6

Browse files
committed
hook epics exit into interpreter lifecycle
allow interactive_ioc(call_exit=False) to cleanup and exit cleanly.
1 parent 4ff4869 commit 797c3f6

File tree

3 files changed

+35
-12
lines changed

3 files changed

+35
-12
lines changed

softioc/asyncio_dispatcher.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import asyncio
22
import inspect
33
import threading
4-
4+
import atexit
55

66
class AsyncioDispatcher:
77
def __init__(self, loop=None):
@@ -14,7 +14,17 @@ def __init__(self, loop=None):
1414
if loop is None:
1515
# Make one and run it in a background thread
1616
self.loop = asyncio.new_event_loop()
17-
threading.Thread(target=self.loop.run_forever).start()
17+
worker = threading.Thread(target=self.loop.run_forever)
18+
# Explicitly manage worker thread as part of interpreter shutdown.
19+
# Otherwise threading module will deadlock trying to join()
20+
# before our atexit hook runs, while the loop is still running.
21+
worker.daemon = True
22+
23+
@atexit.register
24+
def aioJoin(worker=worker, loop=self.loop):
25+
loop.call_soon_threadsafe(loop.stop)
26+
worker.join()
27+
worker.start()
1828

1929
def __call__(self, func, *args):
2030
async def async_wrapper():

softioc/imports.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ def auto_decode(result, func, args):
9999
epicsExit = Com.epicsExit
100100
epicsExit.argtypes = (c_int,)
101101

102+
epicsExitCallAtExits = Com.epicsExitCallAtExits
103+
epicsExitCallAtExits.argtypes = ()
104+
epicsExitCallAtExits.restype = None
105+
102106

103107
__all__ = [
104108
'get_field_offsets',

softioc/softioc.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111
__all__ = ['dbLoadDatabase', 'iocInit', 'interactive_ioc']
1212

1313

14-
epicsExit = imports.epicsExit
14+
# tie in epicsAtExit() to interpreter lifecycle
15+
@atexit.register
16+
def epicsAtPyExit():
17+
imports.epicsExitCallAtExits()
1518

1619

1720
def iocInit(dispatcher=None):
@@ -39,7 +42,7 @@ def iocInit(dispatcher=None):
3942

4043
def safeEpicsExit(code=0):
4144
'''Calls epicsExit() after ensuring Python exit handlers called.'''
42-
if hasattr(sys, 'exitfunc'): # py 2.x
45+
if hasattr(sys, 'exitfunc'): # py 2.x
4346
try:
4447
# Calling epicsExit() will bypass any atexit exit handlers, so call
4548
# them explicitly now.
@@ -48,12 +51,13 @@ def safeEpicsExit(code=0):
4851
# Make sure we don't try the exit handlers more than once!
4952
del sys.exitfunc
5053

51-
elif hasattr(atexit, '_run_exitfuncs'): # py 3.x
54+
elif hasattr(atexit, '_run_exitfuncs'): # py 3.x
5255
atexit._run_exitfuncs()
5356

5457
# calls epicsExitCallAtExits()
5558
# and then OS exit()
56-
epicsExit(code)
59+
imports.epicsExit(code)
60+
epicsExit = safeEpicsExit
5761

5862
# The following identifiers will be exported to interactive shell.
5963
command_names = []
@@ -240,10 +244,10 @@ def call_f(*args):
240244
# Hacked up exit object so that when soft IOC framework sends us an exit command
241245
# we actually exit.
242246
class Exiter:
243-
def __repr__(self):
244-
safeEpicsExit()
245-
def __call__(self):
246-
safeEpicsExit()
247+
def __repr__(self): # hack to exit when "called" with no parenthesis
248+
sys.exit(0)
249+
def __call__(self, code=0):
250+
sys.exit(code)
247251

248252
exit = Exiter()
249253
command_names.append('exit')
@@ -312,7 +316,12 @@ def interactive_ioc(context = {}, call_exit = True):
312316
# This suppresses irritating exit message introduced by Python3. Alas,
313317
# this option is only available in Python 3.6!
314318
interact_args = dict(exitmsg = '')
315-
code.interact(local = dict(exports, **context), **interact_args)
319+
try:
320+
code.interact(local = dict(exports, **context), **interact_args)
321+
except SystemExit as e:
322+
if call_exit:
323+
safeEpicsExit(e.code)
324+
raise
316325

317326
if call_exit:
318-
safeEpicsExit()
327+
safeEpicsExit(0)

0 commit comments

Comments
 (0)