Skip to content
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

Fix some bugs with async open task cancellation #8178

Merged
merged 1 commit into from
Mar 27, 2023

Conversation

tgoyne
Copy link
Member

@tgoyne tgoyne commented Mar 20, 2023

There were some very specific timings where cancelling the Task for an async open could either crash or fail to call the completion handler. Waiting for downloads when the Realm was already open would not cancel the download.

The tests which exposed these problems rely on actor-confined Realms and aren't easily ported to not depend on that, as they use a custom executor to test cancellation at every suspension point.

@tgoyne tgoyne self-assigned this Mar 20, 2023
@cla-bot cla-bot bot added the cla: yes label Mar 20, 2023
@tgoyne tgoyne force-pushed the tg/async-open-cancel branch 2 times, most recently from 7d58375 to ac8effe Compare March 24, 2023 18:57
Copy link
Contributor

@dianaafanador3 dianaafanador3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one catching this issue.

if (_cancel) {
return [self reportError:s_canceledError];
__weak auto weakSelf = self;
[self waitWithCompletion:^(NSError *error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait is the purpose of separating this into two steps?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is both to sidestep the sendable checking issues with cancellation handlers and to align the APIs for the two tasks so that the Swift code doesn't need to be duplicated. Passing a RLMRealm to the completion handler gives a spurious warning because when it's wrapped in a completion handler there's potentially a hop away from the correct executor and then back, and reading it from the token instead avoids that. These means that Swift wants a completion handler which just returns an error for both task types, but the obj-c (and Swift Realm.asyncOpen()) API needs a completion handler with a RLMRealm and error, so this wraps the applicable one to expose that.

- (void)waitWithCompletion:(void (^)(NSError *))completion {
std::lock_guard lock(_mutex);
_completion = completion;
if (_cancel) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we do [self checkCancellation] here?

}

- (bool)checkCancellation {
std::lock_guard lock(_mutex);
if (_cancel && _completion) {
[self reportError:s_canceledError];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case of cancel, shouldn't we unlock the lock as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reportError: needs to be called with the lock held. The callback is invoked asynchronously on the scheduler so it's not invoked in a context where we're holding the lock.


@available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *)
extension TaskWithCancellation {
func waitWithCancellationHandler() async throws {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool way to do the implementation for both RLMAsyncOpenTask and RLMAsyncDownloadTask

}
}
}
extension RLMAsyncOpenTask: TaskWithCancellation {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the code, I understand that in a async await context we now use RLMAsyncDownloadTask if the realm is already open and RLMAsyncOpenTask if is the first time. Is that correct?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. The design of task cancellation in Swift means that any cancellable operation needs an object which represents that operation. RLMAsyncDownloadTask is mostly just an optimization - we could just go through the normal async open flow even if the Realm is already open - but I think the design of actor-confined Realms will lead to people hitting that code path fairly often.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool 🤗

@@ -19,13 +19,15 @@
#import "RLMAsyncTask_Private.h"

#import "RLMError_Private.hpp"
#import "RLMRealm_Private.hpp"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like opening asynchronously a realm is getting more complex, which is needed to avoid issues. I wish at least some small comments on each of the step for opening a realm for the first time asynchronously to understand why this step by step approach is needed.

Copy link
Contributor

@dianaafanador3 dianaafanador3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thanks for taking the time to answer my questions.

There were some very specific timings where cancelling the Task for an async
open could either crash or fail to call the completion handler. Waiting for
downloads when the Realm was already open would not cancel the download.
@tgoyne tgoyne merged commit 582dc67 into tg/sendable Mar 27, 2023
@tgoyne tgoyne deleted the tg/async-open-cancel branch March 27, 2023 19:41
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 14, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants