Skip to content

fix(harmony): cache buildTime and packageVersion to prevent thread-safety sync errors#590

Closed
sunnylqm wants to merge 2 commits into
masterfrom
fix/harmony-soft-reload-final
Closed

fix(harmony): cache buildTime and packageVersion to prevent thread-safety sync errors#590
sunnylqm wants to merge 2 commits into
masterfrom
fix/harmony-soft-reload-final

Conversation

@sunnylqm

@sunnylqm sunnylqm commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

This PR fixes a background-thread resource-safety issue that causes the update Preferences to be wiped after a soft reload:

  • Problem: When a new RNInstance initializes on the JS background thread, it calls getBuildTime() which runs resourceManager.getRawFileContentSync('meta.json'). In HarmonyOS, synchronous resource manager APIs are not thread-safe and throw exceptions if called from non-UI threads. The exception is silently caught and returns "". Because this doesn't match the previously stored build time, the system assumes the binary changed and clears the entire update Preferences.
  • Solution: Cache the resolved packageVersion and buildTime in static class variables during the first resolution (which always runs on the Main/UI thread in the bundle provider). Background threads then read from memory cache directly without calling the rawfile APIs.

Unit tests pass successfully.

Summary by CodeRabbit

  • New Features

    • Updated the app’s Expo configuration to include required plugins for improved app setup and compatibility.
    • Added support for an iOS native bridge integration needed by the app.
  • Bug Fixes

    • Improved app version/build-time handling by reusing cached values and logging failures when build metadata can’t be read.
    • Reduced extra logging details during startup for cleaner diagnostics.
  • Chores

    • Refreshed several app and tooling dependencies to newer versions.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new app.plugin.js Expo config plugin that injects #import <React/RCTBridge.h> into the iOS bridging header, wires it in the Example app's app.json, bumps Example app dependencies, adds static caching to Harmony's UpdateContext version/build-time lookups with improved error logging, and removes a duplicate uuid field from the core bootup log.

Changes

Expo RCTBridge Config Plugin

Layer / File(s) Summary
Bridging header injection plugin
app.plugin.js
New withPushyRCTBridgeImport function uses withDangerousMod to find and append #import <React/RCTBridge.h> to the iOS *-Bridging-Header.h if not already present. Includes a fallback require.resolve strategy for @expo/config-plugins.
Example app wiring and dependency bumps
Example/expoUsePushy/app.json, Example/expoUsePushy/package.json
Registers the new bridging-header plugin and three standard Expo plugins in app.json; bumps Expo SDK, React, React Native, and dev dependency versions across package.json.

Harmony UpdateContext Caching and Core Log Fix

Layer / File(s) Summary
UpdateContext static caching
harmony/pushy/src/main/ets/UpdateContext.ts
Adds cachedPackageVersion and cachedBuildTime static fields; getPackageVersion and getBuildTime return cached values on repeat calls. getBuildTime catch block now logs the error instead of silently swallowing it.
Remove duplicate uuid from bootup log
src/core.ts
Removes the standalone uuid property from the bootup status log payload since it is already present inside cInfo.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 A header gets patched with a bridge import neat,
Cache saves the version so reads don't repeat,
Plugins now listed in app.json with care,
UUID logged once — no need for a spare.
Hop hop, the bundle is tidy and sweet! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main HarmonyOS fix: caching buildTime and packageVersion to avoid thread-safety sync errors.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/harmony-soft-reload-final

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

app.plugin.js

Oops! Something went wrong! :(

ESLint: 8.57.1

Error: .eslintrc.js » @react-native/eslint-config#overrides[4]:
Environment key "jest/globals" is unknown

at /node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2079:23
at Array.forEach (<anonymous>)
at ConfigValidator.validateEnvironment (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2073:34)
at ConfigValidator.validateConfigArray (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2223:18)
at CascadingConfigArrayFactory._finalizeConfigArray (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3985:23)
at CascadingConfigArrayFactory.getConfigArrayForFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3791:21)
at FileEnumerator._iterateFilesWithFile (/node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/cli-engine/file-enumerator.js:368:43)
at FileEnumerator._iterateFiles (/node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/cli-engine/file-enumerator.js:349:25)
at FileEnumerator.iterateFiles (/node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/cli-engine/file-enumerator.js:299:59)
at iterateFiles.next (<anonymous>)
harmony/pushy/src/main/ets/UpdateContext.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

Error: .eslintrc.js » @react-native/eslint-config#overrides[4]:
Environment key "jest/globals" is unknown

