-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ENH: making doing work better #34
Changes from all commits
abebfc5
51db1b3
69a5329
c18ee2a
31de5a9
1b5910e
61c55b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
""" | ||
A worker specialized to execute ActionNode work functions | ||
""" | ||
from typing import Callable, Any, Optional | ||
|
||
from epics.multiproc import CAProcess | ||
|
||
from beams.sequencer.helpers.Worker import Worker | ||
from beams.behavior_tree.VolatileStatus import VolatileStatus | ||
|
||
|
||
class ActionWorker(Worker): | ||
def __init__(self, | ||
proc_name: str, | ||
volatile_status: VolatileStatus, | ||
work_func: Callable[[Any], None], | ||
comp_cond: Callable[[Any], bool], | ||
stop_func: Optional[Callable[[None], None]] = None): | ||
super().__init__(proc_name=proc_name, | ||
stop_func=stop_func, | ||
work_func=work_func, | ||
proc_type=CAProcess, | ||
add_args=(comp_cond, volatile_status)) | ||
|
||
# Note: there may be a world where we define a common stop_func here in which case | ||
# the class may have maintain a reference to voltaile_status and or comp_cond |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,7 @@ | |
|
||
class CheckAndDo(py_trees.composites.Selector): | ||
def __init__(self, name: str, check: ConditionNode, do: ActionNode) -> None: | ||
super().__init__(name, memory=True) | ||
super().__init__(name, memory=False) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Knowledge check for me: does this make it so we always re-check the "check" side of the "check and do" on every tick even if the "do" is already running? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For my own reference: https://py-trees.readthedocs.io/en/devel/composites.html
I believe what Zach has concluded here is correct. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 100%, I realized that error in this PR. We want the trees to err reactive. I.e. if a condition is no longer true or true we want to catch that even if executing another action. We should call this out more explicitly / write unit tests to show the difference. This is a great call out |
||
self.name = name | ||
self.check = check | ||
self.do = do | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,20 +6,26 @@ | |
""" | ||
import logging | ||
from multiprocessing import Process, Value | ||
from typing import Callable, Any, Optional, List | ||
|
||
|
||
class Worker: | ||
def __init__(self, proc_name, stop_func=None, work_func=None): | ||
self.do_work = Value("i", False) | ||
class Worker(): | ||
def __init__(self, | ||
proc_name: str, | ||
stop_func: Optional[Callable[[None], None]] = None, | ||
work_func: Optional[Callable[[Any], None]] = None, | ||
proc_type: type[Process] = Process, | ||
add_args: List[Any] = [] | ||
): | ||
self.do_work = Value('i', False) | ||
self.proc_name = proc_name | ||
self.proc_type = proc_type | ||
# TODO: we may want to decorate work func so it prints proc id... | ||
if work_func is None: | ||
self.work_proc = Process(target=self.work_func, name=self.proc_name) | ||
if (work_func is None): | ||
self.work_proc = proc_type(target=self.work_func, name=self.proc_name) | ||
else: | ||
self.work_func = work_func | ||
self.work_proc = Process( | ||
target=self.work_func, name=self.proc_name, args=(self,) | ||
) | ||
self.work_func = work_func | ||
self.work_proc = proc_type(target=self.work_func, name=self.proc_name, args=(self, *add_args,)) | ||
self.stop_func = stop_func | ||
|
||
def start_work(self): | ||
|
@@ -28,20 +34,24 @@ def start_work(self): | |
return | ||
self.do_work.value = True | ||
self.work_proc.start() | ||
logging.info(f"Starting work on: {self.work_proc.pid}") | ||
logging.debug(f"Starting work on: {self.work_proc.pid}") | ||
|
||
def stop_work(self): | ||
logging.info(f"Calling stop work on: {self.work_proc.pid}") | ||
if not self.do_work.value: | ||
logging.error("Not working, not stopping work") | ||
return | ||
self.do_work.value = False | ||
if self.stop_func is not None: | ||
logging.info(f"Sending terminate signal to{self.work_proc.pid}") | ||
# Send kill signal to work process. # TODO: the exact loc ation of this is important. Reflect | ||
# with self.do_work.get_lock(): | ||
self.work_proc.terminate() | ||
if (self.stop_func is not None): | ||
self.stop_func() | ||
|
||
print("calling join") | ||
logging.info("calling join") | ||
self.work_proc.join() | ||
print("joined") | ||
logging.info("joined") | ||
|
||
def work_func(self): | ||
""" | ||
|
@@ -55,6 +65,4 @@ def work_func(self): | |
|
||
def set_work_func(self, work_func): | ||
self.work_func = work_func | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a bit of bad / confusing overloading. We define We need to choose our approach here. We're either:
We appear to be doing a mix of both in this codebase. ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, especially with what you taught me about bound vs passed (?) class methods. Removing this functionality may be best, I wanted to leave it flexible till I found what seemed preferable. It seems we have agreed upon not using bound class members (means we have to pass self as an argument for spawned Processes) as it patters well to utilizing the base Worker class to spawn our work threads (and therefore concretize feature adds / debugging in spawned processes) |
||
self.work_proc = Process( | ||
target=self.work_func, name=self.proc_name, args=(self,) | ||
) | ||
self.work_proc = self.proc_type(target=self.work_func, name=self.proc_name, args=(self,)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new file (and many of the old files) has camelcase naming which is against the pep8 recommendations
https://peps.python.org/pep-0008/#package-and-module-names
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great call out, I might make this a seprate trivial PR