Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
99c9723
Add backpressure counter for monitoring token queue load
chall37 Jan 20, 2026
3187540
Fix backpressure counter initialization and document high-priority by…
chall37 Jan 20, 2026
a174c17
Add backpressure release handler and queue cleanup accounting
chall37 Jan 27, 2026
5206e78
Add FairnessScheduler with feature flag integration
chall37 Jan 27, 2026
2d7cd83
Add feature flag gating tests and fix sessionId=0 bug
chall37 Jan 27, 2026
d21ee5a
Remove accidentally added utility script
chall37 Jan 27, 2026
392a2c8
Added thread access annotations.
chall37 Jan 28, 2026
81e1efa
Add queue access annotations to new TokenExecutor vars.
chall37 Jan 28, 2026
4eb5591
Add queue annotations and preconditions to executeTurn/cleanupForUnre…
chall37 Jan 28, 2026
a3672da
Add dispatchPreconditions to FairnessScheduler internal methods.
chall37 Jan 28, 2026
4ea12f8
Extract signalAndRelease() helper in TokenArray.
chall37 Jan 28, 2026
9b97664
Move FairnessSchedulerExecutor conformance to extension.
chall37 Jan 29, 2026
1fb10ef
Cache useFairnessScheduler flag at TokenExecutorImpl init time.
chall37 Jan 29, 2026
75c4580
Replace executionScheduled flag with IdempotentOperationJoiner
chall37 Jan 29, 2026
28c8126
Rewrite executeTurn() to align with execute() patterns
chall37 Jan 29, 2026
a092599
Add TokenExecutorDeferCompletionOrderingTests for high-priority task …
chall37 Jan 29, 2026
de334ea
Preserve tokens on unregister and move registration to setTerminalEna…
chall37 Jan 31, 2026
df0ea51
Fix deadlock in FairnessScheduler by using private Mutex instead of m…
chall37 Feb 1, 2026
9984fc6
Add session restoration tests and fix cleanup tests for token preserv…
chall37 Feb 1, 2026
5f5edf6
Improve fairness scheduler test quality and reliability
chall37 Feb 1, 2026
1b203d1
Remove dead discardAllAndReturnCount() methods from TwoTierTokenQueue
chall37 Feb 1, 2026
5819353
Restore async dispatch for FairnessScheduler hot path to fix performa…
chall37 Feb 1, 2026
bc88974
Increase FairnessScheduler token budget from 500 to 1000
chall37 Feb 1, 2026
1d400e2
Optimize FairnessScheduler callback path and skip legacy session trac…
chall37 Feb 3, 2026
c29299e
Add threading documentation to FairnessSchedulerExecutor protocol met…
chall37 Feb 3, 2026
65bca07
Set isRegistered flag on FairnessScheduler registration
chall37 Feb 3, 2026
860734a
Add thread access documentation to TokenExecutorImpl member variables
chall37 Feb 3, 2026
38edb95
Add per-PTY dispatch sources and optimize FairnessScheduler callback …
chall37 Feb 2, 2026
e8119ec
Add missing VT100ScreenDelegate methods to FakeSession
chall37 Feb 3, 2026
0fc6017
Change dispatchPrecondition gate from DEBUG to ITERM_DEBUG
chall37 Feb 3, 2026
291b55e
Address PR #568 review feedback
chall37 Feb 4, 2026
ef76606
Wire coprocess I/O to dispatch sources and fix addTokens blocking _io…
chall37 Feb 12, 2026
519efac
Fix scheduler stall when session unregisters during executeTurn
chall37 Feb 12, 2026
78fff3f
Fix test failures: enable fairness scheduler flag, remove dead ITERM_…
chall37 Feb 12, 2026
f4f4256
Add test for didRegister backpressure wiring order
chall37 Feb 12, 2026
ac57d4b
Wire backpressure dependencies before starting dispatch sources
chall37 Feb 12, 2026
140ffd1
Add thread safety annotations and debug queue assertions
chall37 Feb 12, 2026
c1a3831
Add test for startup race with preloaded pipe data under heavy backpr…
chall37 Feb 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,581 changes: 1,581 additions & 0 deletions ModernTests/FairnessScheduler/FairnessSchedulerTests.swift

Large diffs are not rendered by default.

1,945 changes: 1,945 additions & 0 deletions ModernTests/FairnessScheduler/IntegrationTests.swift

Large diffs are not rendered by default.

144 changes: 144 additions & 0 deletions ModernTests/FairnessScheduler/Mocks/EnqueuingPTYTaskDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//
// EnqueuingPTYTaskDelegate.swift
// ModernTests
//
// Specialized PTYTaskDelegate for testing the read pipeline that enqueues
// read data to a TokenExecutor instead of just tracking calls.
// This enables end-to-end pipeline testing from read → parse → queue.
//

import Foundation
@testable import iTerm2SharedARC

/// A PTYTaskDelegate that actually enqueues read data to a TokenExecutor.
/// Used for testing the full read handler pipeline.
final class EnqueuingPTYTaskDelegate: NSObject, PTYTaskDelegate {

// MARK: - Configuration

/// The TokenExecutor to enqueue tokens to.
var tokenExecutor: TokenExecutor?

/// Callback invoked when data is read (before enqueueing).
var onThreadedRead: ((Data) -> Void)?

/// Callback invoked after tokens are enqueued.
var onEnqueued: (() -> Void)?

// MARK: - Call Tracking

private let lock = NSLock()
private var _readCount = 0
private var _totalBytesRead = 0
private var _enqueueCount = 0

var readCount: Int {
lock.lock()
defer { lock.unlock() }
return _readCount
}

var totalBytesRead: Int {
lock.lock()
defer { lock.unlock() }
return _totalBytesRead
}

var enqueueCount: Int {
lock.lock()
defer { lock.unlock() }
return _enqueueCount
}

// MARK: - Initialization

override init() {
super.init()
}

/// Convenience initializer for tests that need an executor set immediately.
convenience init(executor: TokenExecutor) {
self.init()
self.tokenExecutor = executor
}

// MARK: - PTYTaskDelegate

func threadedReadTask(_ buffer: UnsafeMutablePointer<CChar>, length: Int32) {
lock.lock()
_readCount += 1
_totalBytesRead += Int(length)
let data = Data(bytes: buffer, count: Int(length))
lock.unlock()

onThreadedRead?(data)

// Enqueue tokens to the executor (mimics PTYSession behavior)
if let executor = tokenExecutor {
// Add enough tokens to trigger backpressure change for verification
executor.addMultipleTokenArrays(count: 100, tokensPerArray: 5)

lock.lock()
_enqueueCount += 1
lock.unlock()

onEnqueued?()
}
}

func threadedTaskBrokenPipe() {
// Not used in pipeline tests
}

func brokenPipe() {
// Not used in pipeline tests
}

func tmuxClientWrite(_ data: Data) {
// Not used in pipeline tests
}

func taskDiedImmediately() {
// Not used in pipeline tests
}

func taskDiedWithError(_ error: String!) {
// Not used in pipeline tests
}

func taskDidChangeTTY(_ task: PTYTask) {
// Not used in pipeline tests
}

func taskDidRegister(_ task: PTYTask) {
// Not used in pipeline tests
}

func taskDidChangePaused(_ task: PTYTask, paused: Bool) {
// Not used in pipeline tests
}

func taskMuteCoprocessDidChange(_ task: PTYTask, hasMuteCoprocess: Bool) {
// Not used in pipeline tests
}

func taskDidResize(to gridSize: VT100GridSize, pixelSize: NSSize) {
// Not used in pipeline tests
}

func taskDidReadFromCoprocessWhileSSHIntegration(inUse data: Data) {
// Not used in pipeline tests
}

// MARK: - Test Helpers

func reset() {
lock.lock()
_readCount = 0
_totalBytesRead = 0
_enqueueCount = 0
onThreadedRead = nil
onEnqueued = nil
lock.unlock()
}
}
82 changes: 82 additions & 0 deletions ModernTests/FairnessScheduler/Mocks/MockCoprocess.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// MockCoprocess.swift
// ModernTests
//
// Mock implementation of Coprocess for testing TaskNotifier coprocess handling.
// Subclass of Coprocess that uses pipes instead of spawning a subprocess.
//

import Foundation
@testable import iTerm2SharedARC

