| 762 | } |
| 763 | |
| 764 | func (m *SyncMockManager) SetMemoryPressure(enabled bool) { |
| 765 | if m == nil { |
| 766 | return |
| 767 | } |
| 768 | |
| 769 | // time.Now() OUTSIDE the lock: cheap, and avoids holding mu across the |
| 770 | // syscall. Reused for both the new-range open AND the close, so the two |
| 771 | // transitions can never produce a range with end < start due to clock skew. |
| 772 | now := time.Now() |
| 773 | |
| 774 | m.mu.Lock() |
| 775 | wasEnabled := m.memoryPause |
| 776 | m.memoryPause = enabled |
| 777 | |
| 778 | // Prune the pressure-range history to the staleness horizon so it stays |
| 779 | // bounded under continuous recording (mentor review #4220, syncMock.go:119). |
| 780 | // Keep the currently-open range (end == zero) and any range that ended |
| 781 | // within the last pressureRangeStaleness; drop the rest. memoryguard calls |
| 782 | // this every 500 ms, so the slice never holds more than ~the last few |
| 783 | // seconds of history regardless of recording length. In-place filter, |
| 784 | // preserving order (the open range, if any, is newest so stays last). |
| 785 | if cutoff := now.Add(-pressureRangeStaleness); len(m.pressureRanges) > 0 { |
| 786 | kept := m.pressureRanges[:0] |
| 787 | for _, r := range m.pressureRanges { |
| 788 | if r.end.IsZero() || r.end.After(cutoff) { |
| 789 | kept = append(kept, r) |
| 790 | } |
| 791 | } |
| 792 | m.pressureRanges = kept |
| 793 | } |
| 794 | |
| 795 | var clearedFromBuffer int |
| 796 | if enabled { |
| 797 | if !wasEnabled { |
| 798 | // false→true transition: open a new pressure interval. |
| 799 | // memoryguard fires SetMemoryPressure(true) once per 500ms tick, |
| 800 | // but only the first call (when wasEnabled is false) is a real |
| 801 | // transition; subsequent ticks while pressure is held are no-ops |
| 802 | // for range tracking — they would otherwise spam the slice. |
| 803 | m.pressureRanges = append(m.pressureRanges, pressureRange{start: now}) |
| 804 | } |
| 805 | // Don't wipe the whole buffer. Two classes of mock must survive: |
| 806 | // 1. Startup-window mocks (IsStartup) — boot traffic and everything |
| 807 | // captured within the first StartupMockTestCaseWindow test cases |
| 808 | // (#4282). They own no resolved window and reach disk only via |
| 809 | // FlushOwnedWindows; wiping them is the exact loss the IsStartup |
| 810 | // tag exists to prevent. |
| 811 | // 2. Mocks whose request happened during calm — they belong to an |
| 812 | // already-captured TC and must survive or replay orphans it |
| 813 | // (#4220 Bug-0). Only mocks whose request was during pressure are |
| 814 | // dropped: the ingress never captured those, so there is no TC to |
| 815 | // orphan. |
| 816 | // Unknown timestamp → keep (safe default). In-place filter, nil the tail. |
| 817 | before := len(m.buffer) |
| 818 | keep := m.buffer[:0] |
| 819 | for _, mk := range m.buffer { |
| 820 | if mk == nil { |
| 821 | continue |