OverviewFilesCommitsIssuesDocsReleasesPeers

favela-surface-android / lib / host / apply-plan.js

45 lines · javascript ref:
// apply-plan.js — what "apply the update" means, separated from the RN glue that performs it.
//
// ⛔ THE DEFECT THIS FIXES. "restart" was `stopNode(); startNode()`: terminate the worklet, start a
// new one, inside the SAME process. Measured on a handset — a staged bundle pressed through that
// path never became the running one, while `adb shell am force-stop` followed by a relaunch applied
// it every time. The only difference between those two is the process, so the process is what has
// to go: dlopen'd addons, a half-torn-down corestore and lingering swarm sockets all outlive a
// thread and none of them outlive a process.
//
// ⛔ AND IT IS THE WHOLE UPDATE MECHANISM. `chooseBundle` runs at worklet start and nowhere else. A
// restart that does not really restart means no update can be applied from the device at all —
// every one needs a cable and somebody at a desk, which is not an update mechanism.
//
// ⚠ HERE RATHER THAN IN worklet.js, because that file imports react-native at module scope and can
// only run on a handset — #70 already names it as the one file exercised solely on a device. This is
// the same split `boot-plan.js` makes: the decision is testable, the glue is thin.

export const RESTART_DELAY_MS = 250

/**
 * Ask for a real restart, and be honest about what was actually obtained.
 *
 * ⚠ FALLS BACK, LOUDLY. On a device that refuses the alarm, the thread restart is still better than
 * nothing. What must not happen is quietly doing the weaker one and reporting success — that is
 * exactly how a restart button that did nothing survived this long.
 */
export async function planApply ({ native, restartNode, log = () => {} } = {}) {
  if (native && typeof native.restartProcess === 'function') {
    try {
      await native.restartProcess(RESTART_DELAY_MS)
      return { restarted: 'process', degraded: false }
    } catch (err) {
      // Reported rather than swallowed: if this is failing, every future update fails with it.
      log({ msg: 'process restart refused — falling back to the worklet', error: String((err && err.message) || err) })
    }
  } else {
    log({ msg: 'this shell cannot restart its own process — updates will not apply until it can' })
  }

  if (typeof restartNode === 'function') await restartNode()
  return { restarted: 'worklet', degraded: true }
}

export default { planApply, RESTART_DELAY_MS }