Permalink
Browse files

Avoid race condition registering filters on startup

It was possible for a persistent push replication to start running on  startup before the app had a chance to install filter blocks.
This meant the replication would ignore the filter and send all documents, which is pretty bad.

* Changed TDPusher to make a missing filter function an error.
* Changed TD_Server to wait till the next runloop iteration to start the replicator, giving the caller time to set up filters and validations.
Fixes #193.
  • Loading branch information...
1 parent c2e9855 commit 05632d581a2640c63040c7b19e1897c4464426fb @snej snej committed Dec 3, 2012
Showing with 41 additions and 11 deletions.
  1. +27 −7 Source/TDPusher.m
  2. +1 −0 Source/TD_DatabaseManager.m
  3. +13 −4 Source/TD_Server.m
View
@@ -44,7 +44,20 @@ - (BOOL) isPush {
- (TD_FilterBlock) filter {
- return _filterName ? [_db filterNamed: _filterName] : NULL;
+ if (!_filterName)
+ return NULL;
+ TD_FilterBlock filter = [_db filterNamed: _filterName];
+ if (!filter) {
+ Warn(@"%@: No TDFilterBlock registered for filter '%@'", self, _filterName);
+ if (!_error) {
+ NSDictionary* info = $dict({NSLocalizedFailureReasonErrorKey, @"Unknown filter"});
+ self.error = [NSError errorWithDomain: TDHTTPErrorDomain
+ code: kTDStatusNotFound
+ userInfo: info];
+ }
+ [self stop];
+ }
+ return filter;
}
@@ -77,9 +90,12 @@ - (void) beginReplicating {
if (_creatingTarget)
return;
- TD_FilterBlock filter = self.filter;
- if (!filter && _filterName)
- Warn(@"%@: No TDFilterBlock registered for filter '%@'; ignoring", self, _filterName);
+ TD_FilterBlock filter = NULL;
+ if (_filterName) {
+ filter = self.filter;
+ if (!filter)
+ return; // missing filter block
+ }
// Include conflicts so all conflicting revisions are replicated too
TDChangesOptions options = kDefaultTDChangesOptions;
@@ -145,9 +161,13 @@ - (void) dbChanged: (NSNotification*)n {
if ([userInfo[@"source"] isEqual: _remote])
return;
TD_Revision* rev = userInfo[@"rev"];
- TD_FilterBlock filter = self.filter;
- if (filter && !filter(rev, _filterParameters))
- return;
+
+ if (_filterName) {
+ TD_FilterBlock filter = self.filter;
+ if (!filter || !filter(rev, _filterParameters))
+ return;
+ }
+
[self addToInbox: rev];
}
@@ -185,6 +185,7 @@ - (void) close {
- (TDReplicatorManager*) replicatorManager {
if (!_replicatorManager && !_options.noReplicator) {
+ LogTo(TD_Server, @"Starting replicator manager for %@", self);
_replicatorManager = [[TDReplicatorManager alloc] initWithDatabaseManager: self];
[_replicatorManager start];
}
View
@@ -61,6 +61,18 @@ - (id) initWithDirectory: (NSString*)dirPath
object: nil];
LogTo(TD_Server, @"Starting server thread %@ ...", _serverThread);
[_serverThread start];
+
+ // Don't start the replicator immediately; instead, give the app a chance to install
+ // filter and validation functions, otherwise persistent replications may behave
+ // incorrectly. The delayed-perform means the replicator won't start until after
+ // the caller (and its caller, etc.) returns back to the runloop.
+ MYAfterDelay(0.0, ^{
+ if (_serverThread) {
+ [self queue: ^{
+ [_manager replicatorManager];
+ }];
+ }
+ });
}
return self;
}
@@ -110,10 +122,7 @@ - (void) runServerThread {
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);
#endif
-
- // Initialize the replicator, if it's enabled:
- [_manager replicatorManager];
-
+
// Now run:
while (!_stopRunLoop && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
beforeDate: [NSDate distantFuture]])

0 comments on commit 05632d5

Please sign in to comment.