Permalink
Browse files

License update, atomic explanation, extra check in KEVENT handler

  • Loading branch information...
1 parent 8b3004a commit 9e2523271b0a8b6bfb3fb904f10a021253311fd4 @bdkjones committed Jan 15, 2013
Showing with 123 additions and 84 deletions.
  1. +16 −12 README.md
  2. +42 −38 VDKQueue.h
  3. +65 −34 VDKQueue.m
View
28 README.md
@@ -19,7 +19,6 @@ Objective-C has come a long way in the past nine years and UKKQueue was long in
-- Grand Central Dispatch is used in place of Uli's "threadProxy" notifications (much faster)
-- Memory footprint is roughly halved, since VDKQueue creates less overhead
-- Fewer locks are taken, especially in loops (faster)
- -- The @autoreleasepool construct is used in place of alloc/init-ing an NSAutoReleasePool (much faster)
-- The code is *much* cleaner and simpler!
-- There is only one .h and one .m file to include.
@@ -44,22 +43,27 @@ All tests conducted on a 2008 MacBook Pro 2.5Ghz with 4GB of RAM running OS 10.7
requirements
------------
-As published, VDKQueue requires that you use Xcode 4.2+ and link against the 10.7 framework (for the @autoreleasepool language feature).
-However, if you want to use VDKQueue on 10.6, you can simply replace the @autoreleasepool with an alloc/init-ed NSAutoReleasePool. If you do,
-VDKQueue will work just fine on 10.6+
+VDKQueue requires Mac OS X 10.6+ because it uses Grand Central Dispatch.
-VDKQueue does not support garbage collection. If you use garbage collection, you are lazy. Shape up. (Also, GC is deprecated in OS 10.8.)
-VDKQueue does not currently use ARC (automatic reference counting), although it should be straightforward to convert if you wish.
+VDKQueue does not support garbage collection. If you use garbage collection, you are lazy. Shape up.
+
+VDKQueue does not currently use ARC, although it should be straightforward to convert if you wish. (Don't be the guy that can't manually manage memory, though.)
license
-------
-Copyright (c) 2012 Bryan D K Jones.
-You are free to use, modify and redistribute this software subject to these conditions:
- 1) I am not liable for anything that happens to you if you use this software --- including if it becomes sentient and eats your grandmother.
- 2) You keep this notice in your derivative work.
- 3) You keep Uli Kusterer's original copyright notice as well (this notice appears at the bottom of the header file.)
- 4) You are awesome.
+Created by Bryan D K Jones on 28 March 2012
+Copyright 2013 Bryan D K Jones
+
+Based heavily on UKKQueue, which was created and copyrighted by Uli Kusterer on 21 Dec 2003.
+
+This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source distribution.
View
80 VDKQueue.h
@@ -1,24 +1,31 @@
+// VDKQueue.h
+// Created by Bryan D K Jones on 28 March 2012
+// Copyright 2013 Bryan D K Jones
//
-// VDKQueue.h
+// Based heavily on UKKQueue, which was created and copyrighted by Uli Kusterer on 21 Dec 2003.
//
-// Created by Bryan Jones on 28 March 2012.
-// Copyright (c) 2012 Bryan D K Jones.
-// You are free to use, modify and redistribute this software subject to these conditions:
-// 1) I am not liable for anything that happens to you if you use this software --- including if it becomes sentient and eats your grandmother.
-// 2) You keep this notice in your derivative work.
-// 3) You keep Uli Kusterer's original copyright notice as well (this notice appears at the bottom of this file.)
-// 4) You are awesome.
-//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source
+// distribution.
//
// BASED ON UKKQUEUE:
//
// This is an updated, modernized and streamlined version of the excellent UKKQueue class, which was authored by Uli Kusterer.
// UKKQueue was written back in 2003 and there have been many, many improvements to Objective-C since then. VDKQueue uses the
// core of Uli's original class, but makes it faster and more efficient. Method calls are reduced. Grand Central Dispatch is used in place
-// of Uli's "threadProxy" objects. The new @autoreleasepool is used instead of alloc/initing a pool (which is much slower). The memory footprint
-// is roughly halved, as I don't create the overhead that UKKQueue does. I take fewer locks, don't depend on notifications to get back to the main thread, and
-// use modern language constructs to MASSIVELY speed up event processing compared to the original UKKQueue class.
+// of Uli's "threadProxy" objects. The memory footprint is roughly halved, as I don't create the overhead that UKKQueue does.
//
// VDKQueue is also simplified. The option to use it as a singleton is removed. You simply alloc/init an instance and add paths you want to
// watch. Your objects can be alerted to changes either by notifications or by a delegate method (or both). See below.
@@ -30,10 +37,29 @@
//
// DEPENDENCIES:
//
-// VDKQueue requires OS 10.7.0+ because it relies on the @autoreleasepool language addition. If you wish to use the class on 10.6, you can
-// simply replace the @autoreleasepool construct with an alloc/init-ed NSAutoReleasePool instance instead. The class will not work on versions of
-// OS X below 10.6, as it requires blocks and Grand Central Dispatch.
+// VDKQueue requires OS 10.6+ because it relies on Grand Central Dispatch.
+//
+
+//
+// IMPORTANT NOTE ABOUT ATOMIC OPERATIONS
+//
+// There are two ways of saving a file on OS X: Atomic and Non-Atomic. In a non-atomic operation, a file is saved by directly overwriting it with new data.
+// In an Atomic save, a temporary file is first written to a different location on disk. When that completes successfully, the original file is deleted and the
+// temporary one is renamed and moved into place where the original file existed.
+//
+// This matters a great deal. If you tell VDKQueue to watch file X, then you save file X ATOMICALLY, you'll receive a notification about that event. HOWEVER, you will
+// NOT receive any additional notifications for file X from then on. This is because the atomic operation has essentially created a new file that replaced the one you
+// told VDKQueue to watch. (This is not an issue for non-atomic operations.)
+//
+// To handle this, any time you receive a change notification from VDKQueue, you should call -removePath: followed by -addPath: on the file's path, even if the path
+// has not changed. This will ensure that if the event that triggered the notification was an atomic operation, VDKQueue will start watching the "new" file that took
+// the place of the old one.
//
+// Other frameworks out there try to work around this issue by immediately attempting to re-open the file descriptor to the path. This is not bulletproof and may fail;
+// it all depends on the timing of disk I/O. Bottom line: you could not rely on it and might miss future changes to the file path you're supposedly watching. That's why
+// VDKQueue does not take this approach, but favors the "manual" method of "stop-watching-then-rewatch".
+//
+
#import <Foundation/Foundation.h>
@@ -121,26 +147,4 @@ extern NSString * VDKQueueAccessRevocationNotification;
@property (assign) id<VDKQueueDelegate> delegate;
@property (assign) BOOL alwaysPostNotifications;
-@end
-
-
-
-
-// This is the original copyright header that shipped with UKKQueue, on which VDKQueue is based:
-// UKKQueue.m
-// Created by Uli Kusterer on 21.12.2003
-// Copyright 2003 Uli Kusterer.
-// This software is provided 'as-is', without any express or implied
-// warranty. In no event will the authors be held liable for any damages
-// arising from the use of this software.
-// Permission is granted to anyone to use this software for any purpose,
-// including commercial applications, and to alter it and redistribute it
-// freely, subject to the following restrictions:
-// 1. The origin of this software must not be misrepresented; you must not
-// claim that you wrote the original software. If you use this software
-// in a product, an acknowledgment in the product documentation would be
-// appreciated but is not required.
-// 2. Altered source versions must be plainly marked as such, and must not be
-// misrepresented as being the original software.
-// 3. This notice may not be removed or altered from any source
-// distribution.
+@end
View
99 VDKQueue.m
@@ -1,11 +1,23 @@
+// VDKQueue.m
+// Created by Bryan D K Jones on 28 March 2012
+// Copyright 2013 Bryan D K Jones
//
-// VDKQueue.m
-//
-// Created by Bryan Jones on 28 March 2012.
-// Copyright (c) 2012 Bryan D K Jones.
-//
-// (See the header file for full copyright and usage information.)
+// Based heavily on UKKQueue, which was created and copyrighted by Uli Kusterer on 21 Dec 2003.
//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source
+// distribution.
#import "VDKQueue.h"
#import <unistd.h>
@@ -27,8 +39,7 @@
#pragma mark -
#pragma mark VDKQueuePathEntry
#pragma mark -
-#pragma ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
-#pragma ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
+#pragma ------------------------------------------------------------------------------------------------------------------------------------------------------------
// This is a simple model class used to hold info about each path we watch.
@interface VDKQueuePathEntry : NSObject
@@ -46,7 +57,6 @@ - (id) initWithPath:(NSString*)inPath andSubscriptionFlags:(u_int)flags;
@end
-
@implementation VDKQueuePathEntry
@synthesize path = _path, watchedFD = _watchedFD, subscriptionFlags = _subscriptionFlags;
@@ -94,8 +104,7 @@ -(void) dealloc
#pragma mark -
#pragma mark VDKQueue
#pragma mark -
-#pragma ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
-#pragma ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
+#pragma ------------------------------------------------------------------------------------------------------------------------------------------------------------
@interface VDKQueue ()
- (void) watcherThread:(id)sender;
@@ -201,7 +210,11 @@ - (VDKQueuePathEntry *) addPathToQueue:(NSString *)path notifyingAbout:(u_int)fl
}
-- (void) watcherThread:(id)sender;
+//
+// WARNING: This thread has no active autorelease pool, so if you make changes, you must manually manage
+// memory without relying on autorelease. Otherwise, you will leak!
+//
+- (void) watcherThread:(id)sender
{
int n;
struct kevent ev;
@@ -216,29 +229,38 @@ - (void) watcherThread:(id)sender;
while(_keepWatcherThreadRunning)
{
- @autoreleasepool
+ @try
{
- @try
+ n = kevent(theFD, NULL, 0, &ev, 1, &timeout);
+ if (n > 0)
{
- n = kevent(theFD, NULL, 0, &ev, 1, &timeout);
- if (n > 0)
+ //NSLog( @"KEVENT returned %d", n );
+ if (ev.filter == EVFILT_VNODE)
{
- //NSLog( @"KEVENT returned %d", n );
- if (ev.filter == EVFILT_VNODE)
+ //NSLog( @"KEVENT filter is EVFILT_VNODE" );
+ if (ev.fflags)
{
- //NSLog( @"KEVENT filter is EVFILT_VNODE" );
- if (ev.fflags)
+ //NSLog( @"KEVENT flags are set" );
+
+ //
+ // Note: VDKQueue gets tested by thousands of CodeKit users who each watch several thousand files at once.
+ // I was receiving about 3 EXC_BAD_ACCESS (SIGSEGV) crash reports a month that listed the 'path' objc_msgSend
+ // as the culprit. That suggests the KEVENT is being sent back to us with a udata value that is NOT what we assigned
+ // to the queue, though I don't know why and I don't know why it's intermittent. Regardless, I've added an extra
+ // check here to try to eliminate this (infrequent) problem. In theory, a KEVENT that does not have a VDKQueuePathEntry
+ // object attached as the udata parameter is not an event we registered for, so we should not be "missing" any events. In theory.
+ //
+ id pe = ev.udata;
+ if (pe && [pe respondsToSelector:@selector(path)])
{
- //NSLog( @"KEVENT flags are set" );
- VDKQueuePathEntry *pe = [[(VDKQueuePathEntry*)ev.udata retain] autorelease]; // In case one of the notified folks removes the path.
- NSString *fpath = [pe path];
- [[NSWorkspace sharedWorkspace] noteFileSystemChanged:fpath];
+ NSString *fpath = [((VDKQueuePathEntry *)pe).path retain]; // Need to retain so it does not disappear while the block at the bottom is waiting to run on the main thread. Released in that block.
+ if (!fpath) continue;
+ [[NSWorkspace sharedWorkspace] noteFileSystemChanged:fpath];
// Clear any old notifications
[notesToPost removeAllObjects];
-
// Figure out which notifications we need to issue
if ((ev.fflags & NOTE_RENAME) == NOTE_RENAME)
{
@@ -270,21 +292,21 @@ - (void) watcherThread:(id)sender;
}
- [fpath retain]; // Need to retain so it does not disappear while the block below is waiting to run on the main thread.
NSArray *notes = [[NSArray alloc] initWithArray:notesToPost]; // notesToPost will be changed in the next loop iteration, which will likely occur before the block below runs.
// Post the notifications (or call the delegate method) on the main thread.
- dispatch_async(dispatch_get_main_queue(),
+ dispatch_async(dispatch_get_main_queue(),
^{
for (NSString *note in notes)
{
[_delegate VDKQueue:self receivedNotification:note forPath:fpath];
if (!_delegate || _alwaysPostNotifications)
{
- NSDictionary *userInfoDict = [NSDictionary dictionaryWithObject:fpath forKey:@"path"];
+ NSDictionary *userInfoDict = [[NSDictionary alloc] initWithObjectsAndKeys:fpath, @"path", nil];
[[[NSWorkspace sharedWorkspace] notificationCenter] postNotificationName:note object:self userInfo:userInfoDict];
+ [userInfoDict release];
}
}
@@ -295,11 +317,11 @@ - (void) watcherThread:(id)sender;
}
}
}
-
- @catch (NSException *localException)
- {
- NSLog(@"Error in VDKQueue watcherThread: %@", localException);
- }
+ }
+
+ @catch (NSException *localException)
+ {
+ NSLog(@"Error in VDKQueue watcherThread: %@", localException);
}
}
@@ -323,6 +345,8 @@ - (void) watcherThread:(id)sender;
#pragma mark -
#pragma mark PUBLIC METHODS
+#pragma -----------------------------------------------------------------------------------------------------------------------------------------------------
+
- (void) addPath:(NSString *)aPath
{
@@ -400,7 +424,14 @@ - (void) removeAllPaths
- (NSUInteger) numberOfWatchedPaths
{
- return [_watchedPathEntries count];
+ NSUInteger count;
+
+ @synchronized(self)
+ {
+ count = [_watchedPathEntries count];
+ }
+
+ return count;
}

0 comments on commit 9e25232

Please sign in to comment.