@@ -706,11 +706,16 @@ - (void)beginFetchMayDelay:(BOOL)mayDelay
706
706
mayDelay = NO;
707
707
}
708
708
if (mayDelay && _service) {
709
- // Set the delayed state so there can't be a race incase between it getting queued in
710
- // the `fetcherShouldBeginFetching:` call and some other thread completing a different
711
- // fetch and thus starting this one (if we were trying to set the state based on the
712
- // return result).
713
- @synchronized(self) { _delayState = kDelayStateServiceDelayed; }
709
+ BOOL savedStoppedState;
710
+ @synchronized(self) {
711
+ savedStoppedState = _userStoppedFetching;
712
+ // Set the delayed state so there can't be a race incase between it getting queued in
713
+ // the `fetcherShouldBeginFetching:` call and some other thread completing a different
714
+ // fetch and thus starting this one. If we were trying to set the state based on the
715
+ // return result, there would be a small window for that race.
716
+ _delayState = kDelayStateServiceDelayed;
717
+ }
718
+
714
719
BOOL shouldFetchNow = [_service fetcherShouldBeginFetching:self];
715
720
if (!shouldFetchNow) {
716
721
// The fetch is deferred, but will happen later.
@@ -726,8 +731,21 @@ - (void)beginFetchMayDelay:(BOOL)mayDelay
726
731
}
727
732
return;
728
733
}
729
- // Per comment above, correct state since it wasn't delayed.
730
- @synchronized(self) { _delayState = kDelayStateNotDelayed; }
734
+
735
+ @synchronized(self) {
736
+ // Per comment above, correct state since it wasn't delayed.
737
+ _delayState = kDelayStateNotDelayed;
738
+
739
+ // If a `-stopFetching` came in while the service check was made, then the side effect of the
740
+ // state setting caused the handler (if needed) to already be made, so there we want to just
741
+ // exit and not continue the fetch.
742
+ //
743
+ // TODO(thomasvl): If `savedStoppedState` was already `YES`, then it's a more general problem
744
+ // which will be handled elsewhere/later.
745
+ if (!savedStoppedState && savedStoppedState != _userStoppedFetching) {
746
+ return;
747
+ }
748
+ }
731
749
}
732
750
733
751
if ([fetchRequest valueForHTTPHeaderField:@"User-Agent"] == nil) {
@@ -2051,14 +2069,28 @@ - (void)forgetSessionIdentifierForFetcherWithoutSyncCheck {
2051
2069
2052
2070
// External stop method
2053
2071
- (void)stopFetching {
2072
+ BOOL triggerCallback;
2054
2073
@synchronized(self) {
2055
2074
GTMSessionMonitorSynchronized(self);
2056
2075
2057
2076
// Prevent enqueued callbacks from executing. The completion handler will still execute if
2058
2077
// the property `stopFetchingTriggersCompletionHandler` is `YES`.
2059
2078
_userStoppedFetching = YES;
2079
+
2080
+ // `stopFetchReleasingCallbacks:` will dequeue it if there is a sevice throttled
2081
+ // delay, so the canceled callback needs to be directly triggered since the serivce
2082
+ // won't attempt to restart it.
2083
+ triggerCallback = _delayState == kDelayStateServiceDelayed && self.stopFetchingTriggersCompletionHandler;
2060
2084
} // @synchronized(self)
2061
- [self stopFetchReleasingCallbacks:!self.stopFetchingTriggersCompletionHandler];
2085
+
2086
+ if (triggerCallback) {
2087
+ NSError *error = [NSError errorWithDomain:kGTMSessionFetcherErrorDomain
2088
+ code:GTMSessionFetcherErrorUserCancelled
2089
+ userInfo:nil];
2090
+ [self finishWithError:error shouldRetry:NO];
2091
+ } else {
2092
+ [self stopFetchReleasingCallbacks:!self.stopFetchingTriggersCompletionHandler];
2093
+ }
2062
2094
}
2063
2095
2064
2096
// Cancel the fetch of the URL that's currently in progress.
0 commit comments