Skip to content

investigate(syncthing): reproduce and resolve the upstream STUN race #152

Description

@psimaker

Classification

PROVEN race in the pinned upstream source — P1. Product impact is UNVERIFIED.

Tested state

  • VaultSync commit: 9931f7a
  • Go: go1.26.6 darwin/arm64
  • Pinned Syncthing module: v1.30.0-rc.1.0.20260211104138-dc2a77ab8e5b
  • Focused command:
cd go
go test -race -tags noassets ./bridge -run '^TestGetConflictFilesJSON$' -count=10

Result: exit 1; race detector failed TestGetConflictFilesJSON.

Complete race stack

Absolute local prefixes are intentionally normalized to repository/module-relative paths; every function, frame, and line is retained.

WARNING: DATA RACE
Write at 0x00c000134040 by goroutine 211:
  github.com/syncthing/syncthing/lib/stun.(*Service).runStunForServer.func1()
      go/_syncthing_patched/lib/stun/stun.go:156 +0xb4
  github.com/syncthing/syncthing/lib/svcutil.CallWithContext.func1()
      go/_syncthing_patched/lib/svcutil/svcutil.go:231 +0x3c

Previous write at 0x00c000134040 by goroutine 102:
  github.com/syncthing/syncthing/lib/stun.(*Service).runStunForServer()
      go/_syncthing_patched/lib/stun/stun.go:155 +0x430
  github.com/syncthing/syncthing/lib/stun.(*Service).Serve()
      go/_syncthing_patched/lib/stun/stun.go:112 +0x350
  github.com/syncthing/syncthing/lib/connections.(*quicListener).serve.gowrap3()
      go/_syncthing_patched/lib/connections/quic_listen.go:111 +0x40

Goroutine 211 (running) created at:
  github.com/syncthing/syncthing/lib/svcutil.CallWithContext()
      go/_syncthing_patched/lib/svcutil/svcutil.go:230 +0x148
  github.com/syncthing/syncthing/lib/stun.(*Service).runStunForServer()
      go/_syncthing_patched/lib/stun/stun.go:155 +0x420
  github.com/syncthing/syncthing/lib/stun.(*Service).Serve()
      go/_syncthing_patched/lib/stun/stun.go:112 +0x350
  github.com/syncthing/syncthing/lib/connections.(*quicListener).serve.gowrap3()
      go/_syncthing_patched/lib/connections/quic_listen.go:111 +0x40

Goroutine 102 (finished) created at:
  github.com/syncthing/syncthing/lib/connections.(*quicListener).serve()
      go/_syncthing_patched/lib/connections/quic_listen.go:111 +0x5e8
  github.com/syncthing/syncthing/lib/connections.(*quicListener).serve-fm()
      <autogenerated>:1 +0x3c
  github.com/syncthing/syncthing/lib/svcutil.(*service).Serve()
      go/_syncthing_patched/lib/svcutil/svcutil.go:129 +0xa0
  github.com/syncthing/syncthing/lib/connections.(*quicListener).Serve()
      <autogenerated>:1 +0x50
  github.com/thejerf/suture/v4.(*Supervisor).runService.func2()
      github.com/thejerf/suture/v4@v4.0.6/supervisor.go:570 +0x160

--- FAIL: TestGetConflictFilesJSON (1.19s)
    testing.go:1712: race detected during execution of test
FAIL
FAIL github.com/psimaker/vaultsync/go/bridge 12.167s
FAIL

A full-suite race run also reproduced accesses at stun.go:155-161. Which bridge test is marked failed varies with scheduling; that does not yet make the conflict test the root cause.

Origin evidence

No repository patch references stun.go or svcutil.go. The audited files were byte-identical to the pinned module cache:

cmp -s go/_syncthing_patched/lib/stun/stun.go "$(go env GOMODCACHE)/github.com/syncthing/syncthing@v1.30.0-rc.1.0.20260211104138-dc2a77ab8e5b/lib/stun/stun.go"
exit 0
cmp -s go/_syncthing_patched/lib/svcutil/svcutil.go "$(go env GOMODCACHE)/github.com/syncthing/syncthing@v1.30.0-rc.1.0.20260211104138-dc2a77ab8e5b/lib/svcutil/svcutil.go"
exit 0

This attributes the observed source to the pinned upstream snapshot, not to a current local patch. It does not establish whether current upstream has already fixed it.

User and safety impact

UNVERIFIED. No production crash, corruption, connectivity failure, or supported real-device symptom has been causally linked to this race.

Acceptance criteria

  • Reproduce with the minimal engine/STUN/QUIC setup, independent of unrelated bridge tests.
  • Compare the pinned snapshot with then-current upstream.
  • Search for and link an existing upstream issue/PR/fix if present.
  • Determine whether the trigger is upstream runtime, VaultSync configuration, Bridge lifecycle, or fixture teardown.
  • Preserve complete race stacks and version/toolchain data.
  • Decide explicitly among upstream fix, minimal reviewed local patch, or Syncthing upgrade.
  • Run the full Bridge suite and race suite after the separately approved resolution.
  • Do not claim product impact without reproduction.

Minimal regression test

A focused repeated race test that starts only the necessary STUN/QUIC service lifecycle and deterministically reaches the shared access, with clean teardown and no dependence on GetConflictFilesJSON.

Out of scope

Any 2.0.2 fix, direct edit of go/_syncthing_patched, unapproved local patch, opportunistic Syncthing upgrade, and product-impact claim without evidence.

Verification

Pinned/current upstream diff; upstream issue search; minimal race reproducer; full race stacks; normal Bridge suite; gofmt; patch provenance; XCFramework only after an approved Go change.

Dependencies

  • Scheduled in 2.1.1 Syncthing Upgrade.
  • Does not block isolated 2.0.2 safety fixes, but every race run must report it honestly.
  • Any fix requires a separate approved implementation plan.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:syncthingEmbedded Syncthing and upstream behaviorbugSomething isn't workinggoPull requests that update go codepriority:P1Required reliability or security workrisk:reliabilityReliability, race, hang, or persistence risk

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions