Skip to content

testing/synctest: GOEXPERIMENT=synctest blocks indefinitely with context.WithTimeout in tight loop #73130

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

Open
chanchal1987 opened this issue Apr 1, 2025 · 5 comments
Labels
BugReport Issues describing a possible bug in the Go implementation. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.

Comments

@chanchal1987
Copy link

Go version

gotip

Output of go env in your module/workspace:

N/A

What did you do?

When using GOEXPERIMENT=synctest, a goroutine executing a tight loop (https://go.dev/play/p/bjn7YXLgDhh?v=gotip) will block the entire simulation indefinitely, preventing context timeouts from firing.

// GOEXPERIMENT=synctest
package main

import (
	"context"
	"testing"
	"testing/synctest"
	"time"
)

func WaitForContext(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			return
		default:
			// Do nothing
		}
	}
}

func TestWaitInLoop(t *testing.T) {
	println("Starting Test")
	synctest.Run(func() {
		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()

		WaitForContext(ctx)
	})
}

In the provided test case TestWaitInLoop, the WaitForContext function enters a tight for/select loop checking ctx.Done(). Because the default case is always available and there are no yielding operations inside the loop, this goroutine never yields control back to the synctest scheduler.

What did you see happen?

=== RUN TestWaitInLoop
Starting Test
timeout running program

Program exited.

What did you expect to see?

While a tight loop is generally undesirable, one might expect synctest to potentially have mechanisms to detect or handle such non-yielding goroutines.

@gabyhelp gabyhelp added the BugReport Issues describing a possible bug in the Go implementation. label Apr 1, 2025
@seankhliao seankhliao added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Apr 2, 2025
@seankhliao
Copy link
Member

cc @neild

@neild
Copy link
Contributor

neild commented Apr 2, 2025

Your problem is that the context is never canceled: You're creating a context with a one-second timeout (using the fake synctext clock) but not doing anything that advances time, so the context never expires.

If you print time.Now() on each iteration in WaitForContext, you'll see that the time remains the same.

This is working as expected. Within a synctest bubble, time only advances when every goroutine blocks. The goroutine in this test never blocks.

@chanchal1987
Copy link
Author

I believe that even though the synctest bubble is working as intended, this is still a valid testing scenario. There might be situations in third-party libraries where functions have tight loops, which would prevent the time from advancing when the testing/synctest package is used for user testing.

@neild
Copy link
Contributor

neild commented Apr 2, 2025

What's the behavior that you would expect here?

Synctest bubbles use a fake clock, and the clock advances when all goroutines within the bubble are durably blocked. If a goroutine never blocks, the clock never advances. Essentially, a synctest bubble acts as if you are using an infinitely fast computer where any amount of computation will complete instantly.

This does mean you can't use synctest to test a function that assumes time advances without it blocking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
BugReport Issues describing a possible bug in the Go implementation. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

4 participants