Skip to content

Commit 4af0422

Browse files
authored
Merge pull request #85 from tiennou/fix/fs-events-leaks
Fix fsevents leaks
2 parents b514f06 + 8fed755 commit 4af0422

File tree

5 files changed

+120
-168
lines changed

5 files changed

+120
-168
lines changed

Classes/git/PBGitRepositoryWatcher.h

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,41 +9,30 @@
99
//
1010

1111
#import <Foundation/Foundation.h>
12+
13+
NS_ASSUME_NONNULL_BEGIN
14+
1215
@class PBGitRepository;
1316

14-
typedef UInt32 PBGitRepositoryWatcherEventType;
15-
enum {
16-
PBGitRepositoryWatcherEventTypeNone = 0x00000000,
17-
PBGitRepositoryWatcherEventTypeGitDirectory = 0x00000001,
18-
PBGitRepositoryWatcherEventTypeWorkingDirectory = 0x00000002,
19-
PBGitRepositoryWatcherEventTypeIndex = 0x00000004
17+
typedef NS_ENUM(NSUInteger, PBGitRepositoryWatcherEventType) {
18+
PBGitRepositoryWatcherEventTypeNone = (1 << 0),
19+
PBGitRepositoryWatcherEventTypeGitDirectory = (1 << 1),
20+
PBGitRepositoryWatcherEventTypeWorkingDirectory = (1 << 2),
21+
PBGitRepositoryWatcherEventTypeIndex = (1 << 3),
2022
};
2123

2224
extern NSString *PBGitRepositoryEventNotification;
2325
extern NSString *kPBGitRepositoryEventTypeUserInfoKey;
2426
extern NSString *kPBGitRepositoryEventPathsUserInfoKey;
2527

26-
typedef void(^PBGitRepositoryWatcherCallbackBlock)(NSArray *changedFiles);
27-
28-
@interface PBGitRepositoryWatcher : NSObject {
29-
FSEventStreamRef gitDirEventStream;
30-
FSEventStreamRef workDirEventStream;
31-
PBGitRepositoryWatcherCallbackBlock gitDirChangedBlock;
32-
PBGitRepositoryWatcherCallbackBlock workDirChangedBlock;
33-
NSDate *gitDirTouchDate;
34-
NSDate *indexTouchDate;
35-
36-
NSString *gitDir;
37-
NSString *workDir;
38-
39-
__strong PBGitRepositoryWatcher* ownRef;
40-
BOOL _running;
41-
}
28+
@interface PBGitRepositoryWatcher : NSObject
4229

4330
@property (readonly, weak) PBGitRepository *repository;
4431

45-
- (id) initWithRepository:(PBGitRepository *)repository;
32+
- (instancetype) initWithRepository:(PBGitRepository *)repository;
4633
- (void) start;
4734
- (void) stop;
4835

4936
@end
37+
38+
NS_ASSUME_NONNULL_END

Classes/git/PBGitRepositoryWatcher.m

Lines changed: 108 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,32 @@
1111
#import "PBGitRepository.h"
1212
#import "PBEasyPipe.h"
1313
#import "PBGitDefaults.h"
14-
#import "PBGitRepositoryWatcherEventPath.h"
1514

1615
NSString *PBGitRepositoryEventNotification = @"PBGitRepositoryModifiedNotification";
1716
NSString *kPBGitRepositoryEventTypeUserInfoKey = @"kPBGitRepositoryEventTypeUserInfoKey";
1817
NSString *kPBGitRepositoryEventPathsUserInfoKey = @"kPBGitRepositoryEventPathsUserInfoKey";
1918

20-
@interface PBGitRepositoryWatcher ()
19+
typedef void (^PBGitRepositoryWatcherCallbackBlock)(NSArray *changedFiles);
20+
21+
/* Small helper class to keep track of events */
22+
@interface PBGitRepositoryWatcherEventPath : NSObject
23+
@property NSString *path;
24+
@property (assign) FSEventStreamEventFlags flag;
25+
@end
26+
27+
@implementation PBGitRepositoryWatcherEventPath
28+
@end
29+
30+
@interface PBGitRepositoryWatcher () {
31+
FSEventStreamRef eventStream;
32+
NSDate *gitDirTouchDate;
33+
NSDate *indexTouchDate;
34+
35+
BOOL _running;
36+
}
37+
38+
@property (readonly) NSString *gitDir;
39+
@property (readonly) NSString *workDir;
2140

2241
@property (nonatomic, strong) NSMutableDictionary *statusCache;
2342

@@ -32,101 +51,63 @@ void PBGitRepositoryWatcherCallback(ConstFSEventStreamRef streamRef,
3251
void *_eventPaths,
3352
const FSEventStreamEventFlags eventFlags[],
3453
const FSEventStreamEventId eventIds[]){
35-
PBGitRepositoryWatcherCallbackBlock block = (__bridge PBGitRepositoryWatcherCallbackBlock)clientCallBackInfo;
36-
37-
NSMutableArray *changePaths = [[NSMutableArray alloc] init];
54+
PBGitRepositoryWatcher *watcher = (__bridge PBGitRepositoryWatcher *)clientCallBackInfo;
55+
56+
NSMutableArray *gitDirEvents = [NSMutableArray array];
57+
NSMutableArray *workDirEvents = [NSMutableArray array];
3858
NSArray *eventPaths = (__bridge NSArray*)_eventPaths;
3959
for (int i = 0; i < numEvents; ++i) {
4060
NSString *path = [eventPaths objectAtIndex:i];
4161
PBGitRepositoryWatcherEventPath *ep = [[PBGitRepositoryWatcherEventPath alloc] init];
4262
ep.path = [path stringByStandardizingPath];
4363
ep.flag = eventFlags[i];
44-
[changePaths addObject:ep];
4564

65+
66+
if ([ep.path hasPrefix:watcher.gitDir]) {
67+
// exclude all changes to .lock files
68+
if ([ep.path hasSuffix:@".lock"]) {
69+
continue;
70+
}
71+
[gitDirEvents addObject:ep];
72+
} else if ([ep.path hasPrefix:watcher.workDir]) {
73+
[workDirEvents addObject:ep];
74+
}
4675
}
47-
if (block && changePaths.count) {
48-
block(changePaths);
76+
77+
if (workDirEvents.count) {
78+
[watcher handleWorkDirEventCallback:workDirEvents];
79+
}
80+
if (gitDirEvents.count) {
81+
[watcher handleGitDirEventCallback:gitDirEvents];
4982
}
5083
}
5184

5285
@implementation PBGitRepositoryWatcher
5386

54-
@synthesize repository;
87+
- (instancetype) initWithRepository:(PBGitRepository *)theRepository {
88+
NSParameterAssert(theRepository != nil);
5589

56-
- (id) initWithRepository:(PBGitRepository *)theRepository {
5790
self = [super init];
5891
if (!self) {
5992
return nil;
6093
}
61-
62-
__weak PBGitRepositoryWatcher* weakSelf = self;
63-
repository = theRepository;
64-
65-
{
66-
gitDir = [repository.gtRepo.gitDirectoryURL.path stringByStandardizingPath];
67-
if (!gitDir) {
68-
return nil;
69-
}
70-
gitDirChangedBlock = ^(NSArray *changeEvents){
71-
NSMutableArray *filteredEvents = [NSMutableArray new];
72-
for (PBGitRepositoryWatcherEventPath *event in changeEvents) {
73-
// exclude all changes to .lock files
74-
if ([event.path hasSuffix:@".lock"]) {
75-
continue;
76-
}
77-
[filteredEvents addObject:event];
78-
}
79-
if (filteredEvents.count) {
80-
[weakSelf handleGitDirEventCallback:filteredEvents];
81-
}
82-
};
83-
FSEventStreamContext gitDirWatcherContext = {0, (__bridge void *)(gitDirChangedBlock), NULL, NULL, NULL};
84-
gitDirEventStream = FSEventStreamCreate(kCFAllocatorDefault, PBGitRepositoryWatcherCallback, &gitDirWatcherContext,
85-
(__bridge CFArrayRef)@[gitDir],
86-
kFSEventStreamEventIdSinceNow, 1.0,
87-
kFSEventStreamCreateFlagUseCFTypes |
88-
kFSEventStreamCreateFlagIgnoreSelf |
89-
kFSEventStreamCreateFlagFileEvents);
90-
91-
}
92-
{
93-
workDir = repository.gtRepo.isBare ? nil : [repository.gtRepo.fileURL.path stringByStandardizingPath];
94-
if (workDir) {
95-
workDirChangedBlock = ^(NSArray *changeEvents){
96-
NSMutableArray *filteredEvents = [NSMutableArray new];
97-
PBGitRepositoryWatcher *watcher = weakSelf;
98-
if (!watcher) {
99-
return;
100-
}
101-
for (PBGitRepositoryWatcherEventPath *event in changeEvents) {
102-
// exclude anything under the .git dir
103-
if ([event.path hasPrefix:watcher->gitDir]) {
104-
continue;
105-
}
106-
[filteredEvents addObject:event];
107-
}
108-
if (filteredEvents.count) {
109-
[watcher handleWorkDirEventCallback:filteredEvents];
110-
}
111-
};
112-
FSEventStreamContext workDirWatcherContext = {0, (__bridge void *)(workDirChangedBlock), NULL, NULL, NULL};
113-
workDirEventStream = FSEventStreamCreate(kCFAllocatorDefault, PBGitRepositoryWatcherCallback, &workDirWatcherContext,
114-
(__bridge CFArrayRef)@[workDir],
115-
kFSEventStreamEventIdSinceNow, 1.0,
116-
kFSEventStreamCreateFlagUseCFTypes |
117-
kFSEventStreamCreateFlagIgnoreSelf |
118-
kFSEventStreamCreateFlagFileEvents);
119-
}
120-
}
121-
12294

123-
self.statusCache = [NSMutableDictionary new];
95+
_repository = theRepository;
96+
_statusCache = [NSMutableDictionary new];
12497

12598
if ([PBGitDefaults useRepositoryWatcher])
12699
[self start];
127100
return self;
128101
}
129102

103+
- (void)dealloc {
104+
if (eventStream) {
105+
FSEventStreamStop(eventStream);
106+
FSEventStreamInvalidate(eventStream);
107+
FSEventStreamRelease(eventStream);
108+
}
109+
}
110+
130111
- (NSDate *) fileModificationDateAtPath:(NSString *)path {
131112
NSError* error;
132113
NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:path
@@ -144,7 +125,7 @@ - (BOOL) indexChanged {
144125
return NO;
145126
}
146127

147-
NSDate *newTouchDate = [self fileModificationDateAtPath:[gitDir stringByAppendingPathComponent:@"index"]];
128+
NSDate *newTouchDate = [self fileModificationDateAtPath:[self.gitDir stringByAppendingPathComponent:@"index"]];
148129
if (![newTouchDate isEqual:indexTouchDate]) {
149130
indexTouchDate = newTouchDate;
150131
return YES;
@@ -155,18 +136,19 @@ - (BOOL) indexChanged {
155136

156137
- (BOOL) gitDirectoryChanged {
157138

158-
for (NSURL* fileURL in [[NSFileManager defaultManager] contentsOfDirectoryAtURL:repository.gitURL
159-
includingPropertiesForKeys:[NSArray arrayWithObject:NSURLContentModificationDateKey]
160-
options:0
161-
162-
error:nil])
139+
NSArray *properties = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey];
140+
NSArray <NSURL *> *urls = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:self.repository.gitURL
141+
includingPropertiesForKeys:properties
142+
options:0
143+
error:nil];
144+
for (NSURL *fileURL in urls)
163145
{
164-
BOOL isDirectory = NO;
165-
[[NSFileManager defaultManager] fileExistsAtPath:[fileURL path] isDirectory:&isDirectory];
166-
if (isDirectory)
146+
NSNumber *number = nil;
147+
if (![fileURL getResourceValue:&number forKey:NSURLIsDirectoryKey error:nil] || [number boolValue]) {
167148
continue;
149+
}
168150

169-
NSDate* modTime = nil;
151+
NSDate *modTime = nil;
170152
if (![fileURL getResourceValue:&modTime forKey:NSURLContentModificationDateKey error:nil])
171153
continue;
172154

@@ -193,22 +175,22 @@ - (void) handleGitDirEventCallback:(NSArray *)eventPaths
193175
NSMutableArray *paths = [NSMutableArray array];
194176
for (PBGitRepositoryWatcherEventPath *eventPath in eventPaths) {
195177
// .git dir
196-
if ([eventPath.path isEqualToString:gitDir]) {
178+
if ([eventPath.path isEqualToString:self.gitDir]) {
197179
if ([self gitDirectoryChanged] || eventPath.flag != kFSEventStreamEventFlagNone) {
198180
event |= PBGitRepositoryWatcherEventTypeGitDirectory;
199181
[paths addObject:eventPath.path];
200182
}
201183
}
202184
// ignore objects dir ... ?
203-
else if ([eventPath.path rangeOfString:[gitDir stringByAppendingPathComponent:@"objects"]].location != NSNotFound) {
185+
else if ([eventPath.path rangeOfString:[self.gitDir stringByAppendingPathComponent:@"objects"]].location != NSNotFound) {
204186
continue;
205187
}
206188
// index is already covered
207-
else if ([eventPath.path rangeOfString:[gitDir stringByAppendingPathComponent:@"index"]].location != NSNotFound) {
189+
else if ([eventPath.path rangeOfString:[self.gitDir stringByAppendingPathComponent:@"index"]].location != NSNotFound) {
208190
continue;
209191
}
210192
// subdirs of .git dir
211-
else if ([eventPath.path rangeOfString:gitDir].location != NSNotFound) {
193+
else if ([eventPath.path rangeOfString:self.gitDir].location != NSNotFound) {
212194
event |= PBGitRepositoryWatcherEventTypeGitDirectory;
213195
[paths addObject:eventPath.path];
214196
}
@@ -218,7 +200,7 @@ - (void) handleGitDirEventCallback:(NSArray *)eventPaths
218200
NSDictionary *eventInfo = @{kPBGitRepositoryEventTypeUserInfoKey:@(event),
219201
kPBGitRepositoryEventPathsUserInfoKey:paths};
220202

221-
[[NSNotificationCenter defaultCenter] postNotificationName:PBGitRepositoryEventNotification object:repository userInfo:eventInfo];
203+
[[NSNotificationCenter defaultCenter] postNotificationName:PBGitRepositoryEventNotification object:self.repository userInfo:eventInfo];
222204
}
223205
}
224206

@@ -229,15 +211,15 @@ - (void)handleWorkDirEventCallback:(NSArray *)eventPaths
229211
NSMutableArray *paths = [NSMutableArray array];
230212
for (PBGitRepositoryWatcherEventPath *eventPath in eventPaths) {
231213
unsigned int fileStatus = 0;
232-
if (![eventPath.path hasPrefix:workDir]) {
214+
if (![eventPath.path hasPrefix:self.workDir]) {
233215
continue;
234216
}
235-
if ([eventPath.path isEqualToString:workDir]) {
217+
if ([eventPath.path isEqualToString:self.workDir]) {
236218
event |= PBGitRepositoryWatcherEventTypeWorkingDirectory;
237219
[paths addObject:eventPath.path];
238220
continue;
239221
}
240-
NSString *eventRepoRelativePath = [eventPath.path substringFromIndex:(workDir.length + 1)];
222+
NSString *eventRepoRelativePath = [eventPath.path substringFromIndex:(self.workDir.length + 1)];
241223
int ignoreResult = 0;
242224
int ignoreError = git_status_should_ignore(&ignoreResult, self.repository.gtRepo.git_repository, eventRepoRelativePath.UTF8String);
243225
if (ignoreError == GIT_OK && ignoreResult) {
@@ -262,25 +244,49 @@ - (void)handleWorkDirEventCallback:(NSArray *)eventPaths
262244
NSDictionary *eventInfo = @{kPBGitRepositoryEventTypeUserInfoKey:@(event),
263245
kPBGitRepositoryEventPathsUserInfoKey:paths};
264246

265-
[[NSNotificationCenter defaultCenter] postNotificationName:PBGitRepositoryEventNotification object:repository userInfo:eventInfo];
247+
[[NSNotificationCenter defaultCenter] postNotificationName:PBGitRepositoryEventNotification object:self.repository userInfo:eventInfo];
266248
}
267249
}
268250

251+
- (NSString *)gitDir {
252+
return [self.repository.gtRepo.gitDirectoryURL.path stringByStandardizingPath];
253+
}
254+
255+
- (NSString *)workDir {
256+
return !self.repository.gtRepo.isBare ? [self.repository.gtRepo.fileURL.path stringByStandardizingPath] : nil;
257+
}
258+
259+
- (void) _initializeStream {
260+
if (eventStream) return;
261+
262+
NSMutableArray *array = [NSMutableArray array];
263+
if (self.gitDir) [array addObject:self.gitDir];
264+
if (self.workDir) [array addObject:self.workDir];
265+
266+
if (!array.count) return;
267+
268+
FSEventStreamContext gitDirWatcherContext = {0, (__bridge void *)(self), NULL, NULL, NULL};
269+
eventStream = FSEventStreamCreate(kCFAllocatorDefault, PBGitRepositoryWatcherCallback, &gitDirWatcherContext,
270+
(__bridge CFArrayRef)array,
271+
kFSEventStreamEventIdSinceNow, 1.0,
272+
kFSEventStreamCreateFlagUseCFTypes |
273+
kFSEventStreamCreateFlagIgnoreSelf |
274+
kFSEventStreamCreateFlagFileEvents);
275+
}
276+
269277
- (void) start {
270278
if (_running)
271279
return;
272280

273281
// set initial state
274282
[self gitDirectoryChanged];
275283
[self indexChanged];
276-
ownRef = self; // The callback has no reference to us, so we need to stay alive as long as it may be called
277-
FSEventStreamScheduleWithRunLoop(gitDirEventStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
278-
FSEventStreamStart(gitDirEventStream);
284+
[self _initializeStream];
279285

280-
if (workDirEventStream) {
281-
FSEventStreamScheduleWithRunLoop(workDirEventStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
282-
FSEventStreamStart(workDirEventStream);
283-
}
286+
if (!eventStream) return;
287+
288+
FSEventStreamScheduleWithRunLoop(eventStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
289+
FSEventStreamStart(eventStream);
284290

285291
_running = YES;
286292
}
@@ -289,13 +295,11 @@ - (void) stop {
289295
if (!_running)
290296
return;
291297

292-
if (workDirEventStream) {
293-
FSEventStreamStop(workDirEventStream);
294-
FSEventStreamUnscheduleFromRunLoop(workDirEventStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
298+
if (eventStream) {
299+
FSEventStreamStop(eventStream);
300+
FSEventStreamUnscheduleFromRunLoop(eventStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
295301
}
296-
FSEventStreamStop(gitDirEventStream);
297-
FSEventStreamUnscheduleFromRunLoop(gitDirEventStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
298-
ownRef = nil; // Now that we can't be called anymore, we can allow ourself to be -dealloc'd
302+
299303
_running = NO;
300304
}
301305

0 commit comments

Comments
 (0)