Stuck in deadlock with up-to-date "run once" task
- Task version: v3.12.0 (h1:viFy8kdDZ2iTcpTuxzzJCeKtTGt9U+5iXMVIpLjvIro=)
- Operating System: Linux 5.10.16.3-microsoft-standard-WSL2
Example Taskfile showing the issue
I have a quite large project where I manage to end up in a deadlock every time I try to run task. It seems to be related to a run: once task that other tasks depend on. This happens when I run with -C X where X < 4 in my case. I suspect that If I add more "apps" I will eventually end up in this situation when running with all my cores as well.
I've managed to condense my layout into this:
version: "3"
tasks:
do_something:
run: once
status:
- "true"
prepare_env:
cmds:
- task: do_something
app1:
deps: [prepare_env]
app2:
deps: [prepare_env]
default:
deps: [app1, app2]
Running this example ends up in deadlock about one in ten times I run it (my larger repo ends up in deadlock every time, as I mentioned). I'm pretty sure it has to do with timing.
Example of a successful run:
$ ../work/task -v -C 1
task: "default" started
task: "app2" started
task: "app1" started
task: "prepare_env" started
task: "prepare_env" started
task: "do_something" started
task: status command true exited zero
task: Task "do_something" is up to date
task: skipping execution of task: do_something
task: "prepare_env" finished
task: "prepare_env" finished
task: "app2" finished
task: "app1" finished
task: "default" finished
And with deadlock:
$ ../work/task -v -C 1
task: "default" started
task: "app1" started
task: "app2" started
task: "prepare_env" started
task: "prepare_env" started
task: "do_something" started
task: skipping execution of task: do_something
When you say deadlock, you mean task is sitting there trying to do something, but nothing is happening?
Something like that, yes. In the second run above, it just sits there forever and nothing happens.
+1, seeing this as well on Linux in version v3.13.0.
Workaround for me seems to be to avoid using run: once to be able to use the -C option.
I believe I've found the deadlock.
When the second execution of a Task needs to be skipped because it has already run (or is currently running), the code holds onto an execution slot while it waits for the first execution of the task to complete.
If the first execution of the task has already completed, this wait immediately completes, and the execution slot is also immediately freed.
If the first execution of the task is running its dependencies, it has temporarily released its execution slot to allow the dependencies to be run. Once those are complete, it attempts to reacquire an execution slot before running the commands for the task.
If there is only one execution slot, this gets blocked because the only slot is held by the second execution of the task.