-
Notifications
You must be signed in to change notification settings - Fork 17.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
x/sync/semaphore: acquire should fail if context is canceled or timed out #63615
Comments
CC @jba |
In general a Go API that accepts a That said, I agree that it would be reasonable for |
@bcmills I found it with @AlexanderYastrebov when I tried to use it with context.WithTimeout. For the records, you can use https://go.dev/play/p/wgsWdy8mGGo to handle the timeout case. |
The documentation on Acquire states that this is allowed behavior. Does changing this need to go through proposal review? |
Change https://go.dev/cl/536275 mentions this issue: |
Both filters did not check for canceled context from request before semaphore.Acquire, see golang/go#63615 Signed-off-by: Sandor Szücs <[email protected]>
Both filters did not check for canceled context from request before semaphore.Acquire, see golang/go#63615 Signed-off-by: Sandor Szücs <[email protected]>
To be clear, is the desired behavior (in terms of the language of the Go memory model) "if ctx closing happens before the call to Acquire returns, Acquire never acquires the semaphore," or is it sufficient to say, "if ctx closing happens before the call to Acquire begins, Acquire never acquires the semaphore?" The latter is easier and probably cheaper to implement. |
@zephyrtronium, I think the only reasonable guarantee we can make is if ctx is already canceled at the start of the call. (If it is canceled partway through, then there isn't a happens-before relationship between acquiring the semaphore and canceling the context — canceling a |
Correct me if I'm wrong, but I think it is possible to construct a happens-before relation with (Acquire starts) <= (context canceled) <= (Acquire completes):
sem := semaphore.NewWeighted(2)
ctx, cancel := context.WithCancel(context.Background())
sem.TryAcquire(1)
go sem.Acquire(ctx, 2)
for sem.TryAcquire(1) {
sem.Release(1)
}
cancel()
sem.Release(1) With the current implementation or if we choose the closed-before-begins option (implemented with a minor adjustment to CL 536275's current patch set), the semaphore may have either 0 or 2 tokens held at the end of this sequence. I believe that having Acquire immediately release its acquisition if it finds both context canceled and a ready signal is sufficient to implement the closed-before-returns option and leave the semaphore always at 0 tokens held. To me, the question is more about how much work will happen after the semaphore is acquired with a canceled context. Or more precisely, does Acquire really need to be the first point where the context being done causes the caller to quit its work? Maybe the situation that caused this issue for @szuecs in the first place would help answer that question. |
* feature: filter fifoWithBody that works similar to fifo(), but release deferred until body streaming to client was finished * fix: fifo() and fifoWithBody() with canceled requests Both filters did not check for canceled context from request before semaphore.Acquire, see golang/go#63615 Signed-off-by: Sandor Szücs <[email protected]>
@zephyrtronium, it is possible to construct a happens-before relationship, but I think you'll find that actually having such a relationship is extremely rare in practice. (Typically the goroutine that cancels the Context doesn't have any semaphone tokens at all, let alone enough to block a specific goroutine from acquiring the semaphore.) |
Sorry, I think we're talking past each other. I agree it is unusual to explicitly construct a happens-before relation between the context cancelling and Acquire returning in general. When there isn't one, my premise of "if ctx closing happens before" doesn't apply. The example I gave is contrived, but more practically, I can imagine a Weighted-backed task pool which allows a worker to cancel remaining tasks, along the lines of an errgroup. That could reasonably cause cancellation to happen before some blocked Acquire returns even if that isn't particularly the intent. My question is whether it still resolves this issue to leave the result unspecified when the happens-before-returns relation does occur. Is it sufficient to have some point where Acquire will start to always fail, or should we make that point respect the synchronizes-before relation that Acquire itself enforces? |
@zephyrtronium I am not sure, if you ave an open question that I should answer. More specific where we used the code: |
* feature: filter fifoWithBody that works similar to fifo(), but release deferred until body streaming to client was finished * fix: fifo() and fifoWithBody() with canceled requests Both filters did not check for canceled context from request before semaphore.Acquire, see golang/go#63615 Signed-off-by: Sandor Szücs <[email protected]>
* feature: filter fifoWithBody that works similar to fifo(), but release deferred until body streaming to client was finished * fix: fifo() and fifoWithBody() with canceled requests Both filters did not check for canceled context from request before semaphore.Acquire, see golang/go#63615 Signed-off-by: Sandor Szücs <[email protected]>
* feature: filter fifoWithBody that works similar to fifo(), but release deferred until body streaming to client was finished * fix: fifo() and fifoWithBody() with canceled requests Both filters did not check for canceled context from request before semaphore.Acquire, see golang/go#63615 Signed-off-by: Sandor Szücs <[email protected]>
* feature: filter fifoWithBody that works similar to fifo(), but release deferred until body streaming to client was finished * fix: fifo() and fifoWithBody() with canceled requests Both filters did not check for canceled context from request before semaphore.Acquire, see golang/go#63615 Signed-off-by: Sandor Szücs <[email protected]>
To address this question explicitly: I don't think this needs a proposal review because the new behavior still conforms with the existing documentation. (We're updating the documentation to no longer note a behavior that is no longer possible, but it would also be technically correct to leave that note in place and just let Hyrum's Law lock in the stronger invariant over time. 😅) |
When acquiring from a semaphore could proceed without contention, the previous behavior was to always do so, even when the provided context was done. This was the documented behavior, but it could lead to confusion. It isn't much more expensive to check the context error, so cancel acquisition if it's done. Fixes golang/go#63615. goos: linux goarch: amd64 pkg: golang.org/x/sync/semaphore cpu: 12th Gen Intel(R) Core(TM) i5-1235U │ old.bench │ new.bench │ │ sec/op │ sec/op vs base │ AcquireSeq/Weighted-acquire-1-1-1-12 26.45n ± 2% 27.25n ± 3% +3.04% (p=0.001 n=20) AcquireSeq/Weighted-acquire-2-1-1-12 26.96n ± 1% 27.12n ± 1% ~ (p=0.104 n=20) AcquireSeq/Weighted-acquire-16-1-1-12 26.07n ± 3% 27.48n ± 1% +5.45% (p=0.000 n=20) AcquireSeq/Weighted-acquire-128-1-1-12 26.19n ± 2% 27.24n ± 1% +4.01% (p=0.000 n=20) AcquireSeq/Weighted-acquire-2-2-1-12 25.61n ± 1% 25.99n ± 2% ~ (p=0.066 n=20) AcquireSeq/Weighted-acquire-16-2-8-12 209.6n ± 2% 211.0n ± 3% ~ (p=0.280 n=20) AcquireSeq/Weighted-acquire-128-2-64-12 1.669µ ± 1% 1.721µ ± 2% +3.09% (p=0.000 n=20) AcquireSeq/Weighted-acquire-2-1-2-12 51.08n ± 1% 53.03n ± 2% +3.82% (p=0.000 n=20) AcquireSeq/Weighted-acquire-16-8-2-12 52.48n ± 2% 53.66n ± 2% +2.26% (p=0.028 n=20) AcquireSeq/Weighted-acquire-128-64-2-12 52.27n ± 1% 53.71n ± 2% +2.75% (p=0.000 n=20) geomean 60.06n 61.69n +2.71% Change-Id: I0ae1a0bb6c027461ac1a9ee71c51efd8427ab308 Reviewed-on: https://go-review.googlesource.com/c/sync/+/536275 Auto-Submit: Bryan Mills <[email protected]> Reviewed-by: Bryan Mills <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
What version of Go are you using (
go version
)?Issue is also reproducibe in https://go.dev/play/p/CNcJihWeMMU
Does this issue reproduce with the latest release?
yes
What operating system and processor architecture are you using (
go env
)?go env
OutputWhat did you do?
Reproducible error:
https://go.dev/play/p/CNcJihWeMMU
What did you expect to see?
no error printed
What did you see instead?
The text was updated successfully, but these errors were encountered: