make AsyncStorage serially execute requests (#18522)

Summary:
This patch is a bit of a hack job, but I'd argue it's necessary to dramatically improve the dev UX on Android devices. Somewhere in react-native, there's a shared SerialExecutor which AsyncStorage uses that is getting blocked, causing remote debugging to occasionally hang indefinitely for folks making AsyncStorage requests. This is frustrating from a dev UX perspective, and has persisted across several versions as far back as RN 0.44, and still remains on RN 0.54.

The issue seems to only happen on Android > 7+, which is likely because the ThreadPoolExecutor behavior changed in this version:
https://stackoverflow.com/questions/9654148/android-asynctask-threads-limits

Fixes #14101

We've been using this patch in production for the past 4 months on our team by overriding the AsyncStorage native module. We use AsyncStorage extensively for offline data and caching.

You can test by compiling this commit version into a test react native repository that is set to build from source:

```sh
git clone https://github.com/dannycochran/react-native rnAsyncStorage
cd rnAsyncStorage
git checkout asyncStorage
cd ..
git clone https://github.com/dannycochran/asyncStorageTest
yarn install
cp -r ../rnAsyncStorage node_modules/react-native
react-native run-android
```

No documentation change is required.

https://github.com/facebook/react-native/pull/16905

[Android] [BUGFIX] [AsyncStorage] - Fix AsyncStorage causing remote debugger to hang indefinitely.
Pull Request resolved: https://github.com/facebook/react-native/pull/18522

Differential Revision: D8624088

Pulled By: hramos

fbshipit-source-id: a1d2e3458d98467845cb34ac73f2aafaaa15ace2
This commit is contained in:
Daniel Cochran
2018-07-30 12:00:35 -07:00
committed by Facebook Github Bot
parent 82af7c989b
commit 1b09bd7fba
2 changed files with 82 additions and 22 deletions

View File

@@ -7,10 +7,13 @@
package com.facebook.react.modules.storage;
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.concurrent.Executor;
import android.database.Cursor;
import android.database.sqlite.SQLiteStatement;
import android.os.AsyncTask;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.Arguments;
@@ -23,6 +26,7 @@ import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.common.ModuleDataCleaner;
@@ -43,8 +47,47 @@ public final class AsyncStorageModule
private ReactDatabaseSupplier mReactDatabaseSupplier;
private boolean mShuttingDown = false;
// Adapted from https://android.googlesource.com/platform/frameworks/base.git/+/1488a3a19d4681a41fb45570c15e14d99db1cb66/core/java/android/os/AsyncTask.java#237
private class SerialExecutor implements Executor {
private final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
private Runnable mActive;
private final Executor executor;
SerialExecutor(Executor executor) {
this.executor = executor;
}
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
executor.execute(mActive);
}
}
}
private final SerialExecutor executor;
public AsyncStorageModule(ReactApplicationContext reactContext) {
this(reactContext, AsyncTask.THREAD_POOL_EXECUTOR);
}
@VisibleForTesting
AsyncStorageModule(ReactApplicationContext reactContext, Executor executor) {
super(reactContext);
this.executor = new SerialExecutor(executor);
mReactDatabaseSupplier = ReactDatabaseSupplier.getInstance(reactContext);
}
@@ -141,7 +184,7 @@ public final class AsyncStorageModule
callback.invoke(null, data);
}
}.execute();
}.executeOnExecutor(executor);
}
/**
@@ -208,7 +251,7 @@ public final class AsyncStorageModule
callback.invoke();
}
}
}.execute();
}.executeOnExecutor(executor);
}
/**
@@ -259,7 +302,7 @@ public final class AsyncStorageModule
callback.invoke();
}
}
}.execute();
}.executeOnExecutor(executor);
}
/**
@@ -322,7 +365,7 @@ public final class AsyncStorageModule
callback.invoke();
}
}
}.execute();
}.executeOnExecutor(executor);
}
/**
@@ -345,7 +388,7 @@ public final class AsyncStorageModule
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()));
}
}
}.execute();
}.executeOnExecutor(executor);
}
/**
@@ -379,7 +422,7 @@ public final class AsyncStorageModule
}
callback.invoke(null, data);
}
}.execute();
}.executeOnExecutor(executor);
}
/**