Skip to content

Issues with SyncTest and durable blocking #117

@harrisonmetz

Description

@harrisonmetz

I'd like to use the dataloaders in a unit test with synctest in go 1.25. However, there is an issue with this test:

package goexp1_test

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

	"github.com/graph-gophers/dataloader/v7"
)

func TestDataLoaderSync(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		dl := dataloader.NewBatchedLoader(func(ctx context.Context, ints []int) []*dataloader.Result[string] {
			// simple resolve function that "resolves" an int into a string
			results := make([]*dataloader.Result[string], len(ints))
			for i := range ints {
				results[i] = &dataloader.Result[string]{
					Data: strconv.Itoa(ints[i]),
				}
			}
			return results
		})

		t1 := dl.LoadMany(t.Context(), []int{1})
		time.Sleep(10 * time.Millisecond) // data loaders default batch time is 16ms so make it less
		t2 := dl.LoadMany(t.Context(), []int{1})
		t1()
		t2()
	})
}

The simulated time does not advance because it's not durably blocked. Here is the state that the program is in:

goexp1 % go test ./... -timeout 5s
panic: test timed out after 5s
        running tests:
                TestDataLoaderSync (5s)

goroutine 40 [running]:
testing.(*M).startAlarm.func1()
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/testing/testing.go:2682 +0x2b0
created by time.goFunc
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/time/sleep.go:215 +0x38

goroutine 1 [chan receive]:
testing.(*T).Run(0x14000102380, {0x102fa5ee2?, 0x14000118b38?}, 0x1030265c0)
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/testing/testing.go:2005 +0x378
testing.runTests.func1(0x14000102380)
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/testing/testing.go:2477 +0x38
testing.tRunner(0x14000102380, 0x14000118c68)
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/testing/testing.go:1934 +0xc8
testing.runTests(0x1400012c018, {0x10311cc60, 0x1, 0x1}, {0x14000130160?, 0x7?, 0x103126b00?})
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/testing/testing.go:2475 +0x3b8
testing.(*M).Run(0x1400011c140)
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/testing/testing.go:2337 +0x530
main.main()
        _testmain.go:45 +0x80

goroutine 35 [synctest.Run (durable), synctest bubble 1]:
internal/synctest.Run(0x140001301a0)
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/runtime/synctest.go:218 +0x1b4
testing/synctest.Test(0x14000102540, 0x103026650)
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/testing/synctest/synctest.go:282 +0x88
goexp1_test.TestDataLoaderSync(0x14000102540?)
        /Users/harry/GolandProjects/goexp1/dl_test.go:14 +0x24
testing.tRunner(0x14000102540, 0x1030265c0)
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/testing/testing.go:1934 +0xc8
created by testing.(*T).Run in goroutine 1
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/testing/testing.go:1997 +0x364

goroutine 36 [chan receive (durable), synctest bubble 1]:
testing/synctest.testingSynctestTest(0x14000102540, 0x103026650)
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/testing/testing.go:2047 +0x210
testing/synctest.Test.func1()
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/testing/synctest/synctest.go:283 +0x28
created by testing/synctest.Test in goroutine 35
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/testing/synctest/synctest.go:282 +0x88

goroutine 37 [chan receive (durable), synctest bubble 1]:
github.com/graph-gophers/dataloader/v7.(*Loader[...]).LoadMany.func3()
        /Users/harry/go/pkg/mod/github.com/graph-gophers/dataloader/v7@v7.1.2/dataloader.go:387 +0x114
goexp1_test.TestDataLoaderSync.func1(0x14000102a80)
        /Users/harry/GolandProjects/goexp1/dl_test.go:29 +0x104
testing.tRunner(0x14000102a80, 0x103026650)
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/testing/testing.go:1934 +0xc8
created by testing/synctest.testingSynctestTest in goroutine 36
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/testing/testing.go:2046 +0x1fc

goroutine 38 [chan receive (durable), synctest bubble 1]:
github.com/graph-gophers/dataloader/v7.(*Loader[...]).Load.func1()
        /Users/harry/go/pkg/mod/github.com/graph-gophers/dataloader/v7@v7.1.2/dataloader.go:263 +0x154
github.com/graph-gophers/dataloader/v7.(*Loader[...]).LoadMany.func1(0x0?)
        /Users/harry/go/pkg/mod/github.com/graph-gophers/dataloader/v7@v7.1.2/dataloader.go:348 +0xa0
