Ben Copsey

More fixes for throttling

@@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
21 #import "ASIInputStream.h" 21 #import "ASIInputStream.h"
22 22
23 // Automatically set on build 23 // Automatically set on build
24 -NSString *ASIHTTPRequestVersion = @"v1.2-33 2009-12-17"; 24 +NSString *ASIHTTPRequestVersion = @"v1.2-34 2009-12-17";
25 25
26 NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain"; 26 NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
27 27
@@ -731,9 +731,9 @@ static BOOL isiPhoneOS2; @@ -731,9 +731,9 @@ static BOOL isiPhoneOS2;
731 731
732 // Are we gzipping the request body? 732 // Are we gzipping the request body?
733 if ([self compressedPostBodyFilePath] && [[NSFileManager defaultManager] fileExistsAtPath:[self compressedPostBodyFilePath]]) { 733 if ([self compressedPostBodyFilePath] && [[NSFileManager defaultManager] fileExistsAtPath:[self compressedPostBodyFilePath]]) {
734 - [self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self compressedPostBodyFilePath]]]; 734 + [self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self compressedPostBodyFilePath] request:self]];
735 } else { 735 } else {
736 - [self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self postBodyFilePath]]]; 736 + [self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self postBodyFilePath] request:self]];
737 } 737 }
738 readStream = CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream]); 738 readStream = CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream]);
739 } else { 739 } else {
@@ -741,9 +741,9 @@ static BOOL isiPhoneOS2; @@ -741,9 +741,9 @@ static BOOL isiPhoneOS2;
741 // If we have a request body, we'll stream it from memory using our custom stream, so that we can measure bandwidth use and it can be bandwidth-throttled if nescessary 741 // If we have a request body, we'll stream it from memory using our custom stream, so that we can measure bandwidth use and it can be bandwidth-throttled if nescessary
742 if ([self postBody] && [[self postBody] length] > 0) { 742 if ([self postBody] && [[self postBody] length] > 0) {
743 if ([self shouldCompressRequestBody] && [self compressedPostBody]) { 743 if ([self shouldCompressRequestBody] && [self compressedPostBody]) {
744 - [self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self compressedPostBody]]]; 744 + [self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self compressedPostBody] request:self]];
745 } else if ([self postBody]) { 745 } else if ([self postBody]) {
746 - [self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self postBody]]]; 746 + [self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self postBody] request:self]];
747 } 747 }
748 readStream = CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream]); 748 readStream = CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream]);
749 749
@@ -917,7 +917,7 @@ static BOOL isiPhoneOS2; @@ -917,7 +917,7 @@ static BOOL isiPhoneOS2;
917 [self performThrottling]; 917 [self performThrottling];
918 918
919 // See if we need to timeout 919 // See if we need to timeout
920 - if (lastActivityTime && timeOutSeconds > 0 && [now timeIntervalSinceDate:lastActivityTime] > timeOutSeconds) { 920 + if (readStreamIsScheduled && lastActivityTime && timeOutSeconds > 0 && [now timeIntervalSinceDate:lastActivityTime] > timeOutSeconds) {
921 921
922 // Prevent timeouts before 128KB* has been sent when the size of data to upload is greater than 128KB* (*32KB on iPhone 3.0 SDK) 922 // Prevent timeouts before 128KB* has been sent when the size of data to upload is greater than 128KB* (*32KB on iPhone 3.0 SDK)
923 // This is to workaround the fact that kCFStreamPropertyHTTPRequestBytesWrittenCount is the amount written to the buffer, not the amount actually sent 923 // This is to workaround the fact that kCFStreamPropertyHTTPRequestBytesWrittenCount is the amount written to the buffer, not the amount actually sent
@@ -2965,6 +2965,9 @@ static BOOL isiPhoneOS2; @@ -2965,6 +2965,9 @@ static BOOL isiPhoneOS2;
2965 #if DEBUG_THROTTLING 2965 #if DEBUG_THROTTLING
2966 NSLog(@"Waking up request %@",self); 2966 NSLog(@"Waking up request %@",self);
2967 #endif 2967 #endif
  2968 +
  2969 + // Reset the timeout
  2970 + [self setLastActivityTime:[NSDate date]];
2968 CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL}; 2971 CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL};
2969 CFReadStreamSetClient(readStream, kNetworkEvents, ReadStreamClientCallBack, &ctxt); 2972 CFReadStreamSetClient(readStream, kNetworkEvents, ReadStreamClientCallBack, &ctxt);
2970 CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); 2973 CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
@@ -2973,6 +2976,13 @@ static BOOL isiPhoneOS2; @@ -2973,6 +2976,13 @@ static BOOL isiPhoneOS2;
2973 } 2976 }
2974 } 2977 }
2975 [bandwidthThrottlingLock unlock]; 2978 [bandwidthThrottlingLock unlock];
  2979 + } else if (!readStreamIsScheduled) {
  2980 + // Reset the timeout
  2981 + [self setLastActivityTime:[NSDate date]];
  2982 + CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL};
  2983 + CFReadStreamSetClient(readStream, kNetworkEvents, ReadStreamClientCallBack, &ctxt);
  2984 + CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
  2985 + readStreamIsScheduled = YES;
