Destroy Fabric State objects earlier in Java

Summary:
There are races between BackgroundExecutor and Fabric/View teardown, where, because of the way things are set up currently, a View will strongly retain a pointer into some native State object which can keep a host of other objects alive in C++, even after stopSurface, and even after RN itself starts tearing down.

To alleviate this, we more aggressively clear State from Java, without waiting for Java GC: 1) on stopSurface, 2) whenever a State object is stale from Java's perspective.

This should allow us to keep all common updateState semantics, while only introducing a new edge-case that stateWrapper can be destroyed during mounting if stopSurface happens at the same time. In those cases, checking for NPEs should be sufficient.

The possible race condition only really happens with updateState, so it's easier to check for. There should practically be no cases where there's a race between `stopSurface` and `getState`, because `getState` is only really called around state updates and view preallocation, and never after; we still check for NPEs in those cases, but it shouldn't be an issue.

Changelog: [Internal]

Reviewed By: thurn, sammy-SC, mdvacca

Differential Revision: D26858584

fbshipit-source-id: 2ef7467220865380037d69d8de322fe8797f6a12
This commit is contained in:
Joshua Gross
2021-03-05 18:42:39 -08:00
committed by Facebook GitHub Bot
parent 47779de424
commit 00959ffd6b
9 changed files with 85 additions and 16 deletions
@@ -9,6 +9,8 @@ package com.facebook.react.fabric;
import android.annotation.SuppressLint;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.jni.HybridData;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.NativeMap;
@@ -26,7 +28,10 @@ public class StateWrapperImpl implements StateWrapper {
FabricSoLoader.staticInit();
}
private static final String TAG = "StateWrapperImpl";
@DoNotStrip private final HybridData mHybridData;
private volatile boolean mDestroyed = false;
private static native HybridData initHybrid();
@@ -34,13 +39,34 @@ public class StateWrapperImpl implements StateWrapper {
mHybridData = initHybrid();
}
private native ReadableNativeMap getStateDataImpl();
@Override
public native ReadableNativeMap getState();
@Nullable
public ReadableNativeMap getStateData() {
if (mDestroyed) {
FLog.e(TAG, "Race between StateWrapperImpl destruction and getState");
return null;
}
return getStateDataImpl();
}
public native void updateStateImpl(@NonNull NativeMap map);
@Override
public void updateState(@NonNull WritableMap map) {
if (mDestroyed) {
FLog.e(TAG, "Race between StateWrapperImpl destruction and updateState");
return;
}
updateStateImpl((NativeMap) map);
}
@Override
public void destroyState() {
if (!mDestroyed) {
mDestroyed = true;
mHybridData.resetNative();
}
}
}