diff options
author | Muxi Yan <muxi@users.noreply.github.com> | 2017-08-22 12:47:28 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-08-22 12:47:28 -0700 |
commit | 72827fb8beabc0cf3e6cf669da580ab7c13c53c2 (patch) | |
tree | 75db43cd72d0ab0a40949d8725b64f707d5953ff /src | |
parent | e3d6a9a8d45d3180e28a529339f5661fe2526e0a (diff) | |
parent | 4e1b26ec1bc0278e5ed2df028510ea1d6c65ec39 (diff) |
Merge pull request #12225 from muxi/fix-already-finished
Fix undefined behavior of using dispatch_once_t as instance variable
Diffstat (limited to 'src')
-rw-r--r-- | src/objective-c/RxLibrary/GRXConcurrentWriteable.m | 38 |
1 files changed, 31 insertions, 7 deletions
diff --git a/src/objective-c/RxLibrary/GRXConcurrentWriteable.m b/src/objective-c/RxLibrary/GRXConcurrentWriteable.m index cb5d0a63f0..bbfe491783 100644 --- a/src/objective-c/RxLibrary/GRXConcurrentWriteable.m +++ b/src/objective-c/RxLibrary/GRXConcurrentWriteable.m @@ -28,7 +28,7 @@ @implementation GRXConcurrentWriteable { dispatch_queue_t _writeableQueue; // This ensures that writesFinishedWithError: is only sent once to the writeable. - dispatch_once_t _alreadyFinished; + BOOL _alreadyFinished; } - (instancetype)init { @@ -65,19 +65,35 @@ - (void)enqueueSuccessfulCompletion { dispatch_async(_writeableQueue, ^{ - dispatch_once(&_alreadyFinished, ^{ + BOOL finished = NO; + @synchronized (self) { + if (!_alreadyFinished) { + _alreadyFinished = YES; + } else { + finished = YES; + } + } + if (!finished) { // Cancellation is now impossible. None of the other three blocks can run concurrently with // this one. [self.writeable writesFinishedWithError:nil]; // Skip any possible message to the wrapped writeable enqueued after this one. self.writeable = nil; - }); + } }); } - (void)cancelWithError:(NSError *)error { NSAssert(error, @"For a successful completion, use enqueueSuccessfulCompletion."); - dispatch_once(&_alreadyFinished, ^{ + BOOL finished = NO; + @synchronized (self) { + if (!_alreadyFinished) { + _alreadyFinished = YES; + } else { + finished = YES; + } + } + if (!finished) { // Skip any of the still-enqueued messages to the wrapped writeable. We use the atomic setter to // nillify writeable because we might be running concurrently with the blocks in // _writeableQueue, and assignment with ARC isn't atomic. @@ -87,15 +103,23 @@ dispatch_async(_writeableQueue, ^{ [writeable writesFinishedWithError:error]; }); - }); + } } - (void)cancelSilently { - dispatch_once(&_alreadyFinished, ^{ + BOOL finished = NO; + @synchronized (self) { + if (!_alreadyFinished) { + _alreadyFinished = YES; + } else { + finished = YES; + } + } + if (!finished) { // Skip any of the still-enqueued messages to the wrapped writeable. We use the atomic setter to // nillify writeable because we might be running concurrently with the blocks in // _writeableQueue, and assignment with ARC isn't atomic. self.writeable = nil; - }); + } } @end |