Skip to content
This repository has been archived by the owner on Mar 9, 2022. It is now read-only.

Commit

Permalink
Fixed a bunch of memory leaks and refcount cycles.
Browse files Browse the repository at this point in the history
Most of these only become apparent when trying to close a CouchServer or TDServer.

Conflicts:

	Couch/CouchTouchDBServer.h
	Couch/CouchTouchDBServer.m
	CouchCocoa.xcodeproj/project.pbxproj

Change-Id: I544eb633fe2b7437bda2cbba68f3f2c1131b021a
  • Loading branch information
snej committed May 5, 2012
1 parent e745afb commit c01fef5
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 36 deletions.
4 changes: 4 additions & 0 deletions Couch/CouchDatabase.m
Expand Up @@ -157,6 +157,10 @@ - (void) clearDocumentCache {
[_docCache forgetAllResources];
}

- (void) unretainDocumentCache {
[_docCache unretainResources];
}


#pragma mark -
#pragma mark BATCH CHANGES
Expand Down
1 change: 1 addition & 0 deletions Couch/CouchInternal.h
Expand Up @@ -38,6 +38,7 @@ typedef void (^OnDatabaseChangeBlock)(CouchDocument*, BOOL externalChange);
- (void) beginDocumentOperation: (CouchResource*)resource;
- (void) endDocumentOperation: (CouchResource*)resource;
- (void) onChange: (OnDatabaseChangeBlock)block; // convenience for unit tests
- (void) unretainDocumentCache;
@end


