Skip to content

Add the ability for threads to manually set their "current task" #659

@lukewagner

Description

@lukewagner

With Preview 3 concurrency, at all execution points, there is a well-defined async call stack that terminates at a call from the host into a component export. This async call stack is useful for runtime debugging/profiling/tracing scenarios but it also allows a host-implemented import to have a well-defined answer to "which host export call am I executing on behalf of" which is otherwise hard to answer when you're executing multiple concurrent tasks. For example, this information is useful for logging and proxy loop detection scenarios in WASI HTTP. In the future, the same information could be exposed to wasm, for virtualizaiton purposes, when a parent component donut wraps a child component.

One nice thing about the async call stack is that it is maintained automatically by default (even with cooperative threads) and so in many scenarios the import-to-export attribution will just work. However, if the guest code / guest runtime has its own in-wasm concurrency runtime/scheduler, it's possible that all component export calls end up funneling down to a single scheduler loop in which case the default async call stack will make it look like everything is coming from one root host call. At this point, there's little we can do automatically without some degree of help from the guest code.

One option is that we could provide a new CM built-in that allows the guest's concurrency runtime / scheduler to explicitly override the default-propagated async call stack with something better, based on the guest's own internal bookkeeping state. The simplest way I can think of to do this would be to add a built-in (say thread.set-task):

(func $thread.set-task (param $other-thread-index i32))

This would change the current thread's associated async call stack to be the same as some other thread. (In the CABI Python code, this would mostly just be current_thread().task = current_instance().threads[other-thread-index].task.) As background: each call to a component export mints a new i32 thread index (retrieved by that thread via thread.index). So then the guest concurrency runtime could record the thread.indexes of all new export calls and, using whatever guest-internal bookkeeping, call thread.set-task right before executing a work-item from the shared scheduler loop so that the scheduler thread executes "on behalf of" that work-item's associated export call.

With the addition of this built-in, we could also non-normatively document an expectation (like we did with the Component Instance lifetime section) that guest toolchains should ensure (via automatic propagation or manual thread.set-task) that import calls are "properly" attributed to export calls ("properly" in quotes b/c what that means is language-dependent). It's important to note that there's no way to formally define or enforce this expectation, so it's a cooperative, best-effort sort of thing and thus should not be used for security-sensitive discrimination between concurrent async export calls (which, if a component instance is being reused, would be a bad idea in any case). Logging and accident-mitigation are good use cases, though.

This all seems additive to what's already defined/implemented, so I don't think adding this built-in blocks the initial release of Preview 3, but it does seem like a good follow-up worth considering.

(I'm going to be on vacation for the next 2 weeks after tomorrow, so apologies in advance for slow replies.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions