@@ -25,12 +25,21 @@ nonisolated(unsafe) private var _cleanupEnabled = false
2525
2626public actor ResourceArbiter {
2727 public static let shared = ResourceArbiter ( )
28-
28+
2929 private var inFlightCount : Int = 0
3030 private var snapshotOpsInProgress : Bool = false
31- private let maxInFlightLightweight = 3
32-
33- private let memoryThresholdMB = 1024 // 1GB - force serial below this
31+
32+ // Dynamic concurrency limits based on empirical telemetry
33+ // Derived from profile-1775950630: min free was 195MB
34+ private var maxInFlightLightweight = 3 // Reduced dynamically when memory pressure
35+
36+ // Empirical thresholds from Victoria Protocol
37+ // Threshold = MinObservedFree(195MB) + SafetyMargin(100MB) + OS_Buffer(150MB)
38+ private let criticalThresholdMB = 300 // Block all tests below this (system unstable)
39+ private let heavyThresholdMB = 450 // Force serial below this (empirical: 195+100+150)
40+ private let mediumThresholdMB = 600 // Limit concurrency below this
41+ private let parallelThresholdMB = 800 // Full parallelism above this
42+
3443 private let ioPressureThreshold = 5 // block new tests if too many I/O ops
3544
3645 // Cleanup configuration
@@ -162,30 +171,69 @@ public actor ResourceArbiter {
162171 }
163172 }
164173
165- public func requestExecutionSlot( for weight: TestWeight ) -> ExecutionMode {
174+ public func requestExecutionSlot( for weight: TestWeight ) -> ExecutionMode {
166175 let freeMemory = ResourceHelper . getLatestFreeMemory ( ) ?? 8192
167-
176+
177+ // Check for critical memory pressure first
178+ if freeMemory < criticalThresholdMB {
179+ return . blocked( reason: " ⛔ CRITICAL: Memory at \( freeMemory) MB (threshold: \( criticalThresholdMB) MB). Close Chrome/Slack to continue. " )
180+ }
181+
168182 if weight == . snapshotHeavy || snapshotOpsInProgress {
169183 return . blocked( reason: " Snapshot operation in progress - preventing I/O pile-up " )
170184 }
171-
172- if freeMemory < memoryThresholdMB {
173- return . serial
174- }
175-
176- if weight == . heavy {
177- return . serial
178- }
179-
180- if weight == . lightweight {
185+
186+ // Dynamic concurrency based on empirical thresholds
187+ // From profile-1775950630: min free was 195MB
188+ switch weight {
189+ case . heavy:
190+ // Heavy tests (WordPress + MySQL) need 450MB+ free
191+ // Empirical: 195MB min + 100MB safety + 150MB OS buffer
192+ if freeMemory < heavyThresholdMB {
193+ return . serial // Force serial when memory constrained
194+ }
195+ // Even with enough memory, limit to 1 heavy test at a time
196+ if inFlightCount >= 1 {
197+ return . blocked( reason: " Heavy test in progress - preventing memory pile-up (free: \( freeMemory) MB) " )
198+ }
199+ inFlightCount += 1
200+ return . parallel
201+
202+ case . medium:
203+ // Medium tests need 600MB+ for comfortable parallel execution
204+ if freeMemory < mediumThresholdMB {
205+ return . serial // Serial when under pressure
206+ }
207+ if freeMemory < parallelThresholdMB {
208+ // Limited parallelism - reduce concurrent tests
209+ maxInFlightLightweight = 2
210+ }
181211 if inFlightCount >= maxInFlightLightweight {
182- return . blocked( reason: " Max in-flight containers ( \( maxInFlightLightweight ) ) reached " )
212+ return . blocked( reason: " Memory pressure - limiting concurrency (free: \( freeMemory ) MB) " )
183213 }
184214 inFlightCount += 1
185215 return . parallel
216+
217+ case . lightweight:
218+ // Lightweight tests are most flexible
219+ // Dynamically adjust concurrency based on available memory
220+ if freeMemory < mediumThresholdMB {
221+ maxInFlightLightweight = 2 // Reduce from 3 to 2
222+ } else if freeMemory < parallelThresholdMB {
223+ maxInFlightLightweight = 3 // Normal
224+ } else {
225+ maxInFlightLightweight = 4 // Can afford more when memory abundant
226+ }
227+
228+ if inFlightCount >= maxInFlightLightweight {
229+ return . blocked( reason: " Max in-flight containers ( \( maxInFlightLightweight) ) reached (free: \( freeMemory) MB) " )
230+ }
231+ inFlightCount += 1
232+ return . parallel
233+
234+ case . snapshotHeavy:
235+ return . blocked( reason: " Snapshot-heavy tests must run serially " )
186236 }
187-
188- return . parallel
189237 }
190238
191239 public func releaseExecutionSlot( for weight: TestWeight ) {
0 commit comments