at /node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2079:23
at Array.forEach (<anonymous>)
at ConfigValidator.validateEnvironment (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2073:34)
at ConfigValidator.validateConfigArray (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2223:18)
at CascadingConfigArrayFactory._finalizeConfigArray (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3985:23)
at CascadingConfigArrayFactory.getConfigArrayForFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3791:21)
at FileEnumerator._iterateFilesWithFile (/node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/cli-engine/file-enumerator.js:368:43)
at FileEnumerator._iterateFiles (/node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/cli-engine/file-enumerator.js:349:25)
at FileEnumerator.iterateFiles (/node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/cli-engine/file-enumerator.js:299:59)
at iterateFiles.next (<anonymous>)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sunnylqm sunnylqm closed this Jun 30, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Example/expoUsePushy/app.json`:
- Around line 29-30: The Expo plugin entry is pointing to the wrong module, so
the bridging-header patch will not be loaded. Update the plugin reference in
app.json to use the new app.plugin.js added at the repo root, and verify the
plugin name/path matches the actual file exported for the Expo config plugin.

In `@harmony/pushy/src/main/ets/UpdateContext.ts`:
- Around line 29-30: The lazy caching in UpdateContext still lets the first
background-thread call reach the unsafe synchronous metadata reads, so warm the
cache on the UI thread before any JS background access or make the background
path avoid reading meta.json when the cache is empty. Update the
UpdateContext/getConstants flow and the getBuildTime()/getPackageVersion() logic
so PushyTurboModule.getConstants() never falls back to empty build-time values
or triggers syncStateWithBinaryVersion() with an uninitialized cache.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a0a8cac8-b6b7-424e-8ae6-637a98a2b733

📥 Commits

Reviewing files that changed from the base of the PR and between 009dc5a and 8e99fda.

⛔ Files ignored due to path filters (1)
  • Example/expoUsePushy/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • Example/expoUsePushy/app.json
  • Example/expoUsePushy/package.json
  • app.plugin.js
  • harmony/pushy/src/main/ets/UpdateContext.ts
  • src/core.ts
💤 Files with no reviewable changes (1)
  • src/core.ts

Comment on lines +29 to +30
"plugins": [
"./plugins/with-rct-bridge-bridging-header",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Point the plugin entry at the file added in this PR.

app.plugin.js is added at the repo root, but Line 30 registers ./plugins/with-rct-bridge-bridging-header. Unless another file exists at that exact path, Expo will fail to resolve the plugin and the bridging-header patch never runs.

Suggested fix
     "plugins": [
-      "./plugins/with-rct-bridge-bridging-header",
+      "../../app.plugin.js",
       "expo-font",
       "expo-router",
       "expo-web-browser"
     ]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"plugins": [
"./plugins/with-rct-bridge-bridging-header",
"plugins": [
"../../app.plugin.js",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Example/expoUsePushy/app.json` around lines 29 - 30, The Expo plugin entry is
pointing to the wrong module, so the bridging-header patch will not be loaded.
Update the plugin reference in app.json to use the new app.plugin.js added at
the repo root, and verify the plugin name/path matches the actual file exported
for the Expo config plugin.

Comment on lines +29 to +30
private static cachedPackageVersion: string = '';
private static cachedBuildTime: string = '';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

This still allows the first background-thread read to hit the unsafe sync APIs.

The cache is only populated lazily, so if the first UpdateContext/getConstants() call happens on the JS background thread, Lines 68-72 and especially Lines 84-91 still do the synchronous lookups before anything is cached. That means getBuildTime() can still throw, return '', and then PushyTurboModule.getConstants() will pass that empty build time into syncStateWithBinaryVersion(), preserving the same preference-clearing path described in the PR objective. This needs a guaranteed UI-thread warm-up step before any background access, or the background path must avoid touching meta.json when the cache is empty.

Also applies to: 65-78, 80-96

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@harmony/pushy/src/main/ets/UpdateContext.ts` around lines 29 - 30, The lazy
caching in UpdateContext still lets the first background-thread call reach the
unsafe synchronous metadata reads, so warm the cache on the UI thread before any
JS background access or make the background path avoid reading meta.json when
the cache is empty. Update the UpdateContext/getConstants flow and the
getBuildTime()/getPackageVersion() logic so PushyTurboModule.getConstants()
never falls back to empty build-time values or triggers
syncStateWithBinaryVersion() with an uninitialized cache.

@sunnylqm sunnylqm deleted the fix/harmony-soft-reload-final branch July 1, 2026 03:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant