// // P4ConnectionPool.m // MBMenuExtra // // Created by Michael Bishop on 1/7/10. // Copyright 2010 Perforce Software. All rights reserved. // #import "P4ConnectionPool.h" #import "P4RawConnection.h" #import "NGAUtilities.h" //#define SHOW_STATUS const NSInteger kMaximumConcurrentConnections = 4; @implementation P4ConnectionPool @synthesize networkDelay; SINGLETON_IMPLEMENTATION(P4ConnectionPool, sharedPool) -(id)init { if ( ! (self = [super init]) ) return nil; _connections = [[NSMutableDictionary alloc] init]; return self; } -(void)dealloc { [_connections release]; [super dealloc]; } #pragma mark PUBLIC -(NSString*)poolStatusStringForPort:(NSString*)p4port { NSMutableString * status = [NSMutableString stringWithFormat:@"=== Connection Pool Status for %@ ===\n", p4port]; NSMutableSet * connectionPool = [_connections objectForKey:p4port]; if ( !connectionPool ) { [status appendString:@"No connection pool."]; return status; } if ( [connectionPool count] == 0 ) { [status appendString:@" [empty]"]; return status; } for (P4RawConnection * connection in connectionPool) { NSMutableString * statusBar = [NSMutableString string]; NSInteger barWidth = [connection operationCount] / 5; NSInteger barWidthRemainder = [connection operationCount] % 5; for (int i = 0; i < barWidth; i++) [statusBar appendString:@"@"]; switch (barWidthRemainder) { case 0: break; case 1: [statusBar appendString:@"."]; break; case 2: [statusBar appendString:@"o"]; break; case 3: [statusBar appendString:@"O"]; break; case 4: [statusBar appendString:@"@"]; break; } [status appendFormat:@" |%@|\n", statusBar]; } return status; } -(void)updateStatus { #ifdef SHOW_STATUS if ( kMaximumConcurrentConnections <= 1 ) return; for ( NSString * port in [_connections allKeys] ) { LOG_DEBUG( @"%@", [self poolStatusStringForPort:port] ); } #endif } -(void)connectionFinished:(P4RawConnection*)connection { [self updateStatus]; } -(BOOL)runArgument:(NSString*)argument onPort:(NSString*)p4port withContext:(NSDictionary*)context updateBlock:(UpdateBlock)update completionBlock:(void(^)(P4Response*))completion { return [self runArguments:[NSArray arrayWithObject:argument] onPort:p4port withContext:context updateBlock:update completionBlock:completion]; } -(BOOL)runArguments:(NSArray*)arguments onPort:(NSString*)p4port withContext:(NSDictionary*)context updateBlock:(UpdateBlock)update completionBlock:(void(^)(P4Response*))completion { return [self runArguments:arguments onPort:p4port withContext:context content:nil updateBlock:update completionBlock:completion]; } -(BOOL)runArguments:(NSArray*)arguments onPort:(NSString*)p4port withContext:(NSDictionary*)context content:(NSString*)content updateBlock:(UpdateBlock)update completionBlock:(void(^)(P4Response*))completion { P4RawConnection * targetConnection = nil; @synchronized ( _connections ) { NSMutableSet * connectionPool = [_connections objectForKey:p4port]; if ( !connectionPool ) { connectionPool = [NSMutableSet set]; [_connections setObject:connectionPool forKey:p4port]; } if ( [connectionPool count] < kMaximumConcurrentConnections ) { targetConnection = [[P4RawConnection alloc] initWithPortString:p4port pool:self]; [connectionPool addObject:targetConnection]; [targetConnection release]; // LOG_DEBUG( @"Pool: CREATED connection:%@.", targetConnection ); } else { // Find the connection with the least amount of running operations for (P4RawConnection * connection in connectionPool) { if ( targetConnection == nil ) targetConnection = connection; else if ( [connection operationCount] < [targetConnection operationCount] ) targetConnection = connection; } // LOG_DEBUG( @"Pool: REUSING connection:%@.", targetConnection ); } } // LOG_DEBUG( @"Pool running command %@ on port: %@.", arguments, p4port ); targetConnection.networkDelay = self.networkDelay; BOOL result = [targetConnection runArguments:arguments withContent:content context:context updateBlock:update completionBlock:completion]; [self updateStatus]; return result; } -(P4Response*)runArguments:(NSArray*)arguments onPort:(NSString*)p4port withContext:(NSDictionary*)context content:(NSString*)content { P4RawConnection * connection = [[[P4RawConnection alloc] initWithPortString:p4port pool:self] autorelease]; // LOG_DEBUG( @"Pool running command %@ on port: %@.", arguments, p4port ); return [connection runArguments:arguments withContext:context content:content]; } #pragma mark PRIVATE -(BOOL)removeConnection:(P4RawConnection *)connection { // This only works because we have one connection per port in our pool // TODO: It would be nice to be able to specify more than one connection // per port in our pool, but for now, this is the simplest and it works. @synchronized ( _connections ) { NSMutableSet * connectionPool = [_connections objectForKey:connection.portString]; if ( !connectionPool ) { LOG_ERROR( @"No pool for %@. Ignoring.", connection.portString ); return NO; } if ( ![connectionPool containsObject:connection] ) { LOG_ERROR( @"Tried to remove connection:%@, but it doesn't exist", connection ); return NO; } // LOG_DEBUG( @"Pool: REMOVING connection:%@.", connection ); [connectionPool removeObject:connection]; } return YES; } @end