Skip to content
Open
439 changes: 439 additions & 0 deletions ModernTests/iTermProcessCacheTests.swift

Large diffs are not rendered by default.

1,205 changes: 1,205 additions & 0 deletions ModernTests/iTermProcessMonitorTests.swift

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions iTerm2.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,7 @@
531E71F72229A69C00915960 /* iTermExpressionParser+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 531E71F62229A69C00915960 /* iTermExpressionParser+Private.h */; };
532755852387821C00C50732 /* iTermProcessMonitor.h in Headers */ = {isa = PBXBuildFile; fileRef = 532755832387821C00C50732 /* iTermProcessMonitor.h */; };
532755862387821C00C50732 /* iTermProcessMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = 532755842387821C00C50732 /* iTermProcessMonitor.m */; };
D7A9E6F12ABC123456789012 /* iTermProcessMonitor+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = D7A9E6F02ABC123456789011 /* iTermProcessMonitor+Testing.m */; };
532F942B215DFEF600D509E4 /* iTermCacheableImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 532F9429215DFEF600D509E4 /* iTermCacheableImage.h */; };
532F942C215DFEF600D509E4 /* iTermCacheableImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 532F942A215DFEF600D509E4 /* iTermCacheableImage.m */; };
533292A6237E75360027EB49 /* iTermPythonArgumentParserTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 533292A5237E75360027EB49 /* iTermPythonArgumentParserTests.m */; };
Expand Down Expand Up @@ -815,6 +816,8 @@
537BFDC520FFA5A40098C91F /* iTermStatusBarJobComponent.h in Headers */ = {isa = PBXBuildFile; fileRef = 537BFDC320FFA5A40098C91F /* iTermStatusBarJobComponent.h */; };
537BFDC920FFB2590098C91F /* iTermProcessCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 537BFDC720FFB2590098C91F /* iTermProcessCache.h */; };
537BFDCA20FFB2590098C91F /* iTermProcessCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 537BFDC820FFB2590098C91F /* iTermProcessCache.m */; };
BAB0C1C2780DA546AFE73B80 /* iTermProcessCache+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = 42BC3DE2E88700AF9526F074 /* iTermProcessCache+Testing.m */; };
2A1F4551EB4181AC933F6915 /* iTermProcessCacheTestHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 08829F633C36167ABC85693D /* iTermProcessCacheTestHelper.m */; };
537BFDD12101AD9F0098C91F /* iTermCPUUtilization.h in Headers */ = {isa = PBXBuildFile; fileRef = 537BFDCF2101AD9F0098C91F /* iTermCPUUtilization.h */; };
537BFDD22101AD9F0098C91F /* iTermCPUUtilization.m in Sources */ = {isa = PBXBuildFile; fileRef = 537BFDD02101AD9F0098C91F /* iTermCPUUtilization.m */; };
537BFDD52101B2500098C91F /* iTermStatusBarCPUUtilizationComponent.h in Headers */ = {isa = PBXBuildFile; fileRef = 537BFDD32101B2500098C91F /* iTermStatusBarCPUUtilizationComponent.h */; };
Expand Down Expand Up @@ -5996,6 +5999,8 @@
531E71F62229A69C00915960 /* iTermExpressionParser+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "iTermExpressionParser+Private.h"; sourceTree = "<group>"; };
532755832387821C00C50732 /* iTermProcessMonitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iTermProcessMonitor.h; sourceTree = "<group>"; };
532755842387821C00C50732 /* iTermProcessMonitor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iTermProcessMonitor.m; sourceTree = "<group>"; };
D7A9E6EF2ABC123456789010 /* iTermProcessMonitor+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "iTermProcessMonitor+Testing.h"; sourceTree = "<group>"; };
D7A9E6F02ABC123456789011 /* iTermProcessMonitor+Testing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "iTermProcessMonitor+Testing.m"; sourceTree = "<group>"; };
532F9429215DFEF600D509E4 /* iTermCacheableImage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iTermCacheableImage.h; sourceTree = "<group>"; };
532F942A215DFEF600D509E4 /* iTermCacheableImage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iTermCacheableImage.m; sourceTree = "<group>"; };
533292A5237E75360027EB49 /* iTermPythonArgumentParserTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iTermPythonArgumentParserTests.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6076,6 +6081,10 @@
537BFDC420FFA5A40098C91F /* iTermStatusBarJobComponent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iTermStatusBarJobComponent.m; sourceTree = "<group>"; };
537BFDC720FFB2590098C91F /* iTermProcessCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iTermProcessCache.h; sourceTree = "<group>"; };
537BFDC820FFB2590098C91F /* iTermProcessCache.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iTermProcessCache.m; sourceTree = "<group>"; };
C47650BBAFA6A247F2489C37 /* iTermProcessCache+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "iTermProcessCache+Testing.h"; sourceTree = "<group>"; };
42BC3DE2E88700AF9526F074 /* iTermProcessCache+Testing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "iTermProcessCache+Testing.m"; sourceTree = "<group>"; };
0FB571F65DACC53FDDE3E286 /* iTermProcessCacheTestHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iTermProcessCacheTestHelper.h; sourceTree = "<group>"; };
08829F633C36167ABC85693D /* iTermProcessCacheTestHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iTermProcessCacheTestHelper.m; sourceTree = "<group>"; };
537BFDCF2101AD9F0098C91F /* iTermCPUUtilization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iTermCPUUtilization.h; sourceTree = "<group>"; };
537BFDD02101AD9F0098C91F /* iTermCPUUtilization.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iTermCPUUtilization.m; sourceTree = "<group>"; };
537BFDD32101B2500098C91F /* iTermStatusBarCPUUtilizationComponent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iTermStatusBarCPUUtilizationComponent.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -12643,6 +12652,8 @@
A6A802E8226ED20800BC70DC /* iTermPrintGuard.m */,
532755832387821C00C50732 /* iTermProcessMonitor.h */,
532755842387821C00C50732 /* iTermProcessMonitor.m */,
D7A9E6EF2ABC123456789010 /* iTermProcessMonitor+Testing.h */,
D7A9E6F02ABC123456789011 /* iTermProcessMonitor+Testing.m */,
1D468BBF1B056CD600226083 /* iTermProfileSearchToken.m */,
A63493F623F2685A0047C31B /* iTermPromise.h */,
A63493F723F2685A0047C31B /* iTermPromise.m */,
Expand Down Expand Up @@ -14203,6 +14214,10 @@
children = (
537BFDC720FFB2590098C91F /* iTermProcessCache.h */,
537BFDC820FFB2590098C91F /* iTermProcessCache.m */,
C47650BBAFA6A247F2489C37 /* iTermProcessCache+Testing.h */,
42BC3DE2E88700AF9526F074 /* iTermProcessCache+Testing.m */,
0FB571F65DACC53FDDE3E286 /* iTermProcessCacheTestHelper.h */,
08829F633C36167ABC85693D /* iTermProcessCacheTestHelper.m */,
53AFFC8A1DD2A03800E6CEC6 /* iTermLSOF.h */,
53AFFC8B1DD2A03800E6CEC6 /* iTermLSOF.m */,
A6EEAEDD283712070039E850 /* ProcessDataSource.swift */,
Expand Down Expand Up @@ -18844,6 +18859,7 @@
A667C235276AB01F006B4DEF /* iTermMetalUnavailableReason.m in Sources */,
A6DB7BAC2CE52BB000EB7973 /* iTermPasteSpecialWindowController.m in Sources */,
532755862387821C00C50732 /* iTermProcessMonitor.m in Sources */,
D7A9E6F12ABC123456789012 /* iTermProcessMonitor+Testing.m in Sources */,
A6758565245AA23400827C25 /* iTermSwipeState.m in Sources */,
A667C266277A3B29006B4DEF /* PTYTriggerEvaluator.m in Sources */,
A6024B82254CE2000036D6CF /* iTermAddTriggerViewController.m in Sources */,
Expand Down Expand Up @@ -19638,6 +19654,8 @@
A6D3205529C29EE30088BBE1 /* NerdFontInstaller.swift in Sources */,
5308BF8A22828268004BECAC /* iTermStatusBarBatteryComponent.m in Sources */,
537BFDCA20FFB2590098C91F /* iTermProcessCache.m in Sources */,
BAB0C1C2780DA546AFE73B80 /* iTermProcessCache+Testing.m in Sources */,
2A1F4551EB4181AC933F6915 /* iTermProcessCacheTestHelper.m in Sources */,
A6F9EF3820850C41005530F7 /* iTermCompetentTableRowView.m in Sources */,
A6D10E3427F7C2BC0026DB56 /* NSRange+MultiCursor.swift in Sources */,
A65B1BD32D1E31CB00F5A740 /* iTermMetalView.swift in Sources */,
Expand Down
8 changes: 8 additions & 0 deletions sources/DebugLogging.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ extern BOOL gDebugLogging;
#define ITDebugAssert(condition) ((void)(condition))
#endif

// Assert that we're currently executing on the specified dispatch queue.
// Uses dispatch_assert_queue() which provides clear error messages.
#if ITERM_DEBUG
#define ITAssertOnQueue(queue) dispatch_assert_queue(queue)
#else
#define ITAssertOnQueue(queue) ((void)(queue))
#endif

#if !ITERM_DEBUG && USE_STOPWATCH
#define STOPWATCH_START(name) \
NSTimeInterval start_##name = [NSDate timeIntervalSinceReferenceDate]; \
Expand Down
22 changes: 22 additions & 0 deletions sources/PseudoTerminal.m
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
#import "iTermOrderEnforcer.h"
#import "iTermPasswordManagerWindowController.h"
#import "iTermPreferences.h"
#import "iTermProcessCache.h"
#import "iTermProfilePreferences.h"
#import "iTermProfilesWindowController.h"
#import "iTermPromptOnCloseReason.h"
Expand Down Expand Up @@ -4407,6 +4408,16 @@ - (void)windowDidBecomeKey:(NSNotification *)aNotification {
[aSession.view setNeedsDisplay:YES];
[aSession useTransparencyDidChange];
}

// Update process cache with foreground root PIDs for the current tab
NSMutableSet<NSNumber *> *foregroundPIDs = [NSMutableSet set];
for (PTYSession *session in [self.currentTab sessions]) {
pid_t pid = session.shell.pid;
if (pid > 0) {
[foregroundPIDs addObject:@(pid)];
}
}
[[iTermProcessCache sharedInstance] setForegroundRootPIDs:foregroundPIDs];
// Some users report that the first responder isn't always set properly. Let's try to fix that.
// This attempt (4/20/13) is to fix bug 2431.
if (!self.window.firstResponder.it_preferredFirstResponder) {
Expand Down Expand Up @@ -6704,6 +6715,17 @@ - (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabVi
[_contentView setCurrentSessionAlpha:self.currentSession.textview.transparencyAlpha];
[tab didSelectTab];
[[NSNotificationCenter defaultCenter] postNotificationName:iTermSelectedTabDidChange object:tab];

// Update process cache with foreground root PIDs for this window
NSMutableSet<NSNumber *> *foregroundPIDs = [NSMutableSet set];
for (PTYSession *session in [tab sessions]) {
pid_t pid = session.shell.pid;
if (pid > 0) {
[foregroundPIDs addObject:@(pid)];
}
}
[[iTermProcessCache sharedInstance] setForegroundRootPIDs:foregroundPIDs];

DLog(@"Finished");
}

Expand Down
2 changes: 2 additions & 0 deletions sources/iTerm2SharedARC-Bridging-Header.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@
#import "iTermPreciseTimer.h"
#import "iTermPreferences.h"
#import "iTermProcess.h"
#import "iTermProcessCacheTestHelper.h"
#import "iTermProcessMonitor.h"
#import "iTermProfilePreferencesBaseViewController.h"
#import "iTermPromise.h"
#import "iTermRemotePreferences.h"
Expand Down
1 change: 1 addition & 0 deletions sources/iTermLSOF.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ int iTermProcPidInfoWrapper(int pid, int flavor, uint64_t arg, void *buffer, in
+ (NSString *)commandForProcess:(pid_t)pid execName:(NSString **)execName;
+ (NSArray<NSNumber *> *)allPids;
+ (pid_t)ppidForPid:(pid_t)childPid;
+ (NSArray<NSNumber *> *)childPidsForPid:(pid_t)parentPid;
+ (NSString *)nameOfProcessWithPid:(pid_t)thePid isForeground:(BOOL *)isForeground;
+ (NSString *)workingDirectoryOfProcess:(pid_t)pid;
+ (void)asyncWorkingDirectoryOfProcess:(pid_t)pid
Expand Down
44 changes: 44 additions & 0 deletions sources/iTermLSOF.m
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,50 @@ + (pid_t)ppidForPid:(pid_t)childPid {
}
}

// Returns direct child PIDs for a given parent PID.
// Note: proc_listchildpids returns PID count (not bytes like proc_listpids).
+ (NSArray<NSNumber *> *)childPidsForPid:(pid_t)parentPid {
int count = proc_listchildpids(parentPid, NULL, 0);
if (count <= 0) {
return @[];
}

pid_t *pids = NULL;
int returnedCount = 0;

// Retry loop: child list can change between size query and fetch
for (int attempt = 0; attempt < 3; attempt++) {
int capacity = count + 16; // Headroom for new children
size_t bufferSize = capacity * sizeof(pid_t);
pids = (pid_t *)iTermMalloc(bufferSize);

returnedCount = proc_listchildpids(parentPid, pids, (int)bufferSize);
if (returnedCount < 0) {
free(pids);
return @[];
}
if (returnedCount <= capacity) {
break;
}
// Buffer too small, retry with larger capacity
free(pids);
pids = NULL;
count = returnedCount;
}

// If all retries exhausted (child count kept growing faster than we could allocate), bail out
if (pids == NULL) {
return @[];
}

NSMutableArray<NSNumber *> *result = [NSMutableArray arrayWithCapacity:returnedCount];
for (int i = 0; i < returnedCount; i++) {
[result addObject:@(pids[i])];
}
free(pids);
return result;
}

// Use sysctl magic to get the name of a process and whether it is controlling
// the tty. This code was adapted from ps, here:
// http://opensource.apple.com/source/adv_cmds/adv_cmds-138.1/ps/
Expand Down
40 changes: 40 additions & 0 deletions sources/iTermProcessCache+Testing.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// iTermProcessCache+Testing.h
// iTerm2SharedARC
//
// Testing-only interface for iTermProcessCache.
//

#import "iTermProcessCache.h"

NS_ASSUME_NONNULL_BEGIN

@interface iTermProcessCache (Testing)

/// Create a non-singleton instance for testing.
- (instancetype)initForTesting;

/// Number of roots in the dirty low-priority (background) set.
@property (nonatomic, readonly) NSUInteger dirtyLowRootsCount;

/// Number of roots in the dirty high-priority (foreground) set.
@property (nonatomic, readonly) NSUInteger dirtyHighRootsCount;

/// Whether a specific root PID is currently marked as high priority (foreground).
- (BOOL)isRootHighPriority:(pid_t)rootPID;

/// Whether a specific root PID exists in the tracking system.
- (BOOL)isTrackingRoot:(pid_t)rootPID;

/// Force a background refresh tick (normally called by timer).
- (void)forceBackgroundRefreshTick;

/// Register a root PID for tracking (testing only).
- (void)registerTestRoot:(pid_t)rootPID;

/// Unregister a root PID (testing only).
- (void)unregisterTestRoot:(pid_t)rootPID;

@end

NS_ASSUME_NONNULL_END
97 changes: 97 additions & 0 deletions sources/iTermProcessCache+Testing.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//
// iTermProcessCache+Testing.m
// iTerm2SharedARC
//
// Testing-only implementation for iTermProcessCache.
//

#import "iTermProcessCache+Testing.h"
#import "iTermProcessMonitor.h"

// Forward declare private ivars we need to access
@interface iTermProcessCache () {
@public
dispatch_queue_t _lockQueue;
dispatch_queue_t _workQueue;
NSMutableDictionary *_rootsLQ;
NSMutableIndexSet *_dirtyHighRootsLQ;
NSMutableIndexSet *_dirtyLowRootsLQ;
}
- (void)backgroundRefreshTick;
@end

// Forward declare iTermTrackedRootInfo for access
@interface iTermTrackedRootInfo : NSObject
@property (nonatomic) BOOL isHighPriority;
@property (nonatomic, strong, nullable) iTermProcessMonitor *monitor;
@property (nonatomic, strong) NSMutableIndexSet *cachedDescendants;
@property (nonatomic) NSUInteger lastRefreshEpoch;
@property (nonatomic) BOOL isDirty;
@end

@implementation iTermProcessCache (Testing)

- (instancetype)initForTesting {
return [self init];
}

- (NSUInteger)dirtyLowRootsCount {
__block NSUInteger count = 0;
dispatch_sync(_lockQueue, ^{
count = self->_dirtyLowRootsLQ.count;
});
return count;
}

- (NSUInteger)dirtyHighRootsCount {
__block NSUInteger count = 0;
dispatch_sync(_lockQueue, ^{
count = self->_dirtyHighRootsLQ.count;
});
return count;
}

- (BOOL)isRootHighPriority:(pid_t)rootPID {
__block BOOL result = NO;
dispatch_sync(_lockQueue, ^{
iTermTrackedRootInfo *info = self->_rootsLQ[@(rootPID)];
result = info.isHighPriority;
});
return result;
}

- (BOOL)isTrackingRoot:(pid_t)rootPID {
__block BOOL result = NO;
dispatch_sync(_lockQueue, ^{
result = self->_rootsLQ[@(rootPID)] != nil;
});
return result;
}

- (void)forceBackgroundRefreshTick {
[self backgroundRefreshTick];
}

- (void)registerTestRoot:(pid_t)rootPID {
dispatch_sync(_lockQueue, ^{
if (self->_rootsLQ[@(rootPID)] == nil) {
iTermTrackedRootInfo *info = [[iTermTrackedRootInfo alloc] init];
info.isHighPriority = YES; // Default to foreground
self->_rootsLQ[@(rootPID)] = info;
}
});
}

- (void)unregisterTestRoot:(pid_t)rootPID {
dispatch_sync(_lockQueue, ^{
iTermTrackedRootInfo *info = self->_rootsLQ[@(rootPID)];
if (info) {
[info.monitor invalidate];
[self->_rootsLQ removeObjectForKey:@(rootPID)];
[self->_dirtyHighRootsLQ removeIndex:rootPID];
[self->_dirtyLowRootsLQ removeIndex:rootPID];
}
});
}

@end
5 changes: 5 additions & 0 deletions sources/iTermProcessCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ NS_ASSUME_NONNULL_BEGIN
+ (instancetype)sharedInstance;
+ (iTermProcessCollection *)newProcessCollection;

// Update which root PIDs are foreground (high priority).
// Foreground roots get fast incremental updates via process monitors.
// Background roots have their monitors suspended and rely on the 0.5s cadence.
- (void)setForegroundRootPIDs:(NSSet<NSNumber *> *)foregroundPIDs;

@end

NS_ASSUME_NONNULL_END
Loading