2976 } 2986 }
2977 } 2987 }
2978 2988
@@ -3044,10 +3054,6 @@ static BOOL isiPhoneOS2; @@ -3044,10 +3054,6 @@ static BOOL isiPhoneOS2;
3044 + (unsigned long)averageBandwidthUsedPerSecond 3054 + (unsigned long)averageBandwidthUsedPerSecond
3045 { 3055 {
3046 [bandwidthThrottlingLock lock]; 3056 [bandwidthThrottlingLock lock];
3047 -  
3048 - if (!bandwidthMeasurementDate || [bandwidthMeasurementDate timeIntervalSinceNow] < 0) {  
3049 - [self recordBandwidthUsage];  
3050 - }  
3051 unsigned long amount = averageBandwidthUsedPerSecond; 3057 unsigned long amount = averageBandwidthUsedPerSecond;
3052 [bandwidthThrottlingLock unlock]; 3058 [bandwidthThrottlingLock unlock];
3053 return amount; 3059 return amount;
@@ -3063,11 +3069,11 @@ static BOOL isiPhoneOS2; @@ -3063,11 +3069,11 @@ static BOOL isiPhoneOS2;
3063 } 3069 }
3064 3070
3065 // Are we performing bandwidth throttling? 3071 // Are we performing bandwidth throttling?
3066 -#if TARGET_OS_IPHONE 3072 + #if TARGET_OS_IPHONE
3067 if (isBandwidthThrottled || (!shouldThrottleBandwithForWWANOnly && (maxBandwidthPerSecond))) { 3073 if (isBandwidthThrottled || (!shouldThrottleBandwithForWWANOnly && (maxBandwidthPerSecond))) {
3068 -#else 3074 + #else
3069 if (maxBandwidthPerSecond) { 3075 if (maxBandwidthPerSecond) {
3070 -#endif 3076 + #endif
3071 // How much data can we still send or receive this second? 3077 // How much data can we still send or receive this second?
3072 long long bytesRemaining = (long long)maxBandwidthPerSecond - (long long)bandwidthUsedInLastSecond; 3078 long long bytesRemaining = (long long)maxBandwidthPerSecond - (long long)bandwidthUsedInLastSecond;
3073 3079
@@ -3083,6 +3089,32 @@ static BOOL isiPhoneOS2; @@ -3083,6 +3089,32 @@ static BOOL isiPhoneOS2;
3083 [bandwidthThrottlingLock unlock]; 3089 [bandwidthThrottlingLock unlock];
3084 } 3090 }
3085 3091
  3092 +
  3093 +
  3094 ++ (unsigned long)maxUploadReadLength
  3095 +{
  3096 +
  3097 + [bandwidthThrottlingLock lock];
  3098 +
  3099 + // We'll split our bandwidth allowance into 4 (which is the default for an ASINetworkQueue's max concurrent operations count) to give all running requests a fighting chance of reading data this cycle
  3100 + long long toRead = maxBandwidthPerSecond/4;
  3101 + if (maxBandwidthPerSecond > 0 && (bandwidthUsedInLastSecond + toRead > maxBandwidthPerSecond)) {
  3102 + toRead = (long long)maxBandwidthPerSecond-(long long)bandwidthUsedInLastSecond;
  3103 + if (toRead < 0) {
  3104 + toRead = 0;
  3105 + }
  3106 + }
  3107 +
  3108 + if (toRead == 0 || !bandwidthMeasurementDate || [bandwidthMeasurementDate timeIntervalSinceNow] < -0) {
  3109 + [throttleWakeUpTime release];
  3110 + throttleWakeUpTime = [bandwidthMeasurementDate retain];
  3111 + }
  3112 + [bandwidthThrottlingLock unlock];
  3113 + return (unsigned long)toRead;
  3114 +}
  3115 +
  3116 +
  3117 +
3086 #if TARGET_OS_IPHONE 3118 #if TARGET_OS_IPHONE
3087 + (void)setShouldThrottleBandwidthForWWAN:(BOOL)throttle 3119 + (void)setShouldThrottleBandwidthForWWAN:(BOOL)throttle
3088 { 3120 {
@@ -3145,31 +3177,6 @@ static BOOL isiPhoneOS2; @@ -3145,31 +3177,6 @@ static BOOL isiPhoneOS2;
3145 #endif 3177 #endif
3146 3178
3147 3179
3148 -  
3149 -+ (unsigned long)maxUploadReadLength  
3150 -{  
3151 -  
3152 - [bandwidthThrottlingLock lock];  
3153 -  
3154 - // We'll split our bandwidth allowance into 4 (which is the default for an ASINetworkQueue's max concurrent operations count) to give all running requests a fighting chance of reading data this cycle  
3155 - unsigned long toRead = maxBandwidthPerSecond/4;  
3156 - if (maxBandwidthPerSecond > 0 && (bandwidthUsedInLastSecond + toRead > maxBandwidthPerSecond)) {  
3157 - toRead = maxBandwidthPerSecond-bandwidthUsedInLastSecond;  
3158 - if (toRead < 0) {  
3159 - toRead = 0;  
3160 - }  
3161 - }  
3162 -  
3163 - if (toRead == 0 || !bandwidthMeasurementDate || [bandwidthMeasurementDate timeIntervalSinceNow] < -0) {  
3164 - [throttleWakeUpTime release];  
3165 - throttleWakeUpTime = [bandwidthMeasurementDate retain];  
3166 - [self recordBandwidthUsage];  
3167 - }  
3168 - [bandwidthThrottlingLock unlock];  
3169 - return toRead;  
3170 -}  
3171 -  
3172 -  
3173 #pragma mark miscellany 3180 #pragma mark miscellany
3174 3181
3175 + (BOOL)isiPhoneOS2 3182 + (BOOL)isiPhoneOS2
@@ -7,6 +7,27 @@ @@ -7,6 +7,27 @@
7 // 7 //
8 8
9 9
  10 +// ======
  11 +// Debug output configuration options
  12 +// ======
  13 +
  14 +// When set to 1 ASIHTTPRequests will print information about what a request is doing
  15 +#define DEBUG_REQUEST_STATUS 0
  16 +
  17 +// When set to 1, ASIFormDataRequests will print information about the request body to the console
  18 +#define DEBUG_FORM_DATA_REQUEST 0
  19 +
  20 +// When set to 1, ASIHTTPRequests will print information about bandwidth throttling to the console
  21 +#define DEBUG_THROTTLING 0
  22 +
  23 +
  24 +// ======
  25 +// Reachability API (iPhone only)
  26 +// ======
  27 +
  28 +/*
  29 +Turn off any features you don't need for a speed boost
  30 +*/
10 31
11 /* 32 /*
12 ASIHTTPRequest uses Apple's Reachability class (http://developer.apple.com/iphone/library/samplecode/Reachability/) to turn bandwidth throttling on and off automatically when shouldThrottleBandwidthForWWAN is set to YES on iPhone OS 33 ASIHTTPRequest uses Apple's Reachability class (http://developer.apple.com/iphone/library/samplecode/Reachability/) to turn bandwidth throttling on and off automatically when shouldThrottleBandwidthForWWAN is set to YES on iPhone OS
@@ -25,16 +46,3 @@ This config option is not used for apps targeting Mac OS X @@ -25,16 +46,3 @@ This config option is not used for apps targeting Mac OS X
25 #define REACHABILITY_20_API 0 46 #define REACHABILITY_20_API 0
26 47
27 48
28 -/*  
29 -Debug output configuration options  
30 -*/  
31 -  
32 -// When set to 1 ASIHTTPRequests will print information about what a request is doing  
33 -#define DEBUG_REQUEST_STATUS 0  
34 -  
35 -// When set to 1, ASIFormDataRequests will print information about the request body to the console  
36 -#define DEBUG_FORM_DATA_REQUEST 0  
37 -  
38 -// When set to 1, ASIHTTPRequests will print information about bandwidth throttling to the console  
39 -#define DEBUG_THROTTLING 0  
40 -  
@@ -8,15 +8,19 @@ @@ -8,15 +8,19 @@
8 8
9 #import <Foundation/Foundation.h> 9 #import <Foundation/Foundation.h>
10 10
  11 +@class ASIHTTPRequest;
  12 +
11 // This is a wrapper for NSInputStream that pretends to be an NSInputStream itself 13 // This is a wrapper for NSInputStream that pretends to be an NSInputStream itself
12 // Subclassing NSInputStream seems to be tricky, and may involve overriding undocumented methods, so we'll cheat instead. 14 // Subclassing NSInputStream seems to be tricky, and may involve overriding undocumented methods, so we'll cheat instead.
13 // It is used by ASIHTTPRequest whenever we have a request body, and handles measuring and throttling the bandwidth used for uploading 15 // It is used by ASIHTTPRequest whenever we have a request body, and handles measuring and throttling the bandwidth used for uploading
14 16
15 @interface ASIInputStream : NSObject { 17 @interface ASIInputStream : NSObject {
16 NSInputStream *stream; 18 NSInputStream *stream;
  19 + ASIHTTPRequest *request;
17 } 20 }
18 -+ (id)inputStreamWithFileAtPath:(NSString *)path; 21 ++ (id)inputStreamWithFileAtPath:(NSString *)path request:(ASIHTTPRequest *)request;
19 -+ (id)inputStreamWithData:(NSData *)data; 22 ++ (id)inputStreamWithData:(NSData *)data request:(ASIHTTPRequest *)request;
20 23
21 -@property (retain) NSInputStream *stream; 24 +@property (retain, nonatomic) NSInputStream *stream;
  25 +@property (assign, nonatomic) ASIHTTPRequest *request;
22 @end 26 @end
@@ -21,16 +21,18 @@ static NSLock *readLock = nil; @@ -21,16 +21,18 @@ static NSLock *readLock = nil;
21 } 21 }
22 } 22 }
23 23
24 -+ (id)inputStreamWithFileAtPath:(NSString *)path 24 ++ (id)inputStreamWithFileAtPath:(NSString *)path request:(ASIHTTPRequest *)request
25 { 25 {
26 ASIInputStream *stream = [[[self alloc] init] autorelease]; 26 ASIInputStream *stream = [[[self alloc] init] autorelease];
  27 + [stream setRequest:request];
27 [stream setStream:[NSInputStream inputStreamWithFileAtPath:path]]; 28 [stream setStream:[NSInputStream inputStreamWithFileAtPath:path]];
28 return stream; 29 return stream;
29 } 30 }
30 31
31 -+ (id)inputStreamWithData:(NSData *)data 32 ++ (id)inputStreamWithData:(NSData *)data request:(ASIHTTPRequest *)request
32 { 33 {
33 ASIInputStream *stream = [[[self alloc] init] autorelease]; 34 ASIInputStream *stream = [[[self alloc] init] autorelease];
  35 + [stream setRequest:request];
34 [stream setStream:[NSInputStream inputStreamWithData:data]]; 36 [stream setStream:[NSInputStream inputStreamWithData:data]];
35 return stream; 37 return stream;
36 } 38 }
@@ -54,6 +56,7 @@ static NSLock *readLock = nil; @@ -54,6 +56,7 @@ static NSLock *readLock = nil;
54 } else if (toRead == 0) { 56 } else if (toRead == 0) {
55 toRead = 1; 57 toRead = 1;
56 } 58 }
  59 + [request performThrottling];
57 } 60 }
58 [ASIHTTPRequest incrementBandwidthUsedInLastSecond:toRead]; 61 [ASIHTTPRequest incrementBandwidthUsedInLastSecond:toRead];
59 [readLock unlock]; 62 [readLock unlock];
@@ -73,4 +76,5 @@ static NSLock *readLock = nil; @@ -73,4 +76,5 @@ static NSLock *readLock = nil;
73 } 76 }
74 77
75 @synthesize stream; 78 @synthesize stream;
  79 +@synthesize request;
76 @end 80 @end
@@ -192,7 +192,7 @@ @@ -192,7 +192,7 @@
192 [request setDidFinishSelector:@selector(topSecretFetchComplete:)]; 192 [request setDidFinishSelector:@selector(topSecretFetchComplete:)];
193 [request setDelegate:self]; 193 [request setDelegate:self];
194 [request setUseKeychainPersistance:[keychainCheckbox state]]; 194 [request setUseKeychainPersistance:[keychainCheckbox state]];
195 - [request startAsynchronous]; 195 + [request start];
196 196
197 } 197 }
198 198