Expand Down
12 changes: 8 additions & 4 deletions Couch/CouchQuery.h
Expand Up @@ -35,7 +35,7 @@ typedef enum {
NSString* _startKeyDocID;
NSString* _endKeyDocID;
CouchStaleness _stale;
BOOL _descending, _prefetch;
BOOL _descending, _prefetch, _sequences;
NSArray *_keys;
NSUInteger _groupLevel;
}
Expand Down Expand Up @@ -80,6 +80,8 @@ typedef enum {
These can be accessed via CouchQueryRow's -documentContents property. */
@property BOOL prefetch;

@property BOOL sequences;


/** Starts an asynchronous query of the CouchDB view.
When complete, the operation's resultObject will be the CouchQueryEnumerator. */
Expand Down Expand Up @@ -122,7 +124,7 @@ typedef enum {
@interface CouchQueryEnumerator : NSEnumerator <NSCopying>
{
@private
CouchQuery* _query;
CouchDatabase* _database;
NSArray* _rows;
NSUInteger _totalCount;
NSUInteger _nextRow;
Expand Down Expand Up @@ -151,11 +153,10 @@ typedef enum {
@interface CouchQueryRow : NSObject
{
@private
CouchQuery* _query;
CouchDatabase* _database;
id _result;
}

@property (readonly) CouchQuery* query;
@property (readonly) id key;
@property (readonly) id value;

Expand Down Expand Up @@ -187,4 +188,7 @@ typedef enum {
/** Convenience for use in keypaths. Returns the key at the given index. */
@property (readonly) id key0, key1, key2, key3;

/** The local sequence number of the associated doc/revision.
Valid only if the 'sequences' and 'prefetch' properties were set in the query; otherwise returns 0. */
@property (readonly) UInt64 localSequence;
@end
65 changes: 35 additions & 30 deletions Couch/CouchQuery.m
Expand Up @@ -23,12 +23,12 @@


@interface CouchQueryEnumerator ()
- (id) initWithQuery: (CouchQuery*)query result: (NSDictionary*)result;
- (id) initWithDatabase: (CouchDatabase*)db result: (NSDictionary*)result;
@end


@interface CouchQueryRow ()
- (id) initWithQuery: (CouchQuery*)query result: (id)result;
- (id) initWithDatabase: (CouchDatabase*)db result: (id)result;
@end


Expand Down Expand Up @@ -69,7 +69,7 @@ - (void) dealloc

@synthesize limit=_limit, skip=_skip, descending=_descending, startKey=_startKey, endKey=_endKey,
prefetch=_prefetch, keys=_keys, groupLevel=_groupLevel, startKeyDocID=_startKeyDocID,
endKeyDocID=_endKeyDocID, stale=_stale;
endKeyDocID=_endKeyDocID, stale=_stale, sequences=_sequences;


- (CouchDesignDocument*) designDocument {
Expand Down Expand Up @@ -111,6 +111,8 @@ - (NSMutableDictionary*) requestParams {
[params setObject: @"true" forKey: @"?descending"];
if (_prefetch)
[params setObject: @"true" forKey: @"?include_docs"];
if (_sequences)
[params setObject: @"true" forKey: @"?local_seq"];
if (_groupLevel > 0)
[params setObject: [NSNumber numberWithUnsignedLong: _groupLevel] forKey: @"?group_level"];
[params setObject: @"true" forKey: @"?update_seq"];
Expand Down Expand Up @@ -148,8 +150,8 @@ - (NSError*) operation: (RESTOperation*)op willCompleteWithError: (NSError*)erro
NSArray* rows = $castIf(NSArray, [result objectForKey: @"rows"]);
if (rows) {
[self cacheResponse: op];
op.resultObject = [[[CouchQueryEnumerator alloc] initWithQuery: self
result: result] autorelease];
op.resultObject = [[[CouchQueryEnumerator alloc] initWithDatabase: self.database
result: result] autorelease];
} else {
Warn(@"Couldn't parse rows from CouchDB view response");
error = [RESTOperation errorWithHTTPStatus: 502 message: nil URL: self.URL];
Expand Down Expand Up @@ -269,7 +271,8 @@ - (NSError*) operation: (RESTOperation*)op willCompleteWithError: (NSError*)erro
if (rows && ![rows isEqual: _rows]) {
COUCHLOG(@"CouchLiveQuery: ...Rows changed! (now %lu)", (unsigned long)rows.count);
self.rows = rows; // Triggers KVO notification
self.prefetch = NO; // (prefetch disables conditional GET shortcut on next fetch)
if (!self.sequences)
self.prefetch = NO; // (prefetch disables conditional GET shortcut on next fetch)

// If this query isn't up-to-date (race condition where the db updated again after sending
// the response), start another fetch.
Expand All @@ -293,44 +296,43 @@ @implementation CouchQueryEnumerator
@synthesize totalCount=_totalCount, sequenceNumber=_sequenceNumber;


- (id) initWithQuery: (CouchQuery*)query
rows: (NSArray*)rows
totalCount: (NSUInteger)totalCount
sequenceNumber: (NSUInteger)sequenceNumber
- (id) initWithDatabase: (CouchDatabase*)database
rows: (NSArray*)rows
totalCount: (NSUInteger)totalCount
sequenceNumber: (NSUInteger)sequenceNumber
{
NSParameterAssert(query);
NSParameterAssert(database);
self = [super init];
if (self ) {
if (!rows) {
[self release];
return nil;
}
_query = [query retain];
_database = database;
_rows = [rows retain];
_totalCount = totalCount;
_sequenceNumber = sequenceNumber;
}
return self;
}

- (id) initWithQuery: (CouchQuery*)query result: (NSDictionary*)result {
return [self initWithQuery: query
rows: $castIf(NSArray, [result objectForKey: @"rows"])
totalCount: [[result objectForKey: @"total_rows"] intValue]
sequenceNumber: [[result objectForKey: @"update_seq"] intValue]];
- (id) initWithDatabase: (CouchDatabase*)db result: (NSDictionary*)result {
return [self initWithDatabase: db
rows: $castIf(NSArray, [result objectForKey: @"rows"])
totalCount: [[result objectForKey: @"total_rows"] intValue]
sequenceNumber: [[result objectForKey: @"update_seq"] intValue]];
}

- (id) copyWithZone: (NSZone*)zone {
return [[[self class] alloc] initWithQuery: _query
rows: _rows
totalCount: _totalCount
sequenceNumber: _sequenceNumber];
return [[[self class] alloc] initWithDatabase: _database
rows: _rows
totalCount: _totalCount
sequenceNumber: _sequenceNumber];
}


- (void) dealloc
{
[_query release];
[_rows release];
[super dealloc];
}
Expand All @@ -352,8 +354,8 @@ - (NSUInteger) count {


- (CouchQueryRow*) rowAtIndex: (NSUInteger)index {
return [[[CouchQueryRow alloc] initWithQuery: _query
result: [_rows objectAtIndex:index]]
return [[[CouchQueryRow alloc] initWithDatabase: _database
result: [_rows objectAtIndex:index]]
autorelease];
}

Expand All @@ -378,30 +380,27 @@ - (id) nextObject {
@implementation CouchQueryRow


- (id) initWithQuery: (CouchQuery*)query result: (id)result {
- (id) initWithDatabase: (CouchDatabase*)database result: (id)result {
self = [super init];
if (self) {
if (![result isKindOfClass: [NSDictionary class]]) {
Warn(@"Unexpected row value in view results: %@", result);
[self release];
return nil;
}
_query = [query retain];
_database = database;
_result = [result retain];
}
return self;
}


- (void)dealloc {
[_query release];
[_result release];
[super dealloc];
}


@synthesize query=_query;

- (id) key {return [_result objectForKey: @"key"];}
- (id) value {return [_result objectForKey: @"value"];}
- (NSString*) sourceDocumentID {return [_result objectForKey: @"id"];}
Expand Down Expand Up @@ -451,12 +450,18 @@ - (CouchDocument*) document {
NSString* docID = self.documentID;
if (!docID)
return nil;
CouchDocument* doc = [_query.database documentWithID: docID];
CouchDocument* doc = [_database documentWithID: docID];
[doc loadCurrentRevisionFrom: self];
return doc;
}


- (UInt64) localSequence {
id seq = [self.documentProperties objectForKey: @"_local_seq"];
return $castIf(NSNumber, seq).unsignedLongLongValue;
}


- (NSString*) description {
return [NSString stringWithFormat: @"%@[key=%@; value=%@; id=%@]",
[self class],
Expand Down
3 changes: 3 additions & 0 deletions Couch/CouchServer.h
Expand Up @@ -36,6 +36,9 @@
/** Without a URL, connects to localhost on default port 5984. */
- (id) init;

/** Releases all resources used by the CouchServer instance. */
- (void) close;

/** Fetches the server's current version string. (Synchronous) */
- (NSString*) getVersion: (NSError**)outError;

Expand Down
10 changes: 9 additions & 1 deletion Couch/CouchServer.m
Expand Up @@ -53,6 +53,14 @@ - (void)dealloc {
}


- (void) close {
[_replicationsQuery release];
_replicationsQuery = nil;
for (CouchDatabase* db in _dbCache.allCachedResources)
[db unretainDocumentCache];
}


- (RESTResource*) childWithPath: (NSString*)name {
return [[[CouchResource alloc] initWithParent: self relativePath: name] autorelease];
}
Expand Down Expand Up @@ -122,7 +130,7 @@ - (CouchLiveQuery*) replicationsQuery {
if (!_replicationsQuery) {
CouchDatabase* replicatorDB = [self replicatorDatabase];
replicatorDB.tracksChanges = YES;
_replicationsQuery = [[replicatorDB getAllDocuments] asLiveQuery];
_replicationsQuery = [[[replicatorDB getAllDocuments] asLiveQuery] retain];
[_replicationsQuery wait];
}
return _replicationsQuery;
Expand Down
7 changes: 7 additions & 0 deletions Couch/CouchTouchDBServer.h
Expand Up @@ -28,9 +28,16 @@
/** Preferred initializer. Starts up an in-process server. */
- (id)init;

/** Starts up a server that stores its data at the given path.
@param serverPath The filesystem path to the server directory. If it doesn't already exist it will be created. */
- (id) initWithServerPath: (NSString*)serverPath;

/** Inherited initializer, if you want to connect to a remote server for debugging purposes. */
- (id) initWithURL: (NSURL*)url;

/** Shuts down the TouchDB server. */
- (void) close;

/** If this is non-nil, the server failed to initialize. */
@property (readonly) NSError* error;

Expand Down
41 changes: 40 additions & 1 deletion Couch/CouchTouchDBServer.m
Expand Up @@ -25,11 +25,13 @@ @interface TDServer : NSObject
- (id) initWithDirectory: (NSString*)dirPath error: (NSError**)outError;
- (void) queue: (void(^)())block;
- (void) tellDatabaseNamed: (NSString*)dbName to: (void (^)(TDDatabase*))block;
- (void) close;
@end

@interface TDURLProtocol : NSURLProtocol
+ (NSURL*) rootURL;
+ (void) setServer: (TDServer*)server;
+ (NSURL*) registerServer: (TDServer*)server;
@end


Expand Down Expand Up @@ -84,6 +86,35 @@ - (id)init {
}


- (id) initWithServerPath: (NSString*)serverPath {
#if TARGET_OS_IPHONE
Class classTDURLProtocol = [TDURLProtocol class];
Class classTDServer = [TDServer class];
#else
// On Mac OS TouchDB.framework is linked dynamically, so avoid explicit references to its
// classes because they'd create link errors building CouchCocoa.
Class classTDURLProtocol = NSClassFromString(@"TDURLProtocol");
Class classTDServer = NSClassFromString(@"TDServer");
NSAssert(classTDURLProtocol && classTDServer, @"Not linked with TouchDB framework");
#endif

NSError* error;
TDServer* server = [[classTDServer alloc] initWithDirectory: serverPath error: &error];
NSURL* rootURL = server ? [classTDURLProtocol registerServer: server]
: [classTDURLProtocol rootURL];

self = [super initWithURL: rootURL];
if (self) {
_touchServer = server;
if (!server)
_error = [error retain];
} else {
[server release];
}
return self;
}


- (id) initWithURL:(NSURL *)url {
if (url)
return [super initWithURL: url];
Expand All @@ -94,7 +125,7 @@ - (id) initWithURL:(NSURL *)url {

- (void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_touchServer release];
[self close];
[_error release];
[super dealloc];
}
Expand All @@ -114,6 +145,14 @@ - (void) tellTDDatabaseNamed: (NSString*)dbName to: (void (^)(TDDatabase*))block
}


- (void) close {
[super close];
[_touchServer close];
[_touchServer release];
_touchServer = nil;
}


#pragma mark - ACTIVITY:

// I don't have to resort to polling the /_activity URL; I can listen for direct notifications
Expand Down
6 changes: 6 additions & 0 deletions REST/RESTCache.h
Expand Up @@ -55,4 +55,10 @@
/** Removes all resources from the cache. */
- (void) forgetAllResources;

/** Removes retained references to objects.
All objects that don't have anything else retaining them will be removed from the cache. */
- (void) unretainResources;

- (NSArray*) allCachedResources;

@end
10 changes: 10 additions & 0 deletions REST/RESTCache.m
Expand Up @@ -100,6 +100,16 @@ - (void) resourceBeingDealloced:(RESTResource*)resource {
}


- (NSArray*) allCachedResources {
return _map.allValues;
}


- (void) unretainResources {
[_cache removeAllObjects];
}


- (void) forgetAllResources {
[_map removeAllObjects];
[_cache removeAllObjects];
Expand Down

0 comments on commit c01fef5

Please sign in to comment.