class MockCoprocess: Coprocess {

/// The external write FD - test code writes here to simulate coprocess output.
/// This is the write end of the read pipe (TaskNotifier reads from inputFd/readFileDescriptor).
private(set) var testWriteFd: Int32 = -1

/// The external read FD - test code reads here to see data written to coprocess.
/// This is the read end of the write pipe (TaskNotifier writes to outputFd/writeFileDescriptor).
private(set) var testReadFd: Int32 = -1

/// Create a MockCoprocess with pipe FDs.
/// Returns nil on failure (pipe creation failed).
@objc static func createPipe() -> MockCoprocess? {
var readPipe: [Int32] = [0, 0]
var writePipe: [Int32] = [0, 0]

guard pipe(&readPipe) == 0 else { return nil }
guard pipe(&writePipe) == 0 else {
close(readPipe[0])
close(readPipe[1])
return nil
}

// Set non-blocking on inputFd
var flags = fcntl(readPipe[0], F_GETFL)
fcntl(readPipe[0], F_SETFL, flags | O_NONBLOCK)

// Set non-blocking on outputFd
flags = fcntl(writePipe[1], F_GETFL)
fcntl(writePipe[1], F_SETFL, flags | O_NONBLOCK)

let coprocess = MockCoprocess()
coprocess.inputFd = readPipe[0]
coprocess.outputFd = writePipe[1]
coprocess.pid = getpid()
coprocess.testWriteFd = readPipe[1]
coprocess.testReadFd = writePipe[0]

return coprocess
}

deinit {
closeTestFds()
}

// Override to NOT send kill signal - MockCoprocess uses getpid() as pid
// and we don't want to kill the test process!
override func terminate() {
if outputFd >= 0 {
close(outputFd)
outputFd = -1
}
if inputFd >= 0 {
close(inputFd)
inputFd = -1
}
pid = -1
}

/// Close the test FDs (call in addition to terminate to clean up test resources).
@objc func closeTestFds() {
if testWriteFd >= 0 {
close(testWriteFd)
testWriteFd = -1
}
if testReadFd >= 0 {
close(testReadFd)
testReadFd = -1
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//
// MockFairnessSchedulerExecutor.swift
// ModernTests
//
// Mock executor for testing FairnessScheduler in isolation.
// This simulates the TokenExecutor interface that FairnessScheduler will use.
//

import Foundation
@testable import iTerm2SharedARC

// TurnResult and FairnessSchedulerExecutor are defined in FairnessScheduler.swift

/// Mock implementation for testing FairnessScheduler without real TokenExecutor.
final class MockFairnessSchedulerExecutor: FairnessSchedulerExecutor {

// MARK: - Configuration

/// The result to return from executeTurn. Set this before each test scenario.
var turnResult: TurnResult = .completed

/// If set, executeTurn calls this instead of using turnResult.
/// Useful for dynamic behavior or async testing.
var executeTurnHandler: ((Int, @escaping (TurnResult) -> Void) -> Void)?

/// Delay before calling completion (simulates execution time)
var executionDelay: TimeInterval = 0

// MARK: - Call Tracking

struct ExecuteTurnCall: Equatable {
let tokenBudget: Int
let timestamp: Date

static func == (lhs: ExecuteTurnCall, rhs: ExecuteTurnCall) -> Bool {
return lhs.tokenBudget == rhs.tokenBudget
}
}

private(set) var executeTurnCalls: [ExecuteTurnCall] = []
private(set) var totalTokenBudgetConsumed: Int = 0

// MARK: - FairnessSchedulerExecutor

func executeTurn(tokenBudget: Int, completion: @escaping (TurnResult) -> Void) {
executeTurnCalls.append(ExecuteTurnCall(tokenBudget: tokenBudget, timestamp: Date()))
totalTokenBudgetConsumed += tokenBudget

if let handler = executeTurnHandler {
handler(tokenBudget, completion)
return
}

if executionDelay > 0 {
DispatchQueue.global().asyncAfter(deadline: .now() + executionDelay) { [turnResult] in
completion(turnResult)
}
} else {
completion(turnResult)
}
}

// MARK: - Test Helpers

func reset() {
turnResult = .completed
executeTurnHandler = nil
executionDelay = 0
executeTurnCalls = []
totalTokenBudgetConsumed = 0
}

/// Returns the number of times executeTurn was called
var executeTurnCallCount: Int {
return executeTurnCalls.count
}
}
Loading