Reproduces deadlock from CBG-663. Required adding a sleep inside the <-time.After case in releaseSequenceMonitor to reliably queue up a large number of sequence allocation requests between <-time.After fires and releaseUnusedSequences is called (where previously reserveNotify would block)
(t *testing.T)
| 149 | // releaseSequenceMonitor to reliably queue up a large number of sequence allocation requests |
| 150 | // between <-time.After fires and releaseUnusedSequences is called (where previously reserveNotify would block) |
| 151 | func TestSequenceAllocatorDeadlock(t *testing.T) { |
| 152 | |
| 153 | t.Skip("Requires additional sleep in production code to reliably hit race") |
| 154 | |
| 155 | var a *sequenceAllocator |
| 156 | var err error |
| 157 | |
| 158 | var wg sync.WaitGroup |
| 159 | ctx := base.TestCtx(t) |
| 160 | callbackCount := 0 |
| 161 | incrCallback := func() { |
| 162 | callbackCount++ |
| 163 | if callbackCount == 2 { |
| 164 | // queue up a number of sequence requests |
| 165 | // Wait for 500ms for releaseSequenceMonitor time.After to trigger |
| 166 | time.Sleep(100 * time.Millisecond) |
| 167 | |
| 168 | for i := 0; i < 500; i++ { |
| 169 | wg.Add(1) |
| 170 | go func(a *sequenceAllocator) { |
| 171 | _, err := a.nextSequence(ctx) |
| 172 | assert.NoError(t, err) |
| 173 | wg.Done() |
| 174 | }(a) |
| 175 | } |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | bucket := base.NewLeakyBucket(base.GetTestBucket(t), base.LeakyBucketConfig{IncrCallback: incrCallback}) |
| 180 | defer bucket.Close(ctx) |
| 181 | |
| 182 | sgw, err := base.NewSyncGatewayStats() |
| 183 | require.NoError(t, err) |
| 184 | dbstats, err := sgw.NewDBStats("", false, false, false, nil, nil) |
| 185 | require.NoError(t, err) |
| 186 | testStats := dbstats.Database() |
| 187 | |
| 188 | oldFrequency := MaxSequenceIncrFrequency |
| 189 | defer func() { MaxSequenceIncrFrequency = oldFrequency }() |
| 190 | MaxSequenceIncrFrequency = 1000 * time.Millisecond |
| 191 | |
| 192 | a, err = newSequenceAllocator(ctx, bucket.DefaultDataStore(), testStats, base.DefaultMetadataKeys) |
| 193 | // Reduce sequence wait for Stop testing |
| 194 | a.releaseSequenceWait = 10 * time.Millisecond |
| 195 | assert.NoError(t, err, "error creating allocator") |
| 196 | |
| 197 | nextSequence, err := a.nextSequence(ctx) |
| 198 | assert.NoError(t, err) |
| 199 | assert.Equal(t, uint64(1), nextSequence) |
| 200 | |
| 201 | nextSequence, err = a.nextSequence(ctx) |
| 202 | assert.NoError(t, err) |
| 203 | assert.Equal(t, uint64(2), nextSequence) |
| 204 | |
| 205 | wg.Wait() |
| 206 | |
| 207 | a.Stop(ctx) |
| 208 | } |
nothing calls this directly
no test coverage detected