GTMSessionFetcherService.m 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419
  1. /* Copyright 2014 Google Inc. All rights reserved.
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. #if !defined(__has_feature) || !__has_feature(objc_arc)
  16. #error "This file requires ARC support."
  17. #endif
  18. #import "GTMSessionFetcher/GTMSessionFetcherService.h"
  19. #import "GTMSessionFetcherService+Internal.h"
  20. #include <os/lock.h>
  21. NSString *const kGTMSessionFetcherServiceSessionBecameInvalidNotification =
  22. @"kGTMSessionFetcherServiceSessionBecameInvalidNotification";
  23. NSString *const kGTMSessionFetcherServiceSessionKey = @"kGTMSessionFetcherServiceSessionKey";
  24. static id<GTMUserAgentProvider> SharedStandardUserAgentProvider(void) {
  25. static dispatch_once_t onceToken;
  26. static id<GTMUserAgentProvider> standardUserAgentProvider;
  27. dispatch_once(&onceToken, ^{
  28. standardUserAgentProvider = [[GTMStandardUserAgentProvider alloc] initWithBundle:nil];
  29. });
  30. return standardUserAgentProvider;
  31. }
  32. #if !GTMSESSION_BUILD_COMBINED_SOURCES
  33. @interface GTMSessionFetcher (ServiceMethods)
  34. - (BOOL)beginFetchMayDelay:(BOOL)mayDelay
  35. mayAuthorize:(BOOL)mayAuthorize
  36. mayDecorate:(BOOL)mayDecorate;
  37. @end
  38. #endif // !GTMSESSION_BUILD_COMBINED_SOURCES
  39. @interface GTMSessionFetcherService ()
  40. @property(atomic, strong, readwrite) NSDictionary *delayedFetchersByHost;
  41. @property(atomic, strong, readwrite) NSDictionary *runningFetchersByHost;
  42. // Ordered collection of id<GTMFetcherDecoratorProtocol>, held weakly.
  43. @property(atomic, strong, readonly) NSPointerArray *decoratorsPointerArray;
  44. @end
  45. // Since NSURLSession doesn't support a separate delegate per task (!), instances of this
  46. // class serve as a session delegate trampoline.
  47. //
  48. // This class maps a session's tasks to fetchers, and resends delegate messages to the task's
  49. // fetcher.
  50. @interface GTMSessionFetcherSessionDelegateDispatcher : NSObject <NSURLSessionDelegate>
  51. // The session for the tasks in this dispatcher's task-to-fetcher map.
  52. @property(atomic) NSURLSession *session;
  53. // The timer interval for invalidating a session that has no active tasks.
  54. @property(atomic) NSTimeInterval discardInterval;
  55. // The current discard timer.
  56. @property(atomic, readonly) NSTimer *discardTimer;
  57. - (instancetype)initWithParentService:(GTMSessionFetcherService *)parentService
  58. sessionDiscardInterval:(NSTimeInterval)discardInterval;
  59. - (void)setFetcher:(GTMSessionFetcher *)fetcher forTask:(NSURLSessionTask *)task;
  60. - (void)removeFetcher:(GTMSessionFetcher *)fetcher;
  61. // Before using a session, tells the delegate dispatcher to stop the discard timer.
  62. - (void)startSessionUsage;
  63. // When abandoning a delegate dispatcher, we want to avoid the session retaining
  64. // the delegate after tasks complete.
  65. - (void)abandon;
  66. @end
  67. @implementation GTMSessionFetcherService {
  68. NSMutableDictionary *_delayedFetchersByHost;
  69. NSMutableDictionary *_runningFetchersByHost;
  70. NSUInteger _maxRunningFetchersPerHost;
  71. // When this ivar is nil, the service will not reuse sessions.
  72. GTMSessionFetcherSessionDelegateDispatcher *_delegateDispatcher;
  73. // Fetchers will wait on this if another fetcher is creating the shared NSURLSession.
  74. os_unfair_lock _sessionCreationLock;
  75. BOOL _callbackQueueIsConcurrent;
  76. dispatch_queue_t _callbackQueue;
  77. NSOperationQueue *_delegateQueue;
  78. NSHTTPCookieStorage *_cookieStorage;
  79. id<GTMUserAgentProvider> _userAgentProvider;
  80. NSTimeInterval _timeout;
  81. NSURLCredential *_credential; // Username & password.
  82. NSURLCredential *_proxyCredential; // Credential supplied to proxy servers.
  83. #pragma clang diagnostic push
  84. #pragma clang diagnostic ignored "-Wdeprecated"
  85. id<GTMFetcherAuthorizationProtocol> _authorizer;
  86. #pragma clang diagnostic pop
  87. // For waitForCompletionOfAllFetchersWithTimeout: we need to wait on stopped fetchers since
  88. // they've not yet finished invoking their queued callbacks. This array is nil except when
  89. // waiting on fetchers.
  90. NSMutableArray *_stoppedFetchersToWaitFor;
  91. // For fetchers that enqueued their callbacks before stopAllFetchers was called on the service,
  92. // set a barrier so the callbacks know to bail out.
  93. NSDate *_stoppedAllFetchersDate;
  94. }
  95. // Clang-format likes to cram all @synthesize items onto the fewest lines, rather than one-per.
  96. // clang-format off
  97. @synthesize maxRunningFetchersPerHost = _maxRunningFetchersPerHost,
  98. configuration = _configuration,
  99. configurationBlock = _configurationBlock,
  100. cookieStorage = _cookieStorage,
  101. challengeBlock = _challengeBlock,
  102. credential = _credential,
  103. proxyCredential = _proxyCredential,
  104. allowedInsecureSchemes = _allowedInsecureSchemes,
  105. allowLocalhostRequest = _allowLocalhostRequest,
  106. allowInvalidServerCertificates = _allowInvalidServerCertificates,
  107. retryEnabled = _retryEnabled,
  108. retryBlock = _retryBlock,
  109. maxRetryInterval = _maxRetryInterval,
  110. minRetryInterval = _minRetryInterval,
  111. metricsCollectionBlock = _metricsCollectionBlock,
  112. properties = _properties,
  113. unusedSessionTimeout = _unusedSessionTimeout,
  114. userAgentProvider = _userAgentProvider,
  115. decoratorsPointerArray = _decoratorsPointerArray,
  116. testBlock = _testBlock;
  117. // clang-format on
  118. #if GTM_BACKGROUND_TASK_FETCHING
  119. @synthesize skipBackgroundTask = _skipBackgroundTask;
  120. #endif
  121. - (instancetype)init {
  122. self = [super init];
  123. if (self) {
  124. _delayedFetchersByHost = [[NSMutableDictionary alloc] init];
  125. _runningFetchersByHost = [[NSMutableDictionary alloc] init];
  126. _maxRunningFetchersPerHost = 10;
  127. _unusedSessionTimeout = 60.0;
  128. _delegateDispatcher = [[GTMSessionFetcherSessionDelegateDispatcher alloc]
  129. initWithParentService:self
  130. sessionDiscardInterval:_unusedSessionTimeout];
  131. _callbackQueue = dispatch_get_main_queue();
  132. _delegateQueue = [[NSOperationQueue alloc] init];
  133. _delegateQueue.maxConcurrentOperationCount = 1;
  134. _delegateQueue.name = @"com.google.GTMSessionFetcher.NSURLSessionDelegateQueue";
  135. _sessionCreationLock = OS_UNFAIR_LOCK_INIT;
  136. // Starting with the SDKs for OS X 10.11/iOS 9, the service has a default useragent.
  137. // Apps can remove this and get the default system "CFNetwork" useragent by setting the
  138. // fetcher service's userAgent or userAgentProvider properties to nil.
  139. //
  140. // Formatting the User-Agent string can be expensive, so create a shared cache
  141. // which asynchronously calculates and caches the standard User-Agent.
  142. _userAgentProvider = SharedStandardUserAgentProvider();
  143. }
  144. return self;
  145. }
  146. - (void)dealloc {
  147. [self detachAuthorizer];
  148. [_delegateDispatcher abandon];
  149. }
  150. #pragma mark Generate a new fetcher
  151. // Creates a serial queue targetting the service's callback, meant to be provided to a new
  152. // GTMSessionFetcher instance.
  153. //
  154. // This method is not intended to be overrideable by clients.
  155. - (nonnull dispatch_queue_t)serialQueueForNewFetcher:(GTMSessionFetcher *)fetcher {
  156. @synchronized(self) {
  157. GTMSessionMonitorSynchronized(self);
  158. if (!_callbackQueueIsConcurrent) return _callbackQueue;
  159. static const char *kQueueLabel = "com.google.GTMSessionFetcher.serialCallbackQueue";
  160. return dispatch_queue_create_with_target(kQueueLabel, DISPATCH_QUEUE_SERIAL, _callbackQueue);
  161. }
  162. }
  163. // Clients may override this method. Clients should not override any other library methods.
  164. - (id)fetcherWithRequest:(NSURLRequest *)request fetcherClass:(Class)fetcherClass {
  165. GTMSessionFetcher *fetcher = [[fetcherClass alloc] initWithRequest:request
  166. configuration:self.configuration];
  167. fetcher.callbackQueue = [self serialQueueForNewFetcher:fetcher];
  168. fetcher.sessionDelegateQueue = self.sessionDelegateQueue;
  169. fetcher.challengeBlock = self.challengeBlock;
  170. fetcher.credential = self.credential;
  171. fetcher.proxyCredential = self.proxyCredential;
  172. fetcher.authorizer = self.authorizer;
  173. fetcher.cookieStorage = self.cookieStorage;
  174. fetcher.allowedInsecureSchemes = self.allowedInsecureSchemes;
  175. fetcher.allowLocalhostRequest = self.allowLocalhostRequest;
  176. fetcher.allowInvalidServerCertificates = self.allowInvalidServerCertificates;
  177. fetcher.configurationBlock = self.configurationBlock;
  178. fetcher.retryEnabled = self.retryEnabled;
  179. fetcher.retryBlock = self.retryBlock;
  180. fetcher.maxRetryInterval = self.maxRetryInterval;
  181. fetcher.minRetryInterval = self.minRetryInterval;
  182. if (@available(iOS 10.0, *)) {
  183. fetcher.metricsCollectionBlock = self.metricsCollectionBlock;
  184. }
  185. fetcher.properties = self.properties;
  186. fetcher.service = self;
  187. #if GTM_BACKGROUND_TASK_FETCHING
  188. fetcher.skipBackgroundTask = self.skipBackgroundTask;
  189. #endif
  190. fetcher.userAgentProvider = self.userAgentProvider;
  191. fetcher.testBlock = self.testBlock;
  192. return fetcher;
  193. }
  194. - (GTMSessionFetcher *)fetcherWithRequest:(NSURLRequest *)request {
  195. return [self fetcherWithRequest:request fetcherClass:[GTMSessionFetcher class]];
  196. }
  197. - (GTMSessionFetcher *)fetcherWithURL:(NSURL *)requestURL {
  198. return [self fetcherWithRequest:[NSURLRequest requestWithURL:requestURL]];
  199. }
  200. - (GTMSessionFetcher *)fetcherWithURLString:(NSString *)requestURLString {
  201. NSURL *url = [NSURL URLWithString:requestURLString];
  202. return [self fetcherWithURL:url];
  203. }
  204. - (void)addDecorator:(id<GTMFetcherDecoratorProtocol>)decorator {
  205. @synchronized(self) {
  206. if (!_decoratorsPointerArray) {
  207. _decoratorsPointerArray = [NSPointerArray weakObjectsPointerArray];
  208. }
  209. [_decoratorsPointerArray addPointer:(__bridge void *)decorator];
  210. }
  211. }
  212. - (nullable NSArray<id<GTMFetcherDecoratorProtocol>> *)decorators {
  213. @synchronized(self) {
  214. return _decoratorsPointerArray.allObjects;
  215. }
  216. }
  217. - (void)removeDecorator:(id<GTMFetcherDecoratorProtocol>)decorator {
  218. @synchronized(self) {
  219. NSUInteger i = 0;
  220. for (id<GTMFetcherDecoratorProtocol> decoratorCandidate in _decoratorsPointerArray) {
  221. if (decoratorCandidate == decorator) {
  222. break;
  223. }
  224. ++i;
  225. }
  226. GTMSESSION_ASSERT_DEBUG(i < _decoratorsPointerArray.count,
  227. @"decorator %@ must be passed to -addDecorator: before removing",
  228. decorator);
  229. if (i < _decoratorsPointerArray.count) {
  230. [_decoratorsPointerArray removePointerAtIndex:i];
  231. }
  232. }
  233. }
  234. // Returns a session for the fetcher's host, or nil.
  235. - (NSURLSession *)session {
  236. @synchronized(self) {
  237. GTMSessionMonitorSynchronized(self);
  238. NSURLSession *session = _delegateDispatcher.session;
  239. return session;
  240. }
  241. }
  242. - (NSURLSession *)sessionWithCreationBlock:
  243. (NS_NOESCAPE GTMSessionFetcherSessionCreationBlock)creationBlock {
  244. @synchronized(self) {
  245. GTMSessionMonitorSynchronized(self);
  246. if (!_delegateDispatcher) {
  247. // This fetcher is creating a non-shared session, so skip locking.
  248. return creationBlock(nil);
  249. }
  250. }
  251. @try {
  252. NSURLSession *session;
  253. // Wait if another fetcher is currently creating a session; avoid waiting inside the
  254. // @synchronized block as that could deadlock.
  255. os_unfair_lock_lock(&_sessionCreationLock);
  256. @synchronized(self) {
  257. GTMSessionMonitorSynchronized(self);
  258. // Before getting the NSURLSession for task creation, it is
  259. // important to invalidate and nil out the session discard timer; otherwise
  260. // the session can be invalidated between when it is returned to the
  261. // fetcher, and when the fetcher attempts to create its NSURLSessionTask.
  262. [_delegateDispatcher startSessionUsage];
  263. session = _delegateDispatcher.session;
  264. if (!session) {
  265. session = creationBlock(_delegateDispatcher);
  266. _delegateDispatcher.session = session;
  267. }
  268. }
  269. return session;
  270. } @finally {
  271. // Ensure the lock is always released, even if creationBlock throws.
  272. os_unfair_lock_unlock(&_sessionCreationLock);
  273. }
  274. }
  275. - (id<NSURLSessionDelegate>)sessionDelegate {
  276. @synchronized(self) {
  277. GTMSessionMonitorSynchronized(self);
  278. return _delegateDispatcher;
  279. }
  280. }
  281. #pragma mark Queue Management
  282. - (void)addRunningFetcher:(GTMSessionFetcher *)fetcher forHost:(NSString *)host {
  283. // Add to the array of running fetchers for this host, creating the array if needed.
  284. NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host];
  285. if (runningForHost == nil) {
  286. runningForHost = [NSMutableArray arrayWithObject:fetcher];
  287. [_runningFetchersByHost setObject:runningForHost forKey:host];
  288. } else {
  289. [runningForHost addObject:fetcher];
  290. }
  291. }
  292. - (void)addDelayedFetcher:(GTMSessionFetcher *)fetcher forHost:(NSString *)host {
  293. // Add to the array of delayed fetchers for this host, creating the array if needed.
  294. NSMutableArray *delayedForHost = [_delayedFetchersByHost objectForKey:host];
  295. if (delayedForHost == nil) {
  296. delayedForHost = [NSMutableArray arrayWithObject:fetcher];
  297. [_delayedFetchersByHost setObject:delayedForHost forKey:host];
  298. } else {
  299. [delayedForHost addObject:fetcher];
  300. }
  301. }
  302. - (BOOL)isDelayingFetcher:(GTMSessionFetcher *)fetcher {
  303. @synchronized(self) {
  304. GTMSessionMonitorSynchronized(self);
  305. NSString *host = fetcher.request.URL.host;
  306. if (host == nil) {
  307. return NO;
  308. }
  309. NSArray *delayedForHost = [_delayedFetchersByHost objectForKey:host];
  310. NSUInteger idx = [delayedForHost indexOfObjectIdenticalTo:fetcher];
  311. BOOL isDelayed = (delayedForHost != nil) && (idx != NSNotFound);
  312. return isDelayed;
  313. }
  314. }
  315. - (BOOL)fetcherShouldBeginFetching:(GTMSessionFetcher *)fetcher {
  316. // Entry point from the fetcher
  317. NSURL *requestURL = fetcher.request.URL;
  318. NSString *host = requestURL.host;
  319. // Addresses "file:///path" case where localhost is the implicit host.
  320. if (host.length == 0 && [requestURL isFileURL]) {
  321. host = @"localhost";
  322. }
  323. if (host.length == 0) {
  324. // Data URIs legitimately have no host, reject other hostless URLs.
  325. GTMSESSION_ASSERT_DEBUG([[requestURL scheme] isEqual:@"data"], @"%@ lacks host", fetcher);
  326. return YES;
  327. }
  328. BOOL shouldBeginResult;
  329. @synchronized(self) {
  330. GTMSessionMonitorSynchronized(self);
  331. NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host];
  332. if (runningForHost != nil && [runningForHost indexOfObjectIdenticalTo:fetcher] != NSNotFound) {
  333. GTMSESSION_ASSERT_DEBUG(NO, @"%@ was already running", fetcher);
  334. return YES;
  335. }
  336. BOOL shouldRunNow = (fetcher.usingBackgroundSession || _maxRunningFetchersPerHost == 0 ||
  337. _maxRunningFetchersPerHost >
  338. [[self class] numberOfNonBackgroundSessionFetchers:runningForHost]);
  339. if (shouldRunNow) {
  340. [self addRunningFetcher:fetcher forHost:host];
  341. shouldBeginResult = YES;
  342. } else {
  343. [self addDelayedFetcher:fetcher forHost:host];
  344. shouldBeginResult = NO;
  345. }
  346. } // @synchronized(self)
  347. // We'll save the host that serves as the key for this fetcher's array
  348. // to avoid any chance of the underlying request changing, stranding
  349. // the fetcher in the wrong array
  350. fetcher.serviceHost = host;
  351. return shouldBeginResult;
  352. }
  353. - (void)startFetcher:(GTMSessionFetcher *)fetcher {
  354. [fetcher beginFetchMayDelay:NO mayAuthorize:YES mayDecorate:YES];
  355. }
  356. // Internal utility. Returns a fetcher's delegate if it's a dispatcher, or nil if the fetcher
  357. // is its own delegate (possibly via proxy) and has no dispatcher.
  358. - (GTMSessionFetcherSessionDelegateDispatcher *)delegateDispatcherForFetcher:
  359. (GTMSessionFetcher *)fetcher {
  360. GTMSessionCheckNotSynchronized(self);
  361. NSURLSession *fetcherSession = fetcher.session;
  362. if (fetcherSession) {
  363. id<NSURLSessionDelegate> fetcherDelegate = fetcherSession.delegate;
  364. // If the delegate is non-nil and claims to be a GTMSessionFetcher, there is no dispatcher;
  365. // assume the fetcher is the delegate or has been proxied (some third-party frameworks
  366. // are known to swizzle NSURLSession to proxy its delegate).
  367. BOOL hasDispatcher =
  368. (fetcherDelegate != nil && ![fetcherDelegate isKindOfClass:[GTMSessionFetcher class]]);
  369. if (hasDispatcher) {
  370. GTMSESSION_ASSERT_DEBUG(
  371. [fetcherDelegate isKindOfClass:[GTMSessionFetcherSessionDelegateDispatcher class]],
  372. @"Fetcher delegate class: %@", [fetcherDelegate class]);
  373. return (GTMSessionFetcherSessionDelegateDispatcher *)fetcherDelegate;
  374. }
  375. }
  376. return nil;
  377. }
  378. - (void)fetcherDidBeginFetching:(GTMSessionFetcher *)fetcher {
  379. // If this fetcher has a separate delegate with a shared session, then
  380. // this fetcher should be added to the delegate's map of tasks to fetchers.
  381. GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher =
  382. [self delegateDispatcherForFetcher:fetcher];
  383. if (delegateDispatcher) {
  384. GTMSESSION_ASSERT_DEBUG(fetcher.canShareSession, @"Inappropriate shared session: %@", fetcher);
  385. // There should already be a session, from this or a previous fetcher.
  386. //
  387. // Sanity check that the fetcher's session is the delegate's shared session.
  388. NSURLSession *sharedSession = delegateDispatcher.session;
  389. NSURLSession *fetcherSession = fetcher.session;
  390. GTMSESSION_ASSERT_DEBUG(sharedSession != nil, @"Missing delegate session: %@", fetcher);
  391. GTMSESSION_ASSERT_DEBUG(fetcherSession == sharedSession,
  392. @"Inconsistent session: %@ %@ (shared: %@)", fetcher, fetcherSession,
  393. sharedSession);
  394. if (sharedSession != nil && fetcherSession == sharedSession) {
  395. NSURLSessionTask *task = fetcher.sessionTask;
  396. GTMSESSION_ASSERT_DEBUG(task != nil, @"Missing session task: %@", fetcher);
  397. if (task) {
  398. [delegateDispatcher setFetcher:fetcher forTask:task];
  399. }
  400. }
  401. }
  402. }
  403. - (void)stopFetcher:(GTMSessionFetcher *)fetcher {
  404. [fetcher stopFetching];
  405. }
  406. - (void)fetcherDidStop:(GTMSessionFetcher *)fetcher {
  407. [self fetcherDidStop:fetcher callbacksPending:false];
  408. }
  409. - (void)fetcherDidStop:(GTMSessionFetcher *)fetcher callbacksPending:(BOOL) callbacksPending {
  410. // Entry point from the fetcher
  411. NSString *host = fetcher.serviceHost;
  412. if (!host) {
  413. // fetcher has been stopped previously
  414. return;
  415. }
  416. // This removeFetcher: invocation is a fallback; typically, fetchers are removed from the task
  417. // map when the task completes.
  418. if (!callbacksPending) {
  419. GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher =
  420. [self delegateDispatcherForFetcher:fetcher];
  421. [delegateDispatcher removeFetcher:fetcher];
  422. }
  423. NSMutableArray *fetchersToStart;
  424. @synchronized(self) {
  425. GTMSessionMonitorSynchronized(self);
  426. // If a test is waiting for all fetchers to stop, it needs to wait for this one
  427. // to invoke its callbacks on the callback queue.
  428. [_stoppedFetchersToWaitFor addObject:fetcher];
  429. NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host];
  430. [runningForHost removeObject:fetcher];
  431. NSMutableArray *delayedForHost = [_delayedFetchersByHost objectForKey:host];
  432. [delayedForHost removeObject:fetcher];
  433. while (delayedForHost.count > 0 &&
  434. [[self class] numberOfNonBackgroundSessionFetchers:runningForHost] <
  435. _maxRunningFetchersPerHost) {
  436. // Start another delayed fetcher running, scanning for the minimum
  437. // priority value, defaulting to FIFO for equal priorities
  438. GTMSessionFetcher *nextFetcher = nil;
  439. for (GTMSessionFetcher *delayedFetcher in delayedForHost) {
  440. if (nextFetcher == nil || delayedFetcher.servicePriority < nextFetcher.servicePriority) {
  441. nextFetcher = delayedFetcher;
  442. }
  443. }
  444. if (nextFetcher) {
  445. [self addRunningFetcher:nextFetcher forHost:host];
  446. runningForHost = [_runningFetchersByHost objectForKey:host];
  447. [delayedForHost removeObjectIdenticalTo:nextFetcher];
  448. if (!fetchersToStart) {
  449. fetchersToStart = [NSMutableArray array];
  450. }
  451. [fetchersToStart addObject:nextFetcher];
  452. }
  453. }
  454. if (runningForHost.count == 0) {
  455. // None left; remove the empty array
  456. [_runningFetchersByHost removeObjectForKey:host];
  457. }
  458. if (delayedForHost.count == 0) {
  459. [_delayedFetchersByHost removeObjectForKey:host];
  460. }
  461. } // @synchronized(self)
  462. // Start fetchers outside of the synchronized block to avoid a deadlock.
  463. for (GTMSessionFetcher *nextFetcher in fetchersToStart) {
  464. [self startFetcher:nextFetcher];
  465. }
  466. // The fetcher is no longer in the running or the delayed array,
  467. // so remove its host and thread properties
  468. fetcher.serviceHost = nil;
  469. }
  470. - (NSUInteger)numberOfFetchers {
  471. NSUInteger running = [self numberOfRunningFetchers];
  472. NSUInteger delayed = [self numberOfDelayedFetchers];
  473. return running + delayed;
  474. }
  475. - (NSUInteger)numberOfRunningFetchers {
  476. @synchronized(self) {
  477. GTMSessionMonitorSynchronized(self);
  478. NSUInteger sum = 0;
  479. for (NSString *host in _runningFetchersByHost) {
  480. NSArray *fetchers = [_runningFetchersByHost objectForKey:host];
  481. sum += fetchers.count;
  482. }
  483. return sum;
  484. }
  485. }
  486. - (NSUInteger)numberOfDelayedFetchers {
  487. @synchronized(self) {
  488. GTMSessionMonitorSynchronized(self);
  489. NSUInteger sum = 0;
  490. for (NSString *host in _delayedFetchersByHost) {
  491. NSArray *fetchers = [_delayedFetchersByHost objectForKey:host];
  492. sum += fetchers.count;
  493. }
  494. return sum;
  495. }
  496. }
  497. - (NSArray *)issuedFetchers {
  498. @synchronized(self) {
  499. GTMSessionMonitorSynchronized(self);
  500. NSMutableArray *allFetchers = [NSMutableArray array];
  501. void (^accumulateFetchers)(id, id, BOOL *) =
  502. ^(NSString *host, NSArray *fetchersForHost, BOOL *stop) {
  503. [allFetchers addObjectsFromArray:fetchersForHost];
  504. };
  505. [_runningFetchersByHost enumerateKeysAndObjectsUsingBlock:accumulateFetchers];
  506. [_delayedFetchersByHost enumerateKeysAndObjectsUsingBlock:accumulateFetchers];
  507. GTMSESSION_ASSERT_DEBUG(allFetchers.count == [NSSet setWithArray:allFetchers].count,
  508. @"Fetcher appears multiple times\n running: %@\n delayed: %@",
  509. _runningFetchersByHost, _delayedFetchersByHost);
  510. return allFetchers.count > 0 ? allFetchers : nil;
  511. }
  512. }
  513. - (NSArray *)issuedFetchersWithRequestURL:(NSURL *)requestURL {
  514. NSString *host = requestURL.host;
  515. if (host.length == 0) return nil;
  516. NSURL *targetURL = [requestURL absoluteURL];
  517. NSArray *allFetchers = [self issuedFetchers];
  518. NSIndexSet *indexes = [allFetchers
  519. indexesOfObjectsPassingTest:^BOOL(GTMSessionFetcher *fetcher, NSUInteger idx, BOOL *stop) {
  520. NSURL *fetcherURL = [fetcher.request.URL absoluteURL];
  521. return [fetcherURL isEqual:targetURL];
  522. }];
  523. NSArray *result = nil;
  524. if (indexes.count > 0) {
  525. result = [allFetchers objectsAtIndexes:indexes];
  526. }
  527. return result;
  528. }
  529. - (void)stopAllFetchers {
  530. NSArray *delayedFetchersByHost;
  531. NSArray *runningFetchersByHost;
  532. @synchronized(self) {
  533. GTMSessionMonitorSynchronized(self);
  534. // Set the time barrier so fetchers know not to call back even if
  535. // the stop calls below occur after the fetchers naturally
  536. // stopped and so were removed from _runningFetchersByHost,
  537. // but while the callbacks were already enqueued before stopAllFetchers
  538. // was invoked.
  539. _stoppedAllFetchersDate = [[NSDate alloc] init];
  540. // Remove fetchers from the delayed list to avoid fetcherDidStop: from
  541. // starting more fetchers running as a side effect of stopping one
  542. delayedFetchersByHost = _delayedFetchersByHost.allValues;
  543. [_delayedFetchersByHost removeAllObjects];
  544. runningFetchersByHost = _runningFetchersByHost.allValues;
  545. [_runningFetchersByHost removeAllObjects];
  546. }
  547. for (NSArray *delayedForHost in delayedFetchersByHost) {
  548. for (GTMSessionFetcher *fetcher in delayedForHost) {
  549. [self stopFetcher:fetcher];
  550. }
  551. }
  552. for (NSArray *runningForHost in runningFetchersByHost) {
  553. for (GTMSessionFetcher *fetcher in runningForHost) {
  554. [self stopFetcher:fetcher];
  555. }
  556. }
  557. }
  558. - (NSDate *)stoppedAllFetchersDate {
  559. @synchronized(self) {
  560. GTMSessionMonitorSynchronized(self);
  561. return _stoppedAllFetchersDate;
  562. }
  563. }
  564. #pragma mark Accessors
  565. - (BOOL)reuseSession {
  566. @synchronized(self) {
  567. GTMSessionMonitorSynchronized(self);
  568. return _delegateDispatcher != nil;
  569. }
  570. }
  571. - (void)setReuseSession:(BOOL)shouldReuse {
  572. @synchronized(self) {
  573. GTMSessionMonitorSynchronized(self);
  574. BOOL wasReusing = (_delegateDispatcher != nil);
  575. if (shouldReuse != wasReusing) {
  576. [self abandonDispatcher];
  577. if (shouldReuse) {
  578. _delegateDispatcher = [[GTMSessionFetcherSessionDelegateDispatcher alloc]
  579. initWithParentService:self
  580. sessionDiscardInterval:_unusedSessionTimeout];
  581. } else {
  582. _delegateDispatcher = nil;
  583. }
  584. }
  585. }
  586. }
  587. - (void)resetSession {
  588. GTMSessionCheckNotSynchronized(self);
  589. os_unfair_lock_lock(&_sessionCreationLock);
  590. @synchronized(self) {
  591. GTMSessionMonitorSynchronized(self);
  592. [self resetSessionInternal];
  593. }
  594. os_unfair_lock_unlock(&_sessionCreationLock);
  595. }
  596. - (void)resetSessionInternal {
  597. GTMSessionCheckSynchronized(self);
  598. // The old dispatchers may be retained as delegates of any ongoing sessions by those sessions.
  599. if (_delegateDispatcher) {
  600. [self abandonDispatcher];
  601. _delegateDispatcher = [[GTMSessionFetcherSessionDelegateDispatcher alloc]
  602. initWithParentService:self
  603. sessionDiscardInterval:_unusedSessionTimeout];
  604. }
  605. }
  606. - (void)resetSessionForDispatcherDiscardTimer:(NSTimer *)timer {
  607. GTMSessionCheckNotSynchronized(self);
  608. os_unfair_lock_lock(&_sessionCreationLock);
  609. @synchronized(self) {
  610. GTMSessionMonitorSynchronized(self);
  611. if (_delegateDispatcher.discardTimer == timer) {
  612. // If the delegate dispatcher's current discardTimer is the same object as the timer
  613. // that fired, no fetcher has recently attempted to start using the session by calling
  614. // startSessionUsage, which invalidates and nils out the timer.
  615. [self resetSessionInternal];
  616. } else {
  617. // A fetcher has invalidated the timer between its triggering and now, potentially
  618. // meaning a fetcher has requested access to the NSURLSession, and may be in the process
  619. // of starting a new task. The dispatcher should not be abandoned, as this can lead
  620. // to a race condition between calling -finishTasksAndInvalidate on the NSURLSession
  621. // and the fetcher attempting to create a new task.
  622. }
  623. }
  624. os_unfair_lock_unlock(&_sessionCreationLock);
  625. }
  626. - (NSTimeInterval)unusedSessionTimeout {
  627. @synchronized(self) {
  628. GTMSessionMonitorSynchronized(self);
  629. return _unusedSessionTimeout;
  630. }
  631. }
  632. - (void)setUnusedSessionTimeout:(NSTimeInterval)timeout {
  633. @synchronized(self) {
  634. GTMSessionMonitorSynchronized(self);
  635. _unusedSessionTimeout = timeout;
  636. _delegateDispatcher.discardInterval = timeout;
  637. }
  638. }
  639. // This method should be called inside of @synchronized(self)
  640. - (void)abandonDispatcher {
  641. GTMSessionCheckSynchronized(self);
  642. [_delegateDispatcher abandon];
  643. }
  644. - (NSDictionary *)runningFetchersByHost {
  645. @synchronized(self) {
  646. GTMSessionMonitorSynchronized(self);
  647. return [_runningFetchersByHost copy];
  648. }
  649. }
  650. - (void)setRunningFetchersByHost:(NSDictionary *)dict {
  651. @synchronized(self) {
  652. GTMSessionMonitorSynchronized(self);
  653. _runningFetchersByHost = [dict mutableCopy];
  654. }
  655. }
  656. - (NSDictionary *)delayedFetchersByHost {
  657. @synchronized(self) {
  658. GTMSessionMonitorSynchronized(self);
  659. return [_delayedFetchersByHost copy];
  660. }
  661. }
  662. - (void)setDelayedFetchersByHost:(NSDictionary *)dict {
  663. @synchronized(self) {
  664. GTMSessionMonitorSynchronized(self);
  665. _delayedFetchersByHost = [dict mutableCopy];
  666. }
  667. }
  668. #pragma clang diagnostic push
  669. #pragma clang diagnostic ignored "-Wdeprecated"
  670. - (id<GTMFetcherAuthorizationProtocol>)authorizer {
  671. @synchronized(self) {
  672. GTMSessionMonitorSynchronized(self);
  673. return _authorizer;
  674. }
  675. }
  676. - (void)setAuthorizer:(id<GTMFetcherAuthorizationProtocol>)obj {
  677. @synchronized(self) {
  678. GTMSessionMonitorSynchronized(self);
  679. if (obj != _authorizer) {
  680. [self detachAuthorizer];
  681. }
  682. _authorizer = obj;
  683. }
  684. // Use the fetcher service for the authorization fetches if the auth
  685. // object supports fetcher services
  686. if ([obj respondsToSelector:@selector(setFetcherService:)]) {
  687. [obj setFetcherService:self];
  688. }
  689. }
  690. #pragma clang diagnostic pop
  691. // This should be called inside a @synchronized(self) block except during dealloc.
  692. - (void)detachAuthorizer {
  693. // This method is called by the fetcher service's dealloc and setAuthorizer:
  694. // methods; do not override.
  695. //
  696. // The fetcher service retains the authorizer, and the authorizer has a
  697. // weak pointer to the fetcher service (a non-zeroing pointer for
  698. // compatibility with iOS 4 and Mac OS X 10.5/10.6.)
  699. //
  700. // When this fetcher service no longer uses the authorizer, we want to remove
  701. // the authorizer's dependence on the fetcher service. Authorizers can still
  702. // function without a fetcher service.
  703. if ([_authorizer respondsToSelector:@selector(fetcherService)]) {
  704. id authFetcherService = [_authorizer fetcherService];
  705. if (authFetcherService == self) {
  706. [_authorizer setFetcherService:nil];
  707. }
  708. }
  709. }
  710. - (nonnull dispatch_queue_t)callbackQueue {
  711. @synchronized(self) {
  712. GTMSessionMonitorSynchronized(self);
  713. return _callbackQueue;
  714. } // @synchronized(self)
  715. }
  716. - (void)setCallbackQueue:(dispatch_queue_t)queue {
  717. [self setCallbackQueue:queue isConcurrent:NO];
  718. }
  719. - (void)setConcurrentCallbackQueue:(dispatch_queue_t)queue {
  720. [self setCallbackQueue:queue isConcurrent:YES];
  721. }
  722. - (void)setCallbackQueue:(dispatch_queue_t)queue isConcurrent:(BOOL)isConcurrent {
  723. @synchronized(self) {
  724. GTMSessionMonitorSynchronized(self);
  725. #if DEBUG
  726. // Warn when changing from a concurrent queue to a serial queue.
  727. if (_callbackQueueIsConcurrent && (!isConcurrent || !queue)) {
  728. GTMSESSION_LOG_DEBUG(
  729. @"WARNING: Resetting the service callback queue from concurrent to serial");
  730. }
  731. #endif // DEBUG
  732. _callbackQueue = queue ?: dispatch_get_main_queue();
  733. _callbackQueueIsConcurrent = queue ? isConcurrent : NO;
  734. } // @synchronized(self)
  735. }
  736. - (NSOperationQueue *)sessionDelegateQueue {
  737. @synchronized(self) {
  738. GTMSessionMonitorSynchronized(self);
  739. return _delegateQueue;
  740. } // @synchronized(self)
  741. }
  742. - (void)setSessionDelegateQueue:(NSOperationQueue *)queue {
  743. @synchronized(self) {
  744. GTMSessionMonitorSynchronized(self);
  745. _delegateQueue = queue ?: [NSOperationQueue mainQueue];
  746. } // @synchronized(self)
  747. }
  748. - (nullable NSString *)userAgent {
  749. @synchronized(self) {
  750. return _userAgentProvider.userAgent;
  751. }
  752. }
  753. - (void)setUserAgent:(nullable NSString *)userAgent {
  754. @synchronized(self) {
  755. if (userAgent) {
  756. _userAgentProvider = [[GTMUserAgentStringProvider alloc]
  757. initWithUserAgentString:(NSString *_Nonnull)userAgent];
  758. } else {
  759. // Support setUserAgent:nil to disable `GTMStandardUserAgentProvider`.
  760. _userAgentProvider = nil;
  761. }
  762. }
  763. }
  764. - (nullable id<GTMUserAgentProvider>)userAgentProvider {
  765. @synchronized(self) {
  766. return _userAgentProvider;
  767. }
  768. }
  769. - (void)setUserAgentProvider:(nullable id<GTMUserAgentProvider>)userAgentProvider {
  770. @synchronized(self) {
  771. _userAgentProvider = userAgentProvider;
  772. }
  773. }
  774. - (NSOperationQueue *)delegateQueue {
  775. // Provided for compatibility with the old fetcher service. The gtm-oauth2 code respects
  776. // any custom delegate queue for calling the app.
  777. return nil;
  778. }
  779. + (NSUInteger)numberOfNonBackgroundSessionFetchers:(NSArray *)fetchers {
  780. NSUInteger sum = 0;
  781. for (GTMSessionFetcher *fetcher in fetchers) {
  782. if (!fetcher.usingBackgroundSession) {
  783. ++sum;
  784. }
  785. }
  786. return sum;
  787. }
  788. @end
  789. @implementation GTMSessionFetcherService (TestingSupport)
  790. + (instancetype)mockFetcherServiceWithFakedData:(NSData *)fakedDataOrNil
  791. fakedError:(NSError *)fakedErrorOrNil {
  792. #if !GTM_DISABLE_FETCHER_TEST_BLOCK
  793. NSURL *url = [NSURL URLWithString:@"http://example.invalid"];
  794. NSHTTPURLResponse *fakedResponse =
  795. [[NSHTTPURLResponse alloc] initWithURL:url
  796. statusCode:(fakedErrorOrNil ? 500 : 200)HTTPVersion:@"HTTP/1.1"
  797. headerFields:nil];
  798. return [self mockFetcherServiceWithFakedData:fakedDataOrNil
  799. fakedResponse:fakedResponse
  800. fakedError:fakedErrorOrNil];
  801. #else
  802. GTMSESSION_ASSERT_DEBUG(0, @"Test blocks disabled");
  803. return nil;
  804. #endif // GTM_DISABLE_FETCHER_TEST_BLOCK
  805. }
  806. + (instancetype)mockFetcherServiceWithFakedData:(NSData *)fakedDataOrNil
  807. fakedResponse:(NSHTTPURLResponse *)fakedResponse
  808. fakedError:(NSError *)fakedErrorOrNil {
  809. #if !GTM_DISABLE_FETCHER_TEST_BLOCK
  810. GTMSessionFetcherService *service = [[self alloc] init];
  811. service.allowedInsecureSchemes = @[ @"http" ];
  812. service.testBlock =
  813. ^(GTMSessionFetcher *fetcherToTest, GTMSessionFetcherTestResponse testResponse) {
  814. testResponse(fakedResponse, fakedDataOrNil, fakedErrorOrNil);
  815. };
  816. return service;
  817. #else
  818. GTMSESSION_ASSERT_DEBUG(0, @"Test blocks disabled");
  819. return nil;
  820. #endif // GTM_DISABLE_FETCHER_TEST_BLOCK
  821. }
  822. #pragma mark Synchronous Wait for Unit Testing
  823. - (BOOL)waitForCompletionOfAllFetchersWithTimeout:(NSTimeInterval)timeoutInSeconds {
  824. NSDate *giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds];
  825. _stoppedFetchersToWaitFor = [NSMutableArray array];
  826. BOOL shouldSpinRunLoop = [NSThread isMainThread];
  827. const NSTimeInterval kSpinInterval = 0.001;
  828. BOOL didTimeOut = NO;
  829. while (([self numberOfFetchers] > 0 || _stoppedFetchersToWaitFor.count > 0)) {
  830. didTimeOut = [giveUpDate timeIntervalSinceNow] < 0;
  831. if (didTimeOut) break;
  832. GTMSessionFetcher *stoppedFetcher = _stoppedFetchersToWaitFor.firstObject;
  833. if (stoppedFetcher) {
  834. [_stoppedFetchersToWaitFor removeObject:stoppedFetcher];
  835. [stoppedFetcher waitForCompletionWithTimeout:10.0 * kSpinInterval];
  836. }
  837. if (shouldSpinRunLoop) {
  838. NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:kSpinInterval];
  839. [[NSRunLoop currentRunLoop] runUntilDate:stopDate];
  840. } else {
  841. [NSThread sleepForTimeInterval:kSpinInterval];
  842. }
  843. }
  844. _stoppedFetchersToWaitFor = nil;
  845. return !didTimeOut;
  846. }
  847. @end
  848. @implementation GTMSessionFetcherSessionDelegateDispatcher {
  849. __weak GTMSessionFetcherService *_parentService;
  850. NSURLSession *_session;
  851. // The task map maps NSURLSessionTasks to GTMSessionFetchers
  852. NSMutableDictionary *_taskToFetcherMap;
  853. // The discard timer will invalidate sessions after the session's last task completes.
  854. NSTimer *_discardTimer;
  855. NSTimeInterval _discardInterval;
  856. }
  857. @synthesize discardInterval = _discardInterval, session = _session;
  858. - (instancetype)init {
  859. [self doesNotRecognizeSelector:_cmd];
  860. return nil;
  861. }
  862. - (instancetype)initWithParentService:(GTMSessionFetcherService *)parentService
  863. sessionDiscardInterval:(NSTimeInterval)discardInterval {
  864. self = [super init];
  865. if (self) {
  866. _discardInterval = discardInterval;
  867. _parentService = parentService;
  868. }
  869. return self;
  870. }
  871. - (NSString *)description {
  872. return
  873. [NSString stringWithFormat:@"%@ %p %@ %@", [self class], self, _session ?: @"<no session>",
  874. _taskToFetcherMap.count > 0 ? _taskToFetcherMap : @"<no tasks>"];
  875. }
  876. - (NSTimer *)discardTimer {
  877. GTMSessionCheckNotSynchronized(self);
  878. @synchronized(self) {
  879. return _discardTimer;
  880. }
  881. }
  882. // This method should be called inside of a @synchronized(self) block.
  883. - (void)startDiscardTimer {
  884. GTMSessionCheckSynchronized(self);
  885. [_discardTimer invalidate];
  886. _discardTimer = nil;
  887. if (_discardInterval > 0) {
  888. _discardTimer = [NSTimer timerWithTimeInterval:_discardInterval
  889. target:self
  890. selector:@selector(discardTimerFired:)
  891. userInfo:nil
  892. repeats:NO];
  893. [_discardTimer setTolerance:(_discardInterval / 10)];
  894. [[NSRunLoop mainRunLoop] addTimer:_discardTimer forMode:NSRunLoopCommonModes];
  895. }
  896. }
  897. // This method should be called inside of a @synchronized(self) block.
  898. - (void)destroyDiscardTimer {
  899. GTMSessionCheckSynchronized(self);
  900. [_discardTimer invalidate];
  901. _discardTimer = nil;
  902. }
  903. - (void)discardTimerFired:(NSTimer *)timer {
  904. GTMSessionFetcherService *service;
  905. @synchronized(self) {
  906. GTMSessionMonitorSynchronized(self);
  907. NSUInteger numberOfTasks = _taskToFetcherMap.count;
  908. if (numberOfTasks == 0) {
  909. service = _parentService;
  910. }
  911. }
  912. // Inform the service that the discard timer has fired, and should check whether the
  913. // service can abandon us. -resetSession cannot be called directly, as there is a
  914. // race condition that must be guarded against with the NSURLSession being returned
  915. // from sessionForFetcherCreation outside other locks. The service can take steps
  916. // to prevent resetting the session if that has occurred.
  917. //
  918. // The service must be called from outside the @synchronized block.
  919. [service resetSessionForDispatcherDiscardTimer:timer];
  920. }
  921. - (void)abandon {
  922. @synchronized(self) {
  923. GTMSessionMonitorSynchronized(self);
  924. [self destroySessionAndTimer];
  925. }
  926. }
  927. - (void)startSessionUsage {
  928. @synchronized(self) {
  929. GTMSessionMonitorSynchronized(self);
  930. [self destroyDiscardTimer];
  931. }
  932. }
  933. // This method should be called inside of a @synchronized(self) block.
  934. - (void)destroySessionAndTimer {
  935. GTMSessionCheckSynchronized(self);
  936. [self destroyDiscardTimer];
  937. // Break any retain cycle from the session holding the delegate.
  938. [_session finishTasksAndInvalidate];
  939. // Immediately clear the session so no new task may be issued with it.
  940. //
  941. // The _taskToFetcherMap needs to stay valid until the outstanding tasks finish.
  942. _session = nil;
  943. }
  944. - (void)setFetcher:(GTMSessionFetcher *)fetcher forTask:(NSURLSessionTask *)task {
  945. GTMSESSION_ASSERT_DEBUG(fetcher != nil, @"missing fetcher");
  946. @synchronized(self) {
  947. GTMSessionMonitorSynchronized(self);
  948. if (_taskToFetcherMap == nil) {
  949. _taskToFetcherMap = [[NSMutableDictionary alloc] init];
  950. }
  951. if (fetcher) {
  952. [_taskToFetcherMap setObject:fetcher forKey:task];
  953. [self destroyDiscardTimer];
  954. }
  955. }
  956. }
  957. - (void)removeFetcher:(GTMSessionFetcher *)fetcher {
  958. @synchronized(self) {
  959. GTMSessionMonitorSynchronized(self);
  960. // Typically, a fetcher should be removed when its task invokes
  961. // URLSession:task:didCompleteWithError:.
  962. //
  963. // When fetching with a testBlock, though, the task completed delegate
  964. // method may not be invoked, requiring cleanup here.
  965. NSArray *tasks = [_taskToFetcherMap allKeysForObject:fetcher];
  966. GTMSESSION_ASSERT_DEBUG(tasks.count <= 1, @"fetcher task not unmapped: %@", tasks);
  967. [_taskToFetcherMap removeObjectsForKeys:tasks];
  968. if (_taskToFetcherMap.count == 0) {
  969. [self startDiscardTimer];
  970. }
  971. }
  972. }
  973. // This helper method provides synchronized access to the task map for the delegate
  974. // methods below.
  975. - (id)fetcherForTask:(NSURLSessionTask *)task {
  976. @synchronized(self) {
  977. GTMSessionMonitorSynchronized(self);
  978. return [_taskToFetcherMap objectForKey:task];
  979. }
  980. }
  981. - (void)removeTaskFromMap:(NSURLSessionTask *)task {
  982. @synchronized(self) {
  983. GTMSessionMonitorSynchronized(self);
  984. [_taskToFetcherMap removeObjectForKey:task];
  985. }
  986. }
  987. - (void)setSession:(NSURLSession *)session {
  988. @synchronized(self) {
  989. GTMSessionMonitorSynchronized(self);
  990. _session = session;
  991. }
  992. }
  993. - (NSURLSession *)session {
  994. @synchronized(self) {
  995. GTMSessionMonitorSynchronized(self);
  996. return _session;
  997. }
  998. }
  999. - (NSTimeInterval)discardInterval {
  1000. @synchronized(self) {
  1001. GTMSessionMonitorSynchronized(self);
  1002. return _discardInterval;
  1003. }
  1004. }
  1005. - (void)setDiscardInterval:(NSTimeInterval)interval {
  1006. @synchronized(self) {
  1007. GTMSessionMonitorSynchronized(self);
  1008. _discardInterval = interval;
  1009. }
  1010. }
  1011. // NSURLSessionDelegate protocol methods.
  1012. // - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session;
  1013. //
  1014. // TODO(seh): How do we route this to an appropriate fetcher?
  1015. - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error {
  1016. GTMSESSION_LOG_DEBUG_VERBOSE(@"%@ %p URLSession:%@ didBecomeInvalidWithError:%@", [self class],
  1017. self, session, error);
  1018. NSDictionary *localTaskToFetcherMap;
  1019. @synchronized(self) {
  1020. GTMSessionMonitorSynchronized(self);
  1021. _session = nil;
  1022. localTaskToFetcherMap = [_taskToFetcherMap copy];
  1023. }
  1024. // Any "suspended" tasks may not have received callbacks from NSURLSession when the session
  1025. // completes; we'll call them now.
  1026. [localTaskToFetcherMap enumerateKeysAndObjectsUsingBlock:^(
  1027. NSURLSessionTask *task, GTMSessionFetcher *fetcher, BOOL *stop) {
  1028. if (fetcher.session == session) {
  1029. // Our delegate method URLSession:task:didCompleteWithError: will rely on
  1030. // _taskToFetcherMap so that should still contain this fetcher.
  1031. NSError *canceledError = [NSError errorWithDomain:NSURLErrorDomain
  1032. code:NSURLErrorCancelled
  1033. userInfo:nil];
  1034. [self URLSession:session task:task didCompleteWithError:canceledError];
  1035. } else {
  1036. GTMSESSION_ASSERT_DEBUG(0, @"Unexpected session in fetcher: %@ has %@ (expected %@)", fetcher,
  1037. fetcher.session, session);
  1038. }
  1039. }];
  1040. // Our tests rely on this notification to know the session discard timer fired.
  1041. NSDictionary *userInfo = @{kGTMSessionFetcherServiceSessionKey : session};
  1042. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  1043. [nc postNotificationName:kGTMSessionFetcherServiceSessionBecameInvalidNotification
  1044. object:_parentService
  1045. userInfo:userInfo];
  1046. }
  1047. #pragma mark - NSURLSessionTaskDelegate
  1048. // NSURLSessionTaskDelegate protocol methods.
  1049. //
  1050. // We won't test here if the fetcher responds to these since we only want this
  1051. // class to implement the same delegate methods the fetcher does (so NSURLSession's
  1052. // tests for respondsToSelector: will have the same result whether the session
  1053. // delegate is the fetcher or this dispatcher.)
  1054. - (void)URLSession:(NSURLSession *)session
  1055. task:(NSURLSessionTask *)task
  1056. willPerformHTTPRedirection:(NSHTTPURLResponse *)response
  1057. newRequest:(NSURLRequest *)request
  1058. completionHandler:(void (^)(NSURLRequest *))completionHandler {
  1059. id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
  1060. [fetcher URLSession:session
  1061. task:task
  1062. willPerformHTTPRedirection:response
  1063. newRequest:request
  1064. completionHandler:completionHandler];
  1065. }
  1066. - (void)URLSession:(NSURLSession *)session
  1067. task:(NSURLSessionTask *)task
  1068. didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
  1069. completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))handler {
  1070. id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
  1071. [fetcher URLSession:session task:task didReceiveChallenge:challenge completionHandler:handler];
  1072. }
  1073. - (void)URLSession:(NSURLSession *)session
  1074. task:(NSURLSessionTask *)task
  1075. needNewBodyStream:(void (^)(NSInputStream *bodyStream))handler {
  1076. id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
  1077. [fetcher URLSession:session task:task needNewBodyStream:handler];
  1078. }
  1079. - (void)URLSession:(NSURLSession *)session
  1080. task:(NSURLSessionTask *)task
  1081. didSendBodyData:(int64_t)bytesSent
  1082. totalBytesSent:(int64_t)totalBytesSent
  1083. totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
  1084. id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
  1085. [fetcher URLSession:session
  1086. task:task
  1087. didSendBodyData:bytesSent
  1088. totalBytesSent:totalBytesSent
  1089. totalBytesExpectedToSend:totalBytesExpectedToSend];
  1090. }
  1091. - (void)URLSession:(NSURLSession *)session
  1092. task:(NSURLSessionTask *)task
  1093. didCompleteWithError:(NSError *)error {
  1094. id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
  1095. // This is the usual way tasks are removed from the task map.
  1096. [self removeTaskFromMap:task];
  1097. [fetcher URLSession:session task:task didCompleteWithError:error];
  1098. }
  1099. - (void)URLSession:(NSURLSession *)session
  1100. task:(NSURLSessionTask *)task
  1101. didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
  1102. API_AVAILABLE(ios(10.0), macosx(10.12), tvos(10.0), watchos(6.0)) {
  1103. id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
  1104. [fetcher URLSession:session task:task didFinishCollectingMetrics:metrics];
  1105. }
  1106. // NSURLSessionDataDelegate protocol methods.
  1107. - (void)URLSession:(NSURLSession *)session
  1108. dataTask:(NSURLSessionDataTask *)dataTask
  1109. didReceiveResponse:(NSURLResponse *)response
  1110. completionHandler:(void (^)(NSURLSessionResponseDisposition))handler {
  1111. id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask];
  1112. [fetcher URLSession:session
  1113. dataTask:dataTask
  1114. didReceiveResponse:response
  1115. completionHandler:handler];
  1116. }
  1117. - (void)URLSession:(NSURLSession *)session
  1118. dataTask:(NSURLSessionDataTask *)dataTask
  1119. didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask {
  1120. id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask];
  1121. GTMSESSION_ASSERT_DEBUG(fetcher != nil, @"Missing fetcher for %@", dataTask);
  1122. [self removeTaskFromMap:dataTask];
  1123. if (fetcher) {
  1124. GTMSESSION_ASSERT_DEBUG([fetcher isKindOfClass:[GTMSessionFetcher class]],
  1125. @"Expecting GTMSessionFetcher");
  1126. [self setFetcher:(GTMSessionFetcher *)fetcher forTask:downloadTask];
  1127. }
  1128. [fetcher URLSession:session dataTask:dataTask didBecomeDownloadTask:downloadTask];
  1129. }
  1130. - (void)URLSession:(NSURLSession *)session
  1131. dataTask:(NSURLSessionDataTask *)dataTask
  1132. didReceiveData:(NSData *)data {
  1133. id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask];
  1134. [fetcher URLSession:session dataTask:dataTask didReceiveData:data];
  1135. }
  1136. - (void)URLSession:(NSURLSession *)session
  1137. dataTask:(NSURLSessionDataTask *)dataTask
  1138. willCacheResponse:(NSCachedURLResponse *)proposedResponse
  1139. completionHandler:(void (^)(NSCachedURLResponse *))handler {
  1140. id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask];
  1141. [fetcher URLSession:session
  1142. dataTask:dataTask
  1143. willCacheResponse:proposedResponse
  1144. completionHandler:handler];
  1145. }
  1146. // NSURLSessionDownloadDelegate protocol methods.
  1147. - (void)URLSession:(NSURLSession *)session
  1148. downloadTask:(NSURLSessionDownloadTask *)downloadTask
  1149. didFinishDownloadingToURL:(NSURL *)location {
  1150. id<NSURLSessionDownloadDelegate> fetcher = [self fetcherForTask:downloadTask];
  1151. [fetcher URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
  1152. }
  1153. - (void)URLSession:(NSURLSession *)session
  1154. downloadTask:(NSURLSessionDownloadTask *)downloadTask
  1155. didWriteData:(int64_t)bytesWritten
  1156. totalBytesWritten:(int64_t)totalWritten
  1157. totalBytesExpectedToWrite:(int64_t)totalExpected {
  1158. id<NSURLSessionDownloadDelegate> fetcher = [self fetcherForTask:downloadTask];
  1159. [fetcher URLSession:session
  1160. downloadTask:downloadTask
  1161. didWriteData:bytesWritten
  1162. totalBytesWritten:totalWritten
  1163. totalBytesExpectedToWrite:totalExpected];
  1164. }
  1165. - (void)URLSession:(NSURLSession *)session
  1166. downloadTask:(NSURLSessionDownloadTask *)downloadTask
  1167. didResumeAtOffset:(int64_t)fileOffset
  1168. expectedTotalBytes:(int64_t)expectedTotalBytes {
  1169. id<NSURLSessionDownloadDelegate> fetcher = [self fetcherForTask:downloadTask];
  1170. [fetcher URLSession:session
  1171. downloadTask:downloadTask
  1172. didResumeAtOffset:fileOffset
  1173. expectedTotalBytes:expectedTotalBytes];
  1174. }
  1175. @end