Thanks for your examples! I'll cover them in reverse order, to build up
the syntax.
On 2/29/24 6:18 AM, 'TheDiveO' via golang-nuts wrote:
The second example is unit test code, so TEST:
https://github.com/thediveo/whalewatcher/blob/cca7f5676b3f63b0e2d6311a60ca3da2fd07ead7/watcher/moby/moby_test.go#L58
This one uses the `context.Done()` / `context.Err()` API:
ctx, cancel := context.WithCancel(ctx)
defer cancel()
In general, if you're re-assigning the new context to the same variable,
then it can be directly replaced with setting the context variable. I
reason that scope cancellation should be a sync property, so you would
write (in the cancelable function):
for {
select {
case <-sync.Canceled:
break
case <-...
}
}
OK? So, assuming that, then it follows that setting context var
sync.Canceled (and watching it) is the way to make the current scope
cancelable (using the previous described syntax):
context var sync.Canceled
var cancelEvents func()
sync.Canceled, cancelEvents = sync.CancelChan()
Obviously, this seems slightly awkward, but it's syntax and this can be
fixed. In fact as I argue on the latest version of the proposal on my
github <https://github.com/samv/go-context-proposal>, that given that
there needs to be a path for backwards compatibility. It should be
possible for new code to rely on `sync.Canceled` (or `sync.Cancelled`?)
without requiring all callers to have moved away from context/. /Given
there needs to be way to allow a regular call to `context.WithCancel()`
to update the context variable in the caller, /and/ given that the
"context register" has /callee-save /semantics, it should follow that
functions that /don't/ restore the pointer could exist. And given it's
a property of the function, not the caller, that's where the marker
should live.
I landed on `context func ScopeCanceler()`. This is in line with
`context var variable SomeType`, and it's also a big warning up front
that the function does something special, worthy of your understanding
before you just go and call it. It's also a hint to folk that it's a
special type of function, not to be used everywhere like `ctx
context.Context` was. Of course, perhaps the marker ends up on the
right with the return values, but like I say—it's syntax, and this can
be decided later (especially if I got it wrong and this syntax makes
parsing slower).
I expect we would end up with this:
cancelFunc := sync.ScopeCanceler()
defer cancelFunc()
While your test explicitly calls cancel() at the end and validates that
everything shuts down (an excellent practice, often overlooked), many
cases would be able to get away without the temporary:
defer sync.ScopeCanceler()
If you were to jump-to-definition on `sync.ScopeCanceler()`, it might
look like this:
type CancelFunc func()
type CancelChan <-chan struct{}
context func ScopeCanceler() CancelFunc {
var cancelChan = make(chan struct{})
context var Canceled CancelChan = cancelChan
var guard Once
return func() {
guard.Do(func() {
close(cancelChan)
})
}
}
Remember, this is syntax, not semantics, and we need to nail down the
semantics first, but this is the working syntax I'm using for the proposal.
My first example is code that is run in PROD:
https://github.com/thediveo/whalewatcher/blob/cca7f5676b3f63b0e2d6311a60ca3da2fd07ead7/watcher/watcher.go#L240
So, building on the earlier example, you're asking about this block:
eventsctx, cancelevents := context.WithCancel(ctx)
evs, errs := ww.engine.LifecycleEvents(eventsctx)
Here the difference is that you're assigning the context to a different
variable, meaning you can choose which of the variables you use in a
given line.
That said, your example happens to do something also quite common: it
creates a new context, passes it to a specific function, and then does
not use it again. All this means is that you need to create a new
scope, which could be a method, closure, or an explicit scope block, and
assign the variables you want to use later to variables that are in the
outer scope, and call the function you want to inherit the new context
from inside the scope.
For example, using an explicit scope:
var cancelevents func()
var evs <-chan ContainerEvent
var errs <-chan error
{
cancelevents = sync.ScopeCanceler()
evs, errs = ww.engine.LifecycleEvents()
}
That might look like a lot of annoying variable declarations, and that
you'd rather take advantage of type inference. But if you were to put
the code into a method, then you can then use a type-inferring
assignment statement, and that would be a more likely and natural
refactoring approach for that code.
Hopefully these two examples might shed some more light for you on a
certain class of usages.
Yes, of course! Thanks for your input.
Of course there are contrived cases where multiple contexts are passed
to a function, or a function really does a lot of juggling of different
contexts for use by different functions. In the cases I've looked at, I
have found that there exists a refactoring that aligns the use of the
context with a function, and that the resulting code is cleaner.
Cheers,
Sam
--
You received this message because you are subscribed to the Google Groups
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit
https://groups.google.com/d/msgid/golang-nuts/002c3556-def6-4807-b613-cf453ae61faa%40vilain.net.