-
Notifications
You must be signed in to change notification settings - Fork 118
Fix package loading in Python runner #113
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
Changes from all commits
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 |
|---|---|---|
| @@ -1,28 +1,54 @@ | ||
| import importlib | ||
| from pathlib import Path | ||
| import sys | ||
| import traceback | ||
|
|
||
| import logging | ||
|
|
||
| def get_pkg_path(pkg_name, pkg_type): | ||
| return Path("../../../simulation/package/{}/packages/{}/package.py".format( | ||
| # The engine should be started from the engine's root directory in the repo. | ||
| return Path("./src/simulation/package/{}/packages/{}/package.py".format( | ||
| pkg_type, pkg_name | ||
| )) | ||
|
|
||
|
|
||
| def load_fns(pkg_name, pkg_type): | ||
| # Read code. | ||
| path = get_pkg_path(pkg_name, pkg_type) | ||
| code = "import ........worker.runner.python.hash_util\n"+path.read_text() | ||
|
|
||
| try: | ||
| # TODO: Discuss whether there is some significant drawback to | ||
| # changing `sys.path` here (vs some more complicated way | ||
| # of importing files from parent directories of the | ||
| # package file's directory). | ||
| code = ("import pathlib\n" + | ||
|
Contributor
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 fixed in a later PR so it's okay |
||
| "import sys\n" + | ||
| "sys.path.insert(1, pathlib.Path.cwd()/'worker'/'runner'/'python')\n" + | ||
| "import hash_util\n" + | ||
| path.read_text()) | ||
| except IOError: | ||
| logging.info("`" + str(path) + "` doesn't exist, possibly intentionally") | ||
| return [None, None, None] | ||
|
|
||
| # Run code. | ||
| pkg_globals = {} | ||
| bytecode = compile(code.decode("utf-8"), pkg_name, "exec") | ||
| exec(bytecode, pkg_globals) | ||
| try: | ||
| bytecode = compile(code, pkg_name, "exec") | ||
| exec(bytecode, pkg_globals) | ||
| except Exception: | ||
| # Have to catch generic Exception, because | ||
| # package author's code could throw anything | ||
| e = str(traceback.format_exception(*sys.exc_info())) | ||
| raise RuntimeError( | ||
| "Couldn't import package " + str(path) + ": " + e | ||
| ) | ||
|
|
||
| # Extract functions. | ||
| fn_names = ["start_experiment", "start_sim", "run_task"] | ||
| fns = [(name, pkg_globals.get(name)) for name in fn_names] | ||
| fns = [pkg_globals.get(name) for name in fn_names] | ||
|
|
||
| # Validate functions. | ||
| for (fn_name, fn) in fns: | ||
| for (fn_name, fn) in zip(fn_names, fns): | ||
| if fn is not None and not callable(fn): | ||
| raise Exception( | ||
| "Couldn't import package {}: {} should be callable, not {}".format( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,7 +26,7 @@ def __init__(self, experiment_id, worker_index): | |
| try: | ||
| self.experiment_ctx = self.start_experiment() | ||
| except Exception as e: # Have to catch generic Exception | ||
| self.handle_runner_error(e, sys.exc_info()) | ||
| self.handle_runner_error(sys.exc_info()) | ||
| raise e | ||
|
|
||
| def start_experiment(self): | ||
|
|
@@ -36,7 +36,7 @@ def start_experiment(self): | |
| self.pkgs[pkg_id] = pkg = Package( | ||
| name=config.name, | ||
| pkg_type=config.type, | ||
| owned_fields=config.owned_fields | ||
| owned_fields=[] # TODO: Propagate `config.owned_fields` here. | ||
| ) | ||
|
|
||
| if pkg.start_experiment is not None: | ||
|
|
@@ -137,7 +137,11 @@ def kill(self): | |
| self.batches.free() | ||
| self.messenger = None | ||
|
|
||
| def handle_runner_error(self, exc, tb): | ||
| # `exc_info` is whatever tuple of info `sys.exc_info()` returned about | ||
| # an exception. Calling `sys.exc_info()` inside `handle_runner_error` | ||
| # wouldn't work, because `sys.exc_info` can only return info about an | ||
| # exception that has just occurred. | ||
| def handle_runner_error(self, exc_info): | ||
| # User errors definitely need to be sent back to the Rust process, so | ||
| # they can be sent further to the user and displayed. | ||
|
|
||
|
|
@@ -149,7 +153,7 @@ def handle_runner_error(self, exc, tb): | |
| # Rust process that a runner error occurred so it immediately knows | ||
| # that the runner exited. | ||
|
|
||
| error = "Runner error: " + str(traceback.format_exception(type(exc), exc, tb)) | ||
| error = "Runner error: " + str(traceback.format_exception(*exc_info)) | ||
|
Contributor
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. Can we either pass the destructured args, or can we just type hint? Or at least a comment saying it comes from the function so you can destructure |
||
| logging.error(error) # First make sure the error gets logged; then try to send it. | ||
| self.messenger.send_runner_error(error) | ||
| time.sleep(2) # Give the Rust process time to receive the error message. | ||
|
|
@@ -202,4 +206,4 @@ def run(self): | |
|
|
||
| except Exception as e: | ||
| # Catch generic Exception to make sure it's logged before the runner exits. | ||
| self.handle_runner_error(e, sys.exc_info()) | ||
| self.handle_runner_error(sys.exc_info()) | ||
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.
If this works, it works. But it's something I'd like to revisit at some point, not happy about all of our relative paths and such, especially since things like running Python with
-mcan affect itThere 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.
Yeah, this does rely on running the engine from a specific directory.