Skip to content

Commit

Permalink
Build ViewGroup mechanism for repeatedly retrying UpdateState until i…
Browse files Browse the repository at this point in the history
…t succeeds

Summary:
With BackgroundExecutor+State Reconciliation, it is pretty common to have races where View layer UpdateState calls are thrown away.

Theoretically this is /always/ possible since C++ doesn't retry failed UpdateStates, we just try once and then bail. This is part 1 of improving our mechanisms for ensuring that State stays up-to-date from the View layer.

Changelog: [Internal]

Reviewed By: mdvacca

Differential Revision: D22933927

fbshipit-source-id: 77b3b87402f772e3f149be0f9200e673bbed7b57
  • Loading branch information
JoshuaGross authored and facebook-github-bot committed Aug 5, 2020
1 parent 187fc09 commit c8f571f
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,7 @@ public class ReactFeatureFlags {

/** Feature flag to use stopSurface when ReactRootView is unmounted. */
public static boolean enableStopSurfaceOnRootViewUnmount = false;

/** Use experimental SetState retry mechanism in view? */
public static boolean enableExperimentalStateUpdateRetry = false;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.uimanager;

import android.view.ViewGroup;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.config.ReactFeatureFlags;

/** This is a helper base class for ViewGroups that use Fabric State. */
public abstract class FabricStateBaseViewGroup extends ViewGroup {
public interface StateUpdateCallback {
WritableMap getStateUpdate();
}

private StateWrapper mStateWrapper = null;

public FabricStateBaseViewGroup(ThemedReactContext context) {
super(context);
}

public void setStateWrapper(StateWrapper stateWrapper) {
mStateWrapper = stateWrapper;
}

private static void setState(
final FabricStateBaseViewGroup view,
final StateWrapper stateWrapper,
final StateUpdateCallback stateUpdateCallback,
final int numTries) {
// The StateWrapper will change, breaking this loop, whenever the UpdateState MountItem
// is executed.
// The caller is responsible for detecting if data is up-to-date, and doing nothing, or
// detecting if state is stale and calling setState again.
if (stateWrapper != view.mStateWrapper) {
return;
}
// We arbitrarily bail out after a certain number of retries.
// This is a pretty large number: in practice I've seen this number go over 50
// with minimal/no visual jank.
if (numTries > 5 * 60) {
return;
}

stateWrapper.updateState(stateUpdateCallback.getStateUpdate());

// An `updateState` call can fail, and there's no way to verify if it succeeds besides
// waiting for a corresponding `StateUpdate` MountItem to be executed on some future UI tick.
// So.... to resolve conflicts with updateState, we just keep firing it until it succeeds or
// the View goes away.
if (ReactFeatureFlags.enableExperimentalStateUpdateRetry) {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
setState(view, stateWrapper, stateUpdateCallback, numTries + 1);
}
});
}
}

public static void setState(
final FabricStateBaseViewGroup view,
final StateWrapper stateWrapper,
final StateUpdateCallback stateUpdateCallback) {
setState(view, stateWrapper, stateUpdateCallback, 0);
}
}

0 comments on commit c8f571f

Please sign in to comment.