-
Notifications
You must be signed in to change notification settings - Fork 94
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
post_configure: ensure asyncio tasks created by plugins are awaited #5868
Conversation
cylc/flow/scripts/install.py
Outdated
entry_point.name, | ||
exc | ||
) from None | ||
async with async_block(): |
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 ensures that any async tasks started by the plugins are awaited before we move on.
Rose fileinstallation will create async tasks but cannot await them (because it's called synchronously) so we have to do it here, otherwise cylc vip
would start running the workflow before local file installation has finished.
@@ -97,8 +97,8 @@ def src_run_dirs( | |||
return tmp_src_path, tmp_run_path | |||
|
|||
|
|||
def test_install_scan_no_ping( | |||
src_run_dirs: Callable, |
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.
Note, mypy cannot validate type hints for fixtures so they can be completely wrong as in this case.
@staticmethod | ||
def raiser(*_, **__): | ||
import cylc.flow.flags | ||
if cylc.flow.flags.cylc7_back_compat: |
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 test was intended to check that the back-compat flag was set correctly, however, it achieved this by monkeypatching the back-compat flag (i.e. manually setting it to what it expected it to be).
65a6ec4
to
12f0881
Compare
@@ -54,7 +55,7 @@ run_fail "${TEST_NAME_BASE}-runs" cylc vr "${WORKFLOW_NAME}" | |||
grep_ok "on elephantshrew." "${TEST_NAME_BASE}-runs.stderr" | |||
|
|||
# Clean Up: | |||
sed -i "s@CYLC_WORKFLOW_HOST=elephantshrew@CYLC_WORKFLOW_HOST=$HOSTNAME@" "${CONTACTFILE}" |
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.
Not sure this sed
was working (workflow will eventually time out and the test will pass), switched to restoring the original contact file.
12f0881
to
9a8317f
Compare
e1fd499
to
dff3b63
Compare
|
||
|
||
@asynccontextmanager | ||
async def async_block(): |
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.
Crude but effective way to allow us to call async -> sync -> async.
This makes a list of async tasks before the plugin is called in order to await any new tasks created by the plugin.
This means that plugins can go async like so:
def plugin(opts):
asyncio.create_task(run(opts))
# the plugin will return now, but Cylc will await the "run" for us
async def run(opts):
...
I think it's a reasonable solution.
Sadly, directly converting the plugin interfaces to async
isn't an easy option due to the use of plugins within parsec. Making the plugin async would make loading the global config async which would make global config value retrieval (e.g. the line below) async:
glbl_cfg().get(['install', 'symlink dirs'])
Which would basically require the entire cylc-flow codebase to be async and prohibit config caching at the module level.
async with _async_block(): | ||
plugin_result = meth(*args, **kwargs) |
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 ensures that any async tasks started by the plugins are awaited before we move on.
Rose fileinstallation will create async tasks but cannot await them (because it's called synchronously) so we have to do it here, otherwise cylc vip would start running the workflow before local file installation has finished.
9c45021
to
b4c7e44
Compare
|
monkeypatch.setattr( | ||
'cylc.flow.plugins._async_block', | ||
async_block, | ||
) | ||
|
||
# this is what it would have done without async block | ||
(one_src.path / 'b').touch() # give it something else to install | ||
my_install_plugin.clear() | ||
assert my_install_plugin == [] | ||
await reinstall_cli(opts=ReInstallOptions(), workflow_id=one_run.id) | ||
# the absence of "end" means that the task was not awaited | ||
assert my_install_plugin == ['start', 'return'] |
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.
Not sure I understand why this bit is being tested?
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.
tldr; the second part of this test confirms that the first part is a valid test.
It's easy to write a test for this sort of thing that passes, even if the related functionality doesn't actually work.
To make the that this functionality does work, I wrote two tests:
- One checks that there would be a problem if we didn't use the fix.
- The second checks that the fix solves the problem.
By writing them together using the same approach you reduce the likelyhood of the test being broken and producing false negatives in the future.
953f92d
to
99985d9
Compare
Unfortunately |
The cause isn't obvious, even uninstalling [edit] Daemonization is fine via [edit] Can't replicate this any more 😕 |
* Await any background tasks started by a plugin before continuing. * Addresses cylc/cylc-rose#274 * Centralise the plugin loading/running/reporting logic. * Fix the installation test for back-compat-mode.
99985d9
to
e51537e
Compare
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.
- Read changes
- Run tests locally
- Tried to break various components of this
* The "cylc vip" command combines three individual coroutines, usually run in their own event loops. * For unknown reasons, the cylc.flow.network.scan.scan coroutine pipe, called as part of the the "install" coroutine, interacts with the daemonization routine called as part of the "play" coroutine. * Keeping these event loops separate avoids the conflict.
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.
I'll test this out tomorrow
# NOTE: We call each of the stages in its own event loop because there is a | ||
# strange interaction whereby the cylc.flow.network.scan coroutine pipe can | ||
# cause sys.exit to hang on the original process post-daemonization |
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.
👍
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.
Tested
This is one of a triplet of PRs which closes cylc/cylc-rose#274
Reviewers, please check:
Testing:
This diff will make file installation take longer:
This diff will disable the new guard:
Try installing a workflow with
cylc install --debug
, it will now log the time taken by each plugin.With the
async_block
it should take >1s (due to the sleep) without theasync_block
(i.e. with the above diff applied) it should take <1s because the sleep will not be awaited.CI:
Note that the cylc-rose and metomi-rose tests will fail until both the cylc-flow and metomi-rose branches are merged.
I.E. please run the tests manually due review.
Check List
CONTRIBUTING.md
and added my name as a Code Contributor.setup.cfg
(andconda-environment.yml
if present).CHANGES.md
no - unreleased issue?.?.x
branch.