New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use a semaphore instead of NSCondition for -waitUntilFinished. #135
Conversation
Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla - and if you have received this in error or have any questions, please drop us a line at cla@fb.com. Thanks! |
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks! |
Hmmm, there's probably an issue with this. Whether or not dispatch_release is needed depends on the deployment target. From the documentation of dispatch_release:
Maybe this is needed to conditionally include dealloc: #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0 || __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_8
- (void)dealloc {
dispatch_release(_completedSemaphore);
}
#endif |
} | ||
[self.condition wait]; | ||
[self.condition unlock]; | ||
dispatch_semaphore_wait(_completedSemaphore, DISPATCH_TIME_FOREVER); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is an issue with this one.
waitUntilFinished
should be a no-op if the task is already completed, dependless of how many times it is called.
The previous flow had a check for that around if (self.completed) return
, this one doesn't.
I think simply adding lines 433-436 back would solve the problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good point. Will fix that.
Single piece to fix, but otherwise - looks great. |
Not entirely sure if the unit test is admissible: couldn't figure out a way to have the unit test fail without hanging without using a timeout. So it's possible that the unit test could fail occasionally in the event the timeout is legitimately exceeded for some reason. Would love to see a better of doing this. |
You can use say - diatch_async onto a background thread. Wait for the task there twice and then fulfill a test expectation. In this case - you won't timeout the whole test run, but rather it would fail the single test with timeout. |
XCTAssertEqualObjects(@"foo", task.result); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha. Just noticed this. Yup this is great.
Maybe split into a separate test and use XCTestExpectation instead of a semaphore here.
Last piece about the |
Perfect! Last piece - squash all the commits into a single one and I'll merge it in! |
@toddreed, would love to merge this one in before we do a release, any chance you can squash commits into a single one? |
NSCondition suffers from the possibility of spurious wakeups and requires a predicate to guard against this; -waitUntilFinished did not do this. Changed implementation to used a semaphore (from libdispatch). This fixes #134.
I just realized - this approach isn't going to work if you have multiple threads accessing the same So something like this would fail: BFTaskCompletionSource *taskCompletionSource = [BFTaskCompletionSource taskCompletionSource];
dispatch_apply(100500, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t i) {
[taskCompletionSource.task waitUntilFinished];
NSLog(@"%d", i);
});
[taskCompletionSource trySetResult:nil]; While we could get the dispatch_semaphore_signal to signal all the threads - it still doesn't solve the problem of potential race conditions in the amount of time between I am currently thinking about potential ways of solving this, but as far as I can understand - it's definetly not Another thing to note - I already looked into implementation of this using
|
Crazy late nite ideas:
Open to other ideas, something @richardjrossiii mentioned offline with using NSConditionLock (which hopefully doesn't have thread wakeup problem, but can't confirm that). |
The original issue was that Compared to the original - (BOOL)isCompleted {
[self.condition lock];
BOOL completed = _completed;
[self.condition unlock];
return completed;
}
- (void)setCompleted:(BOOL)completed {
[self.condition lock];
_completed = completed;
if (_completed) {
[self.condition broadcast];
}
[self.condition unlock];
} And then: - (void)waitUntilFinished {
[self.condition lock];
while (!_completed) {
[self.condition wait];
}
[self.condition unlock];
} |
NSCondition suffers from the possibility of spurious wakeups and requires a predicate to guard against this; -waitUntilFinished did not do this. This fixes #134.
I don't think the I'm going to look into the implementation of |
@richardjrossiii I don't think there's a race a condition here. The condition is always checked when the lock is acquired and the |
@toddreed You're right. I had forgotten that NSCondition has both a condition and a mutex internally. The while loop solution should work, for all situations I can think of. |
@toddreed updated the pull request. |
Continuing conversation and implementation in #247 |
NSCondition suffers from the possibility of spurious wakeups and requires a predicate to guard against this; -waitUntilFinished did not do this. Changed implementation to used a semaphore (from libdispatch).
This fixes #134.