I really feel that Golang V.2 should invent a better, native, way of controlling threads of execution.
I really feel that Golang V.2 should invent a better, native, way of controlling threads of execution.
You're talking about context as a way to distribute data? I do that as well. For instance to provide auth/session data to requests, but that's usually just limited to one path in my software that does this. (I agree it is clumsy, but not because it is the "wrong" thing to do, but rather the API feels a bit dodgy).
If you are talking about something like thread-local storage, that's really a very different thing from both the control aspect of context and the request data aspect.
What extra functionality do you want for goroutine control and why do you think Go needs it?
And when I'm accepting Context I'm annoyed at having to write handlers for it all through the stack having no idea how/if people will use it.
Everybody has to be decent enough to do their part.
> And when I'm accepting Context I'm annoyed at having to write handlers for it all through the stack having no idea how/if people will use it.
Thank you for doing yours :)
On the occasions where I've needed that I've used a WaitGroup and done wg.Add(1) at the point where I start goroutines and then have a defer wg.Done() as the first thing in the goroutine. I don't think the functionality belongs in Context. And if you put it there, you'd just end up complicating things.
> And when I'm accepting Context I'm annoyed at having to write handlers for it all through the stack having no idea how/if people will use it.
How would you propose you do it instead?
I'm not sure this is the same thing. The point of Context is to propagate cancelations or timeouts across multiple layers of your app and libraries, it's not supposed to be useful for directly started goroutines.
What changes would you make to Context?
I still think an explicit bag-of-stuff is better than an implicit one though.
If a function does not take a context, you know it probably cannot be interrupted, just like when a function does not return an error, you know it should not fail. In my work projects, this is also a cue that the function does not do any logging since we always carry a zerolog.Logger in our contexts enriched with trace information about the request and handler.
This also makes life easier for me as a reviewer -- I can see the context passing, I can spot when there is a bad pattern, like retaining a context, or failing to handle an error. It does not require me to maintain a detailed mental map of which functions employ dynamic variables or can throw exceptions.
The rules around goroutines and context seem to point in the direction of structured concurrency. For example, if any goroutines started in a function get cleaned up before return then that's following the rules of structured concurrency.
Thread-local storage is bad because it's implicit and causes bugs when used with concurrency; if you farm out some work to another goroutine, it will break.
Can you elaborate on why being implicit is bad and how it causes bugs?
I understand that shared data (via pointers) may cause race conditions and other unexpected behavior, so let's say we require that the thread-local storage can only store values (with value semantics).
If you could point out any issues with that, I'd greatly appreciate it.
So one possible bug is that you have a function that implicitly depends on thread-local storage, and then you move some work to another thread and call the function there, and it doesn't work because its dependencies aren't there. You need to manually set up the thread-local storage of each new thread.
Another bug is that if a thread does work on multiple requests (say, a task queue), some thread-local storage could leak data from a different request.
In larger systems, it might even be worse: one request can be farmed out to multiple servers and then you need to pass the context along over the network when doing rpc. This only works for serializable data, but things like deadlines can be propagated, and a request id that ties it all together is useful for logging.
"Which request am I working on" is something that's transient and often doesn't map directly to OS-level objects. (Although it does map one-to-one in simple cases.)
Sounds like that could be solved by a language surfacing the context at thread boundaries like the `go` statement, possibly channels.
Thanks for the answer!
The tooling around them is nearly non-existent in stdlib, and if you want to understand errors from stdlib you will have to guess at string content.
And like, why is there not a stack trace by default?