created by github.com/graph-gophers/dataloader/v7.(*Loader[...]).LoadMany in goroutine 37
        /Users/harry/go/pkg/mod/github.com/graph-gophers/dataloader/v7@v7.1.2/dataloader.go:355 +0x17c

goroutine 39 [sync.WaitGroup.Wait (durable), synctest bubble 1]:
sync.runtime_SemacquireWaitGroup(0x0?, 0x0?)
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/runtime/sema.go:114 +0x38
sync.(*WaitGroup).Wait(0x140001100c0)
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/sync/waitgroup.go:206 +0xa8
github.com/graph-gophers/dataloader/v7.(*Loader[...]).LoadMany.func2()
        /Users/harry/go/pkg/mod/github.com/graph-gophers/dataloader/v7@v7.1.2/dataloader.go:359 +0x48
created by github.com/graph-gophers/dataloader/v7.(*Loader[...]).LoadMany in goroutine 37
        /Users/harry/go/pkg/mod/github.com/graph-gophers/dataloader/v7@v7.1.2/dataloader.go:358 +0x254

goroutine 2 [chan receive (durable), synctest bubble 1]:
github.com/graph-gophers/dataloader/v7.(*batcher[...]).batch(0x103029860, {0x1030289d8, 0x1400015c140})
        /Users/harry/go/pkg/mod/github.com/graph-gophers/dataloader/v7@v7.1.2/dataloader.go:475 +0xc4
created by github.com/graph-gophers/dataloader/v7.(*Loader[...]).Load in goroutine 38
        /Users/harry/go/pkg/mod/github.com/graph-gophers/dataloader/v7@v7.1.2/dataloader.go:289 +0x4c4

goroutine 3 [select (durable), synctest bubble 1]:
github.com/graph-gophers/dataloader/v7.(*Loader[...]).sleeper(0x10302a2a0, 0x14000072000, 0x14000074000)
        /Users/harry/go/pkg/mod/github.com/graph-gophers/dataloader/v7@v7.1.2/dataloader.go:535 +0x64
created by github.com/graph-gophers/dataloader/v7.(*Loader[...]).Load in goroutine 38
        /Users/harry/go/pkg/mod/github.com/graph-gophers/dataloader/v7@v7.1.2/dataloader.go:292 +0x574

goroutine 20 [sync.RWMutex.RLock, synctest bubble 1]:
sync.runtime_SemacquireRWMutexR(0x1400015c140?, 0xe0?, 0x0?)
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/runtime/sema.go:100 +0x28
sync.(*RWMutex).RLock(...)
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/sync/rwmutex.go:74
github.com/graph-gophers/dataloader/v7.(*Loader[...]).Load.func1()
        /Users/harry/go/pkg/mod/github.com/graph-gophers/dataloader/v7@v7.1.2/dataloader.go:257 +0xa8
github.com/graph-gophers/dataloader/v7.(*Loader[...]).LoadMany.func1(0x0?)
        /Users/harry/go/pkg/mod/github.com/graph-gophers/dataloader/v7@v7.1.2/dataloader.go:348 +0xa0
created by github.com/graph-gophers/dataloader/v7.(*Loader[...]).LoadMany in goroutine 37
        /Users/harry/go/pkg/mod/github.com/graph-gophers/dataloader/v7@v7.1.2/dataloader.go:355 +0x17c

goroutine 21 [sync.WaitGroup.Wait (durable), synctest bubble 1]:
sync.runtime_SemacquireWaitGroup(0x0?, 0x0?)
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/runtime/sema.go:114 +0x38
sync.(*WaitGroup).Wait(0x14000096010)
        /opt/homebrew/Cellar/go/1.25.1/libexec/src/sync/waitgroup.go:206 +0xa8
github.com/graph-gophers/dataloader/v7.(*Loader[...]).LoadMany.func2()
        /Users/harry/go/pkg/mod/github.com/graph-gophers/dataloader/v7@v7.1.2/dataloader.go:359 +0x48
created by github.com/graph-gophers/dataloader/v7.(*Loader[...]).LoadMany in goroutine 37
        /Users/harry/go/pkg/mod/github.com/graph-gophers/dataloader/v7@v7.1.2/dataloader.go:358 +0x254
FAIL    goexp1  5.168s
FAIL

I initially thought it was a bug with synctest, but it was explained: golang/go#75626

The problem is that "waiting" on a mutex isn't "waiting" as for as synctest is concerned. I think the Thunk and ThunkMany logic can be simplified and work with synctest.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions