|
1 | 1 | package playwright
|
2 | 2 |
|
3 | 3 | import (
|
| 4 | + "errors" |
4 | 5 | "fmt"
|
| 6 | + "sync/atomic" |
5 | 7 | "testing"
|
6 | 8 | "time"
|
7 | 9 |
|
@@ -136,3 +138,88 @@ func TestWaiterReturnErrorWhenMisuse(t *testing.T) {
|
136 | 138 | _, err = waiter.Wait()
|
137 | 139 | require.ErrorContains(t, err, "call RejectOnEvent before WaitForEvent")
|
138 | 140 | }
|
| 141 | + |
| 142 | +func TestWaiterDeadlockForErrChanCapIs1AndCallbackErr(t *testing.T) { |
| 143 | + // deadlock happen on waiter timeout before callback return err |
| 144 | + waiterTimeout := 200.0 |
| 145 | + callbackTimeout := time.Duration(waiterTimeout+200.0) * time.Millisecond |
| 146 | + |
| 147 | + mockCallbackErr := errors.New("mock callback error") |
| 148 | + |
| 149 | + emitter := &eventEmitter{} |
| 150 | + w := &waiter{ |
| 151 | + // just receive event timeout err or callback err |
| 152 | + errChan: make(chan error, 1), |
| 153 | + } |
| 154 | + |
| 155 | + callbackOverCh := make(chan struct{}) |
| 156 | + callbackErrCh := make(chan error) |
| 157 | + isAfterWaiterRunAndWaitExecuted := atomic.Bool{} |
| 158 | + go func() { |
| 159 | + _, err := w.WithTimeout(waiterTimeout).WaitForEvent(emitter, "", nil).RunAndWait(func() error { |
| 160 | + time.Sleep(callbackTimeout) |
| 161 | + close(callbackOverCh) |
| 162 | + // block for this err, for waiter.errChan has cache event timeout err |
| 163 | + return mockCallbackErr |
| 164 | + }) |
| 165 | + |
| 166 | + isAfterWaiterRunAndWaitExecuted.Store(true) |
| 167 | + callbackErrCh <- err |
| 168 | + }() |
| 169 | + |
| 170 | + // ensure waiter timeout |
| 171 | + <-callbackOverCh |
| 172 | + // give some time but never enough |
| 173 | + time.Sleep(200 * time.Millisecond) |
| 174 | + |
| 175 | + // Originally it was executed, but because waiter.errChan is currently caching the waiter timeout error, |
| 176 | + // the callback error is blocked (because waitFunc has not been executed yet, |
| 177 | + // waiter.errChan has not started receiving). |
| 178 | + require.False(t, isAfterWaiterRunAndWaitExecuted.Load()) |
| 179 | + |
| 180 | + // if not receive waiter timeout error, isAfterWaiterRunAndWaitExecuted should be always false |
| 181 | + err1 := <-w.errChan |
| 182 | + require.ErrorIs(t, err1, ErrTimeout) |
| 183 | + |
| 184 | + // for w.errChan cache is empty, callback error is sent and received, and then return it |
| 185 | + err2 := <-callbackErrCh |
| 186 | + require.ErrorIs(t, err2, mockCallbackErr) |
| 187 | + require.True(t, isAfterWaiterRunAndWaitExecuted.Load()) |
| 188 | +} |
| 189 | + |
| 190 | +func TestWaiterHasNotDeadlockForErrChanCapBiggerThan1AndCallbackErr(t *testing.T) { |
| 191 | + // deadlock happen on waiter timeout before callback return err |
| 192 | + waiterTimeout := 100.0 |
| 193 | + callbackTimeout := time.Duration(waiterTimeout+100.0) * time.Millisecond |
| 194 | + |
| 195 | + mockCallbackErr := errors.New("mock callback error") |
| 196 | + |
| 197 | + emitter := &eventEmitter{} |
| 198 | + w := newWaiter() |
| 199 | + |
| 200 | + callbackOverCh := make(chan struct{}) |
| 201 | + callbackErrCh := make(chan error) |
| 202 | + isAfterWaiterRunAndWaitExecuted := atomic.Bool{} |
| 203 | + go func() { |
| 204 | + _, err := w.WithTimeout(waiterTimeout).WaitForEvent(emitter, "", nil).RunAndWait(func() error { |
| 205 | + time.Sleep(callbackTimeout) |
| 206 | + close(callbackOverCh) |
| 207 | + return mockCallbackErr |
| 208 | + }) |
| 209 | + isAfterWaiterRunAndWaitExecuted.Store(true) |
| 210 | + callbackErrCh <- err |
| 211 | + }() |
| 212 | + |
| 213 | + // ensure waiter timeout |
| 214 | + <-callbackOverCh |
| 215 | + |
| 216 | + // for waiter.errChan cap is 2(greater than 1), so it will not block(deadlock) |
| 217 | + require.Eventually(t, |
| 218 | + func() bool { return isAfterWaiterRunAndWaitExecuted.Load() }, 100*time.Millisecond, 10*time.Microsecond) |
| 219 | + |
| 220 | + // the first err still is waiter timeout, and is returned |
| 221 | + err1 := <-w.errChan |
| 222 | + require.ErrorIs(t, err1, mockCallbackErr) |
| 223 | + err2 := <-callbackErrCh |
| 224 | + require.ErrorIs(t, err2, ErrTimeout) |
| 225 | +} |
0 commit comments