TestValueResSendRecvAliasRace reproduces the value-resource send/recv aliasing race that survives the issue #926 Sendable fix. ValueRes.CheckApply does `obj.cachedAny = obj.Any` and then publishes `&ValueSends{Any: obj.cachedAny}`. That makes the published snapshot's `Any` pointer alias obj.Any's l
(t *testing.T)
| 64 | // gates the test) with the in-place recv write emulated, and the receiver reads |
| 65 | // Sent() the way engine/graph/sendrecv.go does. |
| 66 | func TestValueResSendRecvAliasRace(t *testing.T) { |
| 67 | res := &ValueRes{} |
| 68 | res.SetKind("value") |
| 69 | res.SetName("test-value-race") |
| 70 | |
| 71 | api := (&local.API{ |
| 72 | Prefix: t.TempDir(), |
| 73 | Logf: func(string, ...interface{}) {}, |
| 74 | }).Init() |
| 75 | |
| 76 | // A persistent recv slot so every CheckApply takes the |
| 77 | // `obj.cachedAny = obj.Any` branch, exactly as the engine would after |
| 78 | // receiving on `any`. |
| 79 | recv := map[string]*engine.Send{ |
| 80 | "any": {Changed: true}, |
| 81 | } |
| 82 | |
| 83 | init := &engine.Init{ |
| 84 | Send: engine.GenerateSendFunc(res), // routes into the Sendable trait |
| 85 | Recv: func() map[string]*engine.Send { return recv }, |
| 86 | Local: api, |
| 87 | Logf: func(string, ...interface{}) {}, |
| 88 | } |
| 89 | if err := res.Init(init); err != nil { |
| 90 | t.Fatalf("func Init failed: %+v", err) |
| 91 | } |
| 92 | |
| 93 | // obj.Any must be a stable, non-nil *interface{} across cycles so the |
| 94 | // recv in-place write keeps aliasing prior snapshots. This mirrors |
| 95 | // types.Into instantiating the pointer once and then mutating in place. |
| 96 | any := interface{}("value") |
| 97 | res.Any = &any |
| 98 | |
| 99 | ctx := context.Background() |
| 100 | const iterations = 100000 |
| 101 | start := make(chan struct{}) |
| 102 | wg := &sync.WaitGroup{} |
| 103 | |
| 104 | wg.Add(2) |
| 105 | // Sender / value Worker: emulate the recv in-place write into obj.Any's |
| 106 | // pointee, then run the real CheckApply which publishes the snapshot. |
| 107 | go func() { |
| 108 | defer wg.Done() |
| 109 | <-start |
| 110 | |
| 111 | for i := 0; i < iterations; i++ { |
| 112 | // Equivalent to lang/types.Into writing into the |
| 113 | // existing non-nil *interface{} in place (rv.Set on the |
| 114 | // interface word). Same value keeps CheckApply cheap, |
| 115 | // and the race is on the access, not the contents. |
| 116 | *res.Any = "value" |
| 117 | if _, err := res.CheckApply(ctx, true); err != nil { |
| 118 | t.Errorf("method CheckApply failed: %+v", err) |
| 119 | return |
| 120 | } |
| 121 | runtime.Gosched() |
| 122 | } |
| 123 | }() |
nothing calls this directly
no test coverage detected