GetMockErrors drains all mock-not-found errors and returns them. When StartErrorDrain is active, it reads from the test accumulator instead of the channel (which is drained by the background goroutine).
(_ context.Context)
| 2919 | // When StartErrorDrain is active, it reads from the test accumulator instead |
| 2920 | // of the channel (which is drained by the background goroutine). |
| 2921 | func (p *Proxy) GetMockErrors(_ context.Context) ([]models.UnmatchedCall, error) { |
| 2922 | // Rendezvous with the drain goroutine BEFORE taking captureMu: push a flush |
| 2923 | // marker and wait until the goroutine pulls it. FIFO ordering then |
| 2924 | // guarantees every error the goroutine had already received from errChannel |
| 2925 | // has been routed into the window — so closing the window below cannot |
| 2926 | // strand an in-flight miss (the remaining race the previous fix left open). |
| 2927 | // The app has already responded by now, so this test's pushes precede the |
| 2928 | // marker. A bounded wait keeps a stalled drain from blocking replay. |
| 2929 | rendezvousOK := false |
| 2930 | drainActive := p.errDrainActive.Load() |
| 2931 | if drainActive && p.errChannel != nil { |
| 2932 | marker := &flushMarker{done: make(chan struct{})} |
| 2933 | select { |
| 2934 | case p.errChannel <- marker: |
| 2935 | select { |
| 2936 | case <-marker.done: |
| 2937 | rendezvousOK = true |
| 2938 | case <-time.After(2 * time.Second): |
| 2939 | // drain stalled — DON'T close the window below (see end). |
| 2940 | } |
| 2941 | default: |
| 2942 | // errChannel full (drain is behind) — marker not enqueued; DON'T |
| 2943 | // close the window below. |
| 2944 | } |
| 2945 | } |
| 2946 | |
| 2947 | p.captureMu.Lock() |
| 2948 | |
| 2949 | // Pre-window stragglers (retained before the first capture window — e.g. |
| 2950 | // startup traffic). Oldest first. |
| 2951 | rawErrs := p.pendingMockErrors.drain() |
| 2952 | |
| 2953 | // Legacy path only (no drain goroutine): errors sit unrouted in errChannel, |
| 2954 | // so sweep them here. With the drain goroutine active, the rendezvous above |
| 2955 | // has already routed everything into the window, so sweeping is unnecessary |
| 2956 | // and would only risk pulling a NEXT test's stray error into this one. |
| 2957 | if !drainActive { |
| 2958 | drainLoop: |
| 2959 | for { |
| 2960 | select { |
| 2961 | case err, ok := <-p.errChannel: |
| 2962 | if !ok { |
| 2963 | break drainLoop |
| 2964 | } |
| 2965 | if m, isMarker := err.(*flushMarker); isMarker { |
| 2966 | close(m.done) |
| 2967 | continue |
| 2968 | } |
| 2969 | rawErrs = append(rawErrs, err) |
| 2970 | default: |
| 2971 | break drainLoop |
| 2972 | } |
| 2973 | } |
| 2974 | } |
| 2975 | |
| 2976 | if rendezvousOK || !drainActive { |
| 2977 | // Safe to END the window: the rendezvous confirmed every received error |
| 2978 | // is routed (or there's no drain goroutine). Swap to nil so the drain |