Skip to content

Fix/workaround a crash when ReactInstance is disposed while AsyncStorage still doing work #1888

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

Closed

Conversation

matthargett
Copy link
Contributor

Fix/workaround a race that occurs when the tReactInstance has been disposed on another thread while the AsyncStorage mutex is held. Thanks to my colleagues for finding this and providing a fix.

The unit test I added doesn't quite do what I want, since there's no way to synchronize without a callback. I can remove it if no one has a better idea.

I can propagate the fix to UWP if the approach is agreeable.

[Test]
public void AsyncStorageModule_DoesNotInvokeCallbackAfterDispose()
{
callback = new MockCallback(invoke => throw new Exception("should not be called"));
Copy link
Contributor

Choose a reason for hiding this comment

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

To make this test a bit more deterministic, can we use a ManualResetEvent and call Wait with a timeout (say 500ms)? Otherwise, if this regresses, the exception that gets thrown could become a red herring / cause another test to fail / cause the test runner to crash.

{
try
{
var token = _cancelSource.Token;
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 use the mutex and check the IsCanceled property rather than relying on exception behavior? I know we'll likely need the catch blocks anyway, but it might prevent a few frequent exceptions.

await _mutex.WaitAsync(token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
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 add a when clause to this so we only catch when the OperationCanceledException is sourced from the correct cancellation token?

@@ -274,6 +289,8 @@ public override string Name

public override void OnReactInstanceDispose()
{
_cancelSource.Cancel();
Copy link
Contributor

Choose a reason for hiding this comment

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

If you use CancellationDisposable from System.Reactive, it only requires a Dispose call, and I think it will protect you when accessing the token (so you won't need the ObjectDisposedException catch block below).

@@ -274,6 +289,8 @@ public async void getAllKeys(ICallback callback)

public override void OnReactInstanceDispose()
{
_cancelSource.Cancel();
Copy link
Contributor

@psivaram psivaram Sep 20, 2018

Choose a reason for hiding this comment

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

Should we be taking the mutex here too?
What happens if a read/write is in progress where the mutex was taken.
We dispose the mutex and then the read/write completes and tries to release the mutex (which has now been disposed)

Another alternative I can think of is to replace all existing calls to _mutex.Release with calls to a new function SafeReleaseMutex which check IsCancelledOrDisposed before releasing the mutex.

@@ -225,6 +236,8 @@ public async void multiMerge(string[][] keyValueArray, ICallback callback)
[ReactMethod]
public async void clear(ICallback callback)
{
if (await IsCancelledOrDisposed()) return;

await _mutex.WaitAsync().ConfigureAwait(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens to these WaitAsync calls which are already in progress? We should pass the cancellation token to them to ensure that they are cancelled before we dispose the mutex.
I tried prototyping a race condition and looks like not cancelling the wait results in a the thread waiting there for ever.

@reseul
Copy link
Contributor

reseul commented Sep 24, 2018

@matthargett I was looking at a similar issue we have in our code, I also talked to @psivaram .. We're both kind of confused on why we need to dispose this object in the first place.
It gets disposed during GC (or finalization, if it has a finalizer), and that's it.

There's nothing to lose, other than maybe a kernel semaphore lingering for some time. It's not a connection to a database, it doesn't keep internet hostage, there's no conflict we have to avoid when another instance shows up...

My proposal is to just comment the line + add a note, so other devs are not jumping on adding the disposing back.

@chrisglein chrisglein added the Needs: Author Feedback The issue/PR needs activity from its author (label drives bot activity) label Mar 27, 2020
@ghost ghost added the no-recent-activity Issue/PR has gone stale and may be closed (label applied by bot) label Apr 3, 2020
@ghost
Copy link

ghost commented Apr 3, 2020

This pull request has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 7 days. It will be closed if no further activity occurs within 7 days of this comment.

@ghost ghost closed this Apr 10, 2020
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs: Author Feedback The issue/PR needs activity from its author (label drives bot activity) no-recent-activity Issue/PR has gone stale and may be closed (label applied by bot)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants