Skip to content

Commit

Permalink
call stop on autosave thread at dispatcher exit
Browse files Browse the repository at this point in the history
lint and isort
  • Loading branch information
jsouter committed Jul 4, 2024
1 parent f4b7aaf commit c6b90b4
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 23 deletions.
12 changes: 8 additions & 4 deletions softioc/asyncio_dispatcher.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import asyncio
import atexit
import inspect
import logging
import threading
import atexit
import signal
import threading

from . import autosave


class AsyncioDispatcher:
def __init__(self, loop=None, debug=False):
"""A dispatcher for `asyncio` based IOCs, suitable to be passed to
Expand Down Expand Up @@ -43,9 +45,9 @@ def __init__(self, loop=None, debug=False):
else:
self.loop = loop
# set up autosave thread
autosaver = autosave.Autosave()
self.__autosave = autosave.Autosave()
self.__autosave_worker = threading.Thread(
target=autosaver.loop,
target=self.__autosave.loop,
)
self.__autosave_worker.daemon = True
self.__autosave_worker.start()
Expand Down Expand Up @@ -80,6 +82,8 @@ def __shutdown(self):
self.loop.call_soon_threadsafe(self.__interrupt.set)
self.__worker.join()
self.__worker = None
self.__autosave.stop()
self.__autosave_worker.join()

def __call__(
self,
Expand Down
58 changes: 41 additions & 17 deletions softioc/autosave.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
import json
from pathlib import Path
from typing import Dict, List, Optional
from datetime import datetime
import shutil
from softioc.device_core import LookupRecordList
import time
import threading
from datetime import datetime
from pathlib import Path

from softioc.device_core import LookupRecordList

SAV_SUFFIX = "softsav"
SAVB_SUFFIX = "softsavB"


def configure(directory=None, save_period=None, device=None):
Autosave.save_period = save_period or Autosave.save_period
if device is None:
if Autosave.device_name is None:
from .builder import GetRecordNames

Autosave.device_name = GetRecordNames().prefix[0]
else:
Autosave.device_name = device
if directory is None and Autosave.directory is None:
raise RuntimeError("Autosave directory is not known, "
"call autosave.configure() with directory keyword argument")
raise RuntimeError(
"Autosave directory is not known, call "
"autosave.configure() with keyword argument "
"directory."
)
else:
Autosave.directory = Path(directory)


class Autosave:
_instance = None
save_period = 30.0
Expand All @@ -36,20 +41,27 @@ def __init__(
self,
):
if not self.directory:
raise RuntimeError("Autosave directory is not known, "
"call autosave.configure() with directory keyword argument")
raise RuntimeError(
"Autosave directory is not known, call "
"autosave.configure() with keyword argument "
"directory."
)
if not self.device_name:
raise RuntimeError("Device name is not known to autosave thread, "
"call autosave.configure() with device keyword argument")
raise RuntimeError(
"Device name is not known to autosave thread, "
"call autosave.configure() with device keyword argument"
)
self._last_saved_time = datetime.now()
if not self.directory.is_dir():
raise RuntimeError(f"{self.directory} is not a valid autosave directory")
raise RuntimeError(
f"{self.directory} is not a valid autosave directory"
)
if self.backup_on_restart:
self.backup_sav_file()
self.get_autosave_pvs()
self._state = {}
self._last_saved_state = {}
self._started = False
self._stop_event = threading.Event()
if self.enabled:
self.load() # load at startup if enabled

Expand Down Expand Up @@ -90,7 +102,10 @@ def _get_current_sav_path(self):

def _save(self, state):
try:
for path in [self._get_current_sav_path(), self._get_backup_save_path()]:
for path in [
self._get_current_sav_path(),
self._get_backup_save_path()
]:
with open(path, "w") as f:
json.dump(state, f, indent=4)
self._last_saved_state = state.copy() # do we need to copy?
Expand All @@ -106,7 +121,7 @@ def save(self):
if state != self._last_saved_state:
self._save(state)

def load(self, path = None):
def load(self, path=None):
if not self.enabled:
print("Not loading from file as autosave adapter disabled")
return
Expand All @@ -123,9 +138,18 @@ def load(self, path = None):
continue
pv.set(value)

def stop(self):
self._stop_event.set()

def loop(self):
if not self._pvs:
return # end thread if no PVs to save
while True:
time.sleep(self.save_period)
self.save()
try:
self._stop_event.wait(timeout=self.save_period)
except TimeoutError:
# No stop requested, we should save and continue
self.save()
else:
# Stop requested
return
15 changes: 13 additions & 2 deletions softioc/cothread_dispatcher.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import atexit
import threading

from . import autosave


class CothreadDispatcher:
def __init__(self, dispatcher = None):
"""A dispatcher for `cothread` based IOCs, suitable to be passed to
Expand All @@ -14,17 +18,20 @@ def __init__(self, dispatcher = None):
# Import here to ensure we don't instantiate any of cothread's
# global state unless we have to
import cothread

# Create our own cothread callback queue so that our callbacks
# processing doesn't interfere with other callback processing.
self.__dispatcher = cothread.cothread._Callback()
else:
self.__dispatcher = dispatcher

self.wait_for_quit = cothread.WaitForQuit
self.__atexit = atexit.register(self.__shutdown)

# set up autosave thread
autosaver = autosave.Autosave()
self.__autosave = autosave.Autosave()
self.__autosave_worker = threading.Thread(
target=autosaver.loop,
target=self.__autosave.loop,
)
self.__autosave_worker.daemon = True
self.__autosave_worker.start()
Expand All @@ -40,3 +47,7 @@ def wrapper():
if completion:
completion(*completion_args)
self.__dispatcher(wrapper)

def __shutdown(self):
self.__autosave.stop()
self.__autosave_worker.join()

0 comments on commit c6b90b4

Please sign in to comment.