[runtime/tokio] support for colocated tasks#3548
Conversation
Deploying monorepo with
|
| Latest commit: |
467a9e8
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://f24f9091.monorepo-eu0.pages.dev |
| Branch Preview URL: | https://andre-runtime-spawn-colocate.monorepo-eu0.pages.dev |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
commonware-mcp | 467a9e8 | Apr 23 2026, 01:20 PM |
| } | ||
| }); | ||
| } else if matches!(past, Execution::Colocated) && parent_on_dedicated { | ||
| tokio::task::spawn_local(f); |
There was a problem hiding this comment.
tokio::task::spawn_local targets the innermost active LocalSet on the current thread, which is always the one created by the dedicated ancestor. This avoids carrying an explicit LocalSet reference on Context, which is not possible because LocalSet is !Send and Context must be Send + Sync.
| SharedBlocking, | ||
| /// Task runs on a dedicated thread. | ||
| Dedicated, | ||
| /// Task is co-located on the same thread as its dedicated ancestor. Falls |
There was a problem hiding this comment.
I think we should instead error/panic on this rather than falling back? At least, we should do that on deterministic.
There was a problem hiding this comment.
The issue is that the context has no way of knowing if its parent (or any ancestor) is running as dedicated, so there's no obvious way to avoid panicking at the call site. colocated() is more of a local hint, but the caller doesn't control where it ends up in the spawn hierarchy, and the same code might be used in contexts with or without a dedicated ancestor. Panicking would make colocated() unusable in generic code.
Maybe this should be made more explicit in the documentation?
|
Because |
|
I don't get why this is needed? What's the problem that Edit: okay I read the test now and see the issue, will see if there's a way around it without the thread local. |
|
@patrick-ogrady I think this issue reflects a misuse (or more precisely "unintended") of context, the same thing happens with the supervision tree if you reuse a context from another "scope" and it can also lead to "surprising" behaviors (again, it might not be surprising at all, it might be intentional). I think for consistency we shouldn't be using thread-locals here, colocation (like parenting) refers to the context that is being spawned on not the "scope" you're in. |
|
I ran some benchmarks: https://gist.github.com/andresilva/c3bc6c31141347dd767ef350ba9e16b9 |
586c600 to
65e2216
Compare
Codecov Report❌ Patch coverage is
@@ Coverage Diff @@
## main #3548 +/- ##
==========================================
- Coverage 95.87% 95.82% -0.05%
==========================================
Files 442 443 +1
Lines 172121 173954 +1833
Branches 4010 4084 +74
==========================================
+ Hits 165017 166697 +1680
- Misses 5840 5955 +115
- Partials 1264 1302 +38
... and 23 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
This PR extends the runtime spawner with a new
colocated()builder method that allows tasks to be co-located on the same OS thread as their dedicated ancestor. When a task is spawned as dedicated, it now runs inside atokio::task::LocalSet. Subsequentcolocated()spawns from that context usetokio::task::spawn_localto land on the same thread, eliminating cross-thread synchronization overhead and enabling cache-friendly data sharing between related tasks. If there is no dedicated ancestor,colocated()falls back to the shared executor.The implementation uses a boolean flag (
on_dedicated_thread) onContextrather than carrying an explicitLocalSetreference. This is necessary becauseLocalSetis!SendandContextmust beSend + Sync.tokio::task::spawn_localalready targets the innermost activeLocalSeton the current thread, so the flag just gates whether calling it is valid. Colocation always targets the closest dedicated ancestor, if a colocated task spawns a new dedicated child, that child starts a fresh colocation chain on its own thread. The chain also breaks when a task is spawned onto the shared pool, i.e. a colocated grandchild from a shared parent will not return to the original dedicated thread.Related #3537.