Skip to content

Commit 1e4f1e6

Browse files
authored
Merge pull request #35 from mdavidsaver/master
Fixup atexit handling
2 parents 271481f + 797c3f6 commit 1e4f1e6

File tree

3 files changed

+43
-13
lines changed

3 files changed

+43
-13
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: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,11 @@ def auto_decode(result, func, args):
9797
iocInit.argtypes = ()
9898

9999
epicsExit = Com.epicsExit
100-
epicsExit.argtypes = ()
100+
epicsExit.argtypes = (c_int,)
101+
102+
epicsExitCallAtExits = Com.epicsExitCallAtExits
103+
epicsExitCallAtExits.argtypes = ()
104+
epicsExitCallAtExits.restype = None
101105

102106

103107
__all__ = [

softioc/softioc.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import sys
3+
import atexit
34
from ctypes import *
45
from tempfile import NamedTemporaryFile
56

@@ -10,7 +11,10 @@
1011
__all__ = ['dbLoadDatabase', 'iocInit', 'interactive_ioc']
1112

1213

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

1519

1620
def iocInit(dispatcher=None):
@@ -36,17 +40,24 @@ def iocInit(dispatcher=None):
3640
imports.iocInit()
3741

3842

39-
def safeEpicsExit():
43+
def safeEpicsExit(code=0):
4044
'''Calls epicsExit() after ensuring Python exit handlers called.'''
41-
if hasattr(sys, 'exitfunc'):
45+
if hasattr(sys, 'exitfunc'): # py 2.x
4246
try:
4347
# Calling epicsExit() will bypass any atexit exit handlers, so call
4448
# them explicitly now.
4549
sys.exitfunc()
4650
finally:
4751
# Make sure we don't try the exit handlers more than once!
4852
del sys.exitfunc
49-
epicsExit()
53+
54+
elif hasattr(atexit, '_run_exitfuncs'): # py 3.x
55+
atexit._run_exitfuncs()
56+
57+
# calls epicsExitCallAtExits()
58+
# and then OS exit()
59+
imports.epicsExit(code)
60+
epicsExit = safeEpicsExit
5061

5162
# The following identifiers will be exported to interactive shell.
5263
command_names = []
@@ -233,10 +244,10 @@ def call_f(*args):
233244
# Hacked up exit object so that when soft IOC framework sends us an exit command
234245
# we actually exit.
235246
class Exiter:
236-
def __repr__(self):
237-
safeEpicsExit()
238-
def __call__(self):
239-
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)
240251

241252
exit = Exiter()
242253
command_names.append('exit')
@@ -305,7 +316,12 @@ def interactive_ioc(context = {}, call_exit = True):
305316
# This suppresses irritating exit message introduced by Python3. Alas,
306317
# this option is only available in Python 3.6!
307318
interact_args = dict(exitmsg = '')
308-
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
309325

310326
if call_exit:
311-
safeEpicsExit()
327+
safeEpicsExit(0)

0 commit comments

Comments
 (0)