state: preserve sync.Once completion across transfers - #31
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Review: sync.Once support in state serialization
The change adds a single, well-targeted arm to syncFields so sync.Once serializes only its done flag, while decodeStruct's obj.SetZero() (decode.go:443) zeros the surrounding struct — including the embedded Mutex — before restoring done. This yields a fresh, unlocked mutex on load, which is the correct behavior. I confirmed against the go1.26.6 sync.Once source that done is set via defer even when the action panics, so the test's want = 0 expectation for the panic status is right.
Strengths
- Clean reuse of existing infrastructure: the nested
done atomic.Boolre-enterssyncFields, and read-only-flag handling in encode/decode makes unexported-field access safe with no extra code. - The expanded
TestSyncOnceZerois thorough — the pre-locked-mutexTryLockassertion validates mutex reset, the 8-goroutine fan-out confirms exactly-once semantics survive restore, and thewritebacksubtest verifies pointer-alias preservation, guest/host isolation, and correctness across two full save/load cycles.
Notes
- No blocking issues. One robustness note is left inline regarding version-gating of the field access.
- Nit: the doc comment's "fresh mutex on load" describes an effect produced generically by
SetZero()for allsyncFieldstypes, notOnce-specific logic — optional wording clarification.
Tests could not be executed in this environment due to a pre-existing ixgo toolchain linking issue (invalid reference to reflect.ptrMap), unrelated to this PR; review is static.
| case reflect.TypeFor[sync.Once](): | ||
| return []reflect.Value{obj.FieldByName("done")}, true |
There was a problem hiding this comment.
[P3] Once/Pool/Cond field access is not version-gated
The sync.Once case uses obj.FieldByName("done") with no field-presence or version guard, and the Once path returns before the runtime.Version() != "go1.26.6" assertion at line 42 (that check only guards the sync/atomic paths). The pre-existing Pool.New/Cond.L arms share this gap. If a future Go release renames or restructures these fields, FieldByName returns an invalid reflect.Value and encode/decode panics at first snapshot of such a type rather than failing loudly at startup. Since the codebase already hard-pins go1.26.6 for the atomic paths, consider extending an equivalent explicit version/field-presence assertion to the Once (and Pool/Cond) cases so a Go upgrade fails early with a clear message. Robustness/portability only — not exploitable and not a correctness defect at the pinned version.
No description provided.