Small status update: I received some excellent feedback off-thread and will be preparing a Pitch #2 soon.
It will extend the semantics to also cover inheritance of values with detached tasks, although with very carefully specialized semantics to avoid infinitely growing task local bound value stacks. It'll help with end-to-end tracing even in presence of detached tasks.
It also means we pretty definitely do need the Key type, as we'll need to special mark some keys for storage in "always inherit" limited-space storage.
Ok, I'd love to know more about this. It just seems odd and would be best to keep these things orthogonal. The word "unsafe" has a lot of precedent in Swift, and keeping that consistent would be good.
Thx! Also, no worries about self-deprecating humor. I didn't mean anything negative, just trying to help make the writing clear and productive for when it gets out to a wider audience in full review.
I don't understand though: why is this desirable? What does "structured" mean in this context?
I'm not arguing for more compiler magic :-) , I'm just trying to understand the model and use-case. People will think about these as "thread local" values or "task local" values, and such concepts have wide precedent in ptherads, GCD, etc. These are based on getter/setter styles of APIs. Why deviate? I think I'm just missing an important aspect of your design goals here.
Ok, I can see why you want scoped mutation so you can restore if this is a goal, but "why is this a goal of the design"? It isn't clear to me at all that this is a better design than a simple getter/setter design. Such a design makes it much more complicated and just seems odd - what is the precedent and rationale here?
Yeah ok, let’s revisit this in the Structured Concucrrency Pitch update (meh, pending, should have been out but slipped) as that’s where we propose that API. John can chime in a bit more then about his preference on the naming there... It is “concurrency unsafe” though I understand that normally in Swift “unsafe” most often meant “memory unsafe”, but then again... Swift never before talked about anything wrt. concurrency, so that’s a first.
The primary use-case of these APIs is tracing, there’s two primary use cases (which are actually the same):
“Instruments style” use cases which need to “for this call, attach some identifier, and carry it through all calls, async/sync and even detached tasks made from this function — such that profilers can “this call, caused all this (a)synchronous work to happen.”
subsequent calls (one line below that call) should not have the same identifier, so we need the “restore” / “reset” operation
distributed tracing — pick any specification you like, but they all need “reset” to implement correctly and efficiently. We (SSWG) have https://opentelemetry.io/ compatible tracer implementations already, but through painfully passing around the metadata around explicitly.
the concept is exactly the same here as the “local” tracing/profiling, but we can extend the traces through multiple machines. Imagine getting a trace from device through all services it is hitting in backend services — a tremendously useful performance analysis tool.
Both those are something we’re actively interested in solving awesomely. Though it will some more time to get the tracers, profilers to make use of this.
—
There’s a lot of prior art on this, I link to three in the proposal itself — how Go’s context’s form such “tree”, Kotlin’s scopes, and Loom’s Scope Variables.
The best one to check out is Loom’s Scope Variables mostly because they have the best “in single place” complete rationale writeup about the pains they’re addressing: State of Loom: Part 2 The ability to solve issues with thread locals is served really well by embracing structured concurrency, and as Swift wants to do this anyway — structured task locals are solving another easy to get wrong thing while we’re at “fixing” concurrency to make it simpler to work with.
I’ll add some more rationale on this to the Motivation section too.
Correct, it’s waiting for Structured Concurrency to conclude. It will be up for review once that is cleared up. We’re trying to have a focused review of Structured Concurrency and Actors right now, and not adding another topic to focus on concurrently.
Yep, I've been using it for some time already :) I'm just afraid the API might change (or the whole feature gets rejected, whereas it is actually critical — currently I have to pass event loop/context struct to every call, while I could just take it from the Task.local).
Yeah it is fairly critical for tracing systems and other instrumentation. I had some mixed feelings about passing the event loop with it, but perhaps it’s fine... I would not recommend designing a library around that idea I think though (specifically for the event loop passing around I mean).
I can’t say much before the review but I do think this is important enough that it’ll make it in some shape or form. The usual caveats remain — this is under an experimental flag and may change at any moment after all
Please make sure to chime in the review to voice the feedback for it once it is going to be under review — we’ll ping here then of course as well too. Thanks!
Given that these task local values are only available inside the task, can this lead to a place where the NonAtomic ARC implementation is used for ref counting ? It would be nice to mark a whole society of instances as task local and this seems like a conduit to that semantic.
Once it’s non atomic, you could also replace malloc with something for this case and wipe out most of the cost of ARC + malloc for task internal allocations. (Which is most allocations in any sensible design ;-))