GRXConcurrentWriteable.m 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. /*
  2. *
  3. * Copyright 2015 gRPC authors.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. */
  18. #import "GRXConcurrentWriteable.h"
  19. #import <RxLibrary/GRXWriteable.h>
  20. @interface GRXConcurrentWriteable ()
  21. // This is atomic so that cancellation can nillify it from any thread.
  22. @property(atomic, strong) id<GRXWriteable> writeable;
  23. @end
  24. @implementation GRXConcurrentWriteable {
  25. dispatch_queue_t _writeableQueue;
  26. // This ensures that writesFinishedWithError: is only sent once to the writeable.
  27. BOOL _alreadyFinished;
  28. }
  29. - (instancetype)init {
  30. return [self initWithWriteable:nil];
  31. }
  32. // Designated initializer
  33. - (instancetype)initWithWriteable:(id<GRXWriteable>)writeable
  34. dispatchQueue:(dispatch_queue_t)queue {
  35. if (self = [super init]) {
  36. _writeableQueue = queue;
  37. _writeable = writeable;
  38. }
  39. return self;
  40. }
  41. - (instancetype)initWithWriteable:(id<GRXWriteable>)writeable {
  42. return [self initWithWriteable:writeable dispatchQueue:dispatch_get_main_queue()];
  43. }
  44. - (void)enqueueValue:(id)value completionHandler:(void (^)(void))handler {
  45. dispatch_async(_writeableQueue, ^{
  46. // We're racing a possible cancellation performed by another thread. To turn all already-
  47. // enqueued messages into noops, cancellation nillifies the writeable property. If we get it
  48. // before it's nil, we won the race.
  49. id<GRXWriteable> writeable = self.writeable;
  50. if (writeable) {
  51. [writeable writeValue:value];
  52. handler();
  53. }
  54. });
  55. }
  56. - (void)enqueueSuccessfulCompletion {
  57. __weak typeof(self) weakSelf = self;
  58. dispatch_async(_writeableQueue, ^{
  59. typeof(self) strongSelf = weakSelf;
  60. if (strongSelf) {
  61. BOOL finished = NO;
  62. @synchronized(self) {
  63. if (!strongSelf->_alreadyFinished) {
  64. strongSelf->_alreadyFinished = YES;
  65. } else {
  66. finished = YES;
  67. }
  68. }
  69. if (!finished) {
  70. // Cancellation is now impossible. None of the other three blocks can run concurrently with
  71. // this one.
  72. [self.writeable writesFinishedWithError:nil];
  73. // Skip any possible message to the wrapped writeable enqueued after this one.
  74. self.writeable = nil;
  75. }
  76. }
  77. });
  78. }
  79. - (void)cancelWithError:(NSError *)error {
  80. NSAssert(error, @"For a successful completion, use enqueueSuccessfulCompletion.");
  81. BOOL finished = NO;
  82. @synchronized(self) {
  83. if (!_alreadyFinished) {
  84. _alreadyFinished = YES;
  85. } else {
  86. finished = YES;
  87. }
  88. }
  89. if (!finished) {
  90. // Skip any of the still-enqueued messages to the wrapped writeable. We use the atomic setter to
  91. // nillify writeable because we might be running concurrently with the blocks in
  92. // _writeableQueue, and assignment with ARC isn't atomic.
  93. id<GRXWriteable> writeable = self.writeable;
  94. self.writeable = nil;
  95. dispatch_async(_writeableQueue, ^{
  96. [writeable writesFinishedWithError:error];
  97. });
  98. }
  99. }
  100. - (void)cancelSilently {
  101. BOOL finished = NO;
  102. @synchronized(self) {
  103. if (!_alreadyFinished) {
  104. _alreadyFinished = YES;
  105. } else {
  106. finished = YES;
  107. }
  108. }
  109. if (!finished) {
  110. // Skip any of the still-enqueued messages to the wrapped writeable. We use the atomic setter to
  111. // nillify writeable because we might be running concurrently with the blocks in
  112. // _writeableQueue, and assignment with ARC isn't atomic.
  113. self.writeable = nil;
  114. }
  115. }
  116. @end