Part 3: Shorebird | shorebird.cc & updater.rs

shorebird.cc & updater.rs — Complete Deep Dive

Line-by-line explanation of how these two files orchestrate the entire device-side patch loading, with visual flow diagrams for every function.

1. The Big Picture — Two Files, One Job

These two files are the entire "runtime" of Shorebird on a user's phone. Everything else (CLI, backend, diffing) happens elsewhere. On the device, it's just these two talking to each other:

┌──────────────────────────────────────────────────────────────────────────┐
│                           YOUR APP (APK/IPA)                            │
│                                                                         │
│  ┌─────────────────────────────┐    C FFI    ┌────────────────────────┐ │
│  │    shorebird.cc             │ ◄═══════════►│    updater.rs         │ │
│  │    (C++, in Flutter Engine) │   (function  │  (Rust, libupdater.a) │ │
│  │                             │    calls)    │                       │ │
│  │  "The Orchestrator"         │             │  "The Brain"           │ │
│  │  - Reads shorebird.yaml     │             │  - State management    │ │
│  │  - Calls Rust functions     │             │  - Network calls       │ │
│  │  - Swaps libapp.so path     │             │  - Patch download      │ │
│  │  - Starts Dart VM           │             │  - Diff inflation      │ │
│  └─────────────────────────────┘             │  - Hash verification   │ │
│                                              │   - Crash recovery     │ │
│  ┌─────────────────────────────┐             │  - Event reporting     │ │
│  │    libapp.so                │             └────────────────────────┘ │
│  │    (Your Dart code - AOT)   │                         │              │
│  │    OR                       │                         │              │
│  │    dlc.vmcode               │                         ▼              │
│  │    (Patched Dart code)      │             ┌────────────────────────┐ │
│  └─────────────────────────────┘             │    state.json          │ │
│                                              │    (on-device storage) │ │
│                                              └────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘

C++ shorebird.cc — "The Orchestrator"

298 lines. Lives inside the Flutter engine. Its job is to be the middleman between the Flutter engine's boot process and the Rust updater library. It reads config, calls Rust functions via C FFI, and modifies the engine's settings.application_library_path to load the patched binary.

Knows about: Flutter Settings object, file paths, shorebird.yaml content, engine lifecycle.

Does NOT know about: Network, patches, state, diffing, hashing.

RUST updater.rs — "The Brain"

3,665 lines. The complete updater logic. Manages all state, makes network calls, downloads patches, inflates diffs, verifies hashes, handles crash recovery, tracks boot lifecycle, and queues events.

Knows about: Everything patch-related — state, network, patches, hashes, events, lifecycle.

Does NOT know about: Flutter, Dart VM, Android, iOS, engine internals. It's a pure library.

2. The C FFI Bridge — How C++ Talks to Rust

shorebird.cc can't call Rust functions directly. It calls C functions (defined in c_api/engine.rs) which are thin wrappers around the Rust updater:: module:

C Function (called by shorebird.cc)Rust Function (actual logic)Purpose
shorebird_init()updater::init()Parse config, load state, crash recovery
shorebird_next_boot_patch_path()updater::next_boot_patch()Returns path to patched file (or NULL)
shorebird_report_launch_start()updater::report_launch_start()Records "we're booting this patch"
shorebird_report_launch_success()updater::report_launch_success()Marks patch as "booted OK"
shorebird_report_launch_failure()updater::report_launch_failure()Marks patch as "bad"
shorebird_should_auto_update()updater::should_auto_update()Reads auto_update from config
shorebird_start_update_thread()updater::start_update_thread()Spawns background download thread
shorebird_validate_next_boot_patch()updater::validate_next_boot_patch()Checks patch file integrity
shorebird_free_string()deallocateFrees strings returned by Rust
How the bridge works
Rust compiles updater.rs + c_api/engine.rs into a static library (libupdater.a). The #[no_mangle] pub extern "C" attribute tells Rust to export each function with C calling conventions. The engine's BUILD.gn links this .a file into libflutter.so. So when shorebird.cc calls shorebird_init(), it's a normal function call — no IPC, no JNI, no overhead. Just a direct jump into Rust-compiled machine code within the same binary.

3. shorebird.cc — Line by Line

3.1 The Call Chain (How We Get Here)

Android starts your app
    │
    ▼
FlutterActivity.onCreate()                          [Java]
    │
    ▼
FlutterJNI.nativeInit(shorebirdYaml, version, ...)  [Java → JNI]
    │
    ▼
FlutterMain::Init()                                  [C++, flutter_main.cc]
    │
    ▼
#if FLUTTER_RELEASE        ◄── Shorebird ONLY runs in release builds
    ConfigureShorebird(code_cache_path, app_storage_path,
                       settings, shorebird_yaml, version,
                       version_code);
#endif
    │
    ▼
settings.application_library_path is now modified (or not)
    │
    ▼
Dart VM starts, loads libapp.so from that path
    │
    ▼
main() runs in Dart ← either original or patched code

3.2 ConfigureShorebird() — The Main Function

There are actually two overloads of this function — one for Android (receives Flutter Settings&) and one for newer platforms (receives a ShorebirdConfigArgs struct). They do the same thing. I'll trace the Android one since that's what you use:

C++ L153 Create directory
auto shorebird_updater_dir_name = "shorebird_updater";
auto code_cache_dir = JoinPaths({code_cache_path, shorebird_updater_dir_name});
auto app_storage_dir = JoinPaths({app_storage_path, shorebird_updater_dir_name});
fml::CreateDirectory(GetCachesDirectory(), {shorebird_updater_dir_name}, kReadWrite);
Creates /data/data/com.app/cache/shorebird_updater/ and /data/data/com.app/files/shorebird_updater/. These directories store downloaded patches and state files. Created before calling Rust so the updater has a place to write.
C++ L165 Build AppParameters + call shorebird_init()
AppParameters app_parameters;
auto release_version = version + "+" + version_code;  // "1.0.0" + "1" = "1.0.0+1"
app_parameters.release_version = release_version.c_str();
app_parameters.code_cache_dir = code_cache_dir.c_str();
app_parameters.app_storage_dir = app_storage_dir.c_str();
app_parameters.original_libapp_paths = c_paths.data();    // path to bundled libapp.so
app_parameters.original_libapp_paths_size = c_paths.size();

init_result = shorebird_init(&app_parameters, ShorebirdFileCallbacks(),
                             shorebird_yaml.c_str());
Packs all the info the Rust updater needs into a C struct and calls shorebird_init(). The shorebird_yaml string is the raw YAML content of your shorebird.yaml file (read from flutter_assets by Java). This is where Rust takes over — see Section 4 for what happens inside.
C++ L199 iOS only: SetBaseSnapshot()
#if SHOREBIRD_USE_INTERPRETER
  SetBaseSnapshot(settings);
#endif
On iOS, Shorebird uses an interpreter-based approach. SetBaseSnapshot() captures the VM and isolate snapshot mappings from the original App.framework. These are passed to the Rust updater via FileCallbacks so it can read the original binary in memory (iOS doesn't allow direct file access to framework bundles the same way Android does).

On Android, this is skipped — the original libapp.so is directly accessible via its file path.
C++ L202 ⭐ THE KEY MOMENT: Ask for patch path + SWAP
char* c_active_path = shorebird_next_boot_patch_path();
if (c_active_path != NULL) {
    std::string active_path = c_active_path;
    shorebird_free_string(c_active_path);

    // ANDROID: Replace the library path entirely
    settings.application_library_path.clear();
    settings.application_library_path.emplace_back(active_path);

    // iOS: Insert at front (keep original for VM snapshot)
    // settings.application_library_path.insert(begin(), active_path);
} else {
    // No patch — use the original bundled libapp.so (do nothing)
}
This is the single most important code in all of Shorebird.

The engine has a list called application_library_path that tells the Dart VM which .so file to load. Normally it points to the bundled libapp.so inside the APK.

If the Rust updater returns a path (meaning a patch is installed), this code replaces that list with the patch file's path. When the Dart VM starts a few milliseconds later, it loads the patched file instead. Your updated Dart code runs without ever touching the Play Store.

If the updater returns NULL (no patch, or patch is bad), the list is unchanged — the original bundled libapp.so loads as normal.
C++ L225 Report launch start + start update thread
if (!init_result) {
    return;  // init failed (e.g., bad YAML) — don't touch anything
}

shorebird_report_launch_start();     // "We're about to boot patch N"

if (shorebird_should_auto_update()) {
    shorebird_start_update_thread(); // Background: check for NEXT patch
}
report_launch_start() tells the Rust updater "we are now booting with this patch." This is the safety mechanism — if the app crashes between this call and report_launch_success(), the next launch will detect it and roll back.

start_update_thread() spawns a Rust background thread that checks the server for a NEWER patch. This runs completely independently from the app — the Dart VM is already starting. If a new patch is found, it's downloaded for the next app launch (not the current one).
That's it for shorebird.cc
It's only ~70 lines of actual logic (the rest is iOS/Android differences, the FileCallbacks implementation, and a getauxval workaround for old Android NDKs). It does 5 things: (1) create directories, (2) call shorebird_init, (3) ask for patch path, (4) swap the library path, (5) start the background updater. Everything complex happens in Rust.

4. updater.rs — Line by Line

This is the 3,665-line Rust file that does all the heavy lifting. Let me break it into its logical sections:

4.1 Global State Architecture

┌──────────────────────────────────────────────────────────────┐
│                    updater.rs — Global State                 │
│                                                              │
│  ┌────────────────┐       ┌─────────────────────────────┐   │
│  │  UPDATE_CONFIG  │       │      UPDATER_STATE          │   │
│  │  (OnceLock)     │       │  (Mutex<Option<Box>>)       │   │
│  │                 │       │                             │   │
│  │  Set ONCE in    │       │  Set in init(), accessed    │   │
│  │  init(), never  │       │  via with_state() and       │   │
│  │  changes after  │       │  with_mut_state() which     │   │
│  │                 │       │  lock the mutex             │   │
│  │  Contains:      │       │                             │   │
│  │  - app_id       │       │  Contains:                  │   │
│  │  - base_url     │       │  - client_id                │   │
│  │  - channel      │       │  - patches lifecycle        │   │
│  │  - auto_update  │       │  - next_boot_patch          │   │
│  │  - storage_dir  │       │  - currently_booting_patch  │   │
│  │  - download_dir │       │  - last_booted_patch        │   │
│  │  - release_ver  │       │  - known_bad_patches        │   │
│  │  - libapp_path  │       │  - queued_events            │   │
│  └────────────────┘       └─────────────────────────────┘   │
│                                                              │
│  ┌────────────────┐                                          │
│  │ UPDATER_THREAD  │  Mutex<()> — ensures only one update    │
│  │ _LOCK           │  cycle runs at a time                   │
│  └────────────────┘                                          │
└──────────────────────────────────────────────────────────────┘

Why this architecture? The updater runs in two contexts simultaneously: (1) the engine thread calling init/next_boot_patch/report_launch_* during boot, and (2) the background update thread checking for new patches. The Mutex on UPDATER_STATE prevents data races. The OnceLock on UPDATE_CONFIG ensures config is set exactly once and never changes.

4.2 init() — First Function Called

shorebird_init() in C
    │
    ▼
updater::init(app_config, file_provider, yaml_string)
    │
    ├── 1. init_logging()                    — Sets up Android logcat / iOS NSLog
    │
    ├── 2. YamlConfig::from_yaml(yaml)       — Parses shorebird.yaml:
    │       {                                    app_id: "abc-123"
    │         app_id, base_url, channel,         base_url: "https://your-server.com"
    │         auto_update, patch_public_key,      channel: "stable"
    │         patch_verification                  auto_update: true
    │       }
    │
    ├── 3. libapp_path_from_settings()       — Finds the bundled libapp.so path
    │       (e.g., "/data/app/com.app/lib/arm64/libapp.so")
    │
    ├── 4. set_config()                      — Stores everything in UPDATE_CONFIG (OnceLock)
    │       If already set → return AlreadyInitialized (not an error)
    │       This merges app_config (paths from engine) + yaml_config (settings from yaml)
    │
    └── 5. handle_prior_boot_failure_if_necessary()    ← THE CRASH RECOVERY

4.3 handle_prior_boot_failure_if_necessary() — Crash Recovery

This is the function that makes Shorebird safe. It runs on EVERY app start, before any patch is loaded:

handle_prior_boot_failure_if_necessary()
    │
    ├── Load state from disk: state.json + patches/*/state.json
    │
    ├── Check: is "currently_booting_patch" set?
    │
    ├── NO → Return OK (last boot was clean, nothing to do)
    │
    └── YES → A previous boot CRASHED before report_launch_success()!
         │
         ├── Read crash details:
         │   - boot_started_at = when the boot started
         │   - file_ok = does the patch file still exist?
         │   - file_size = how big is it?
         │
         ├── state.record_boot_failure_for_patch(patch_number)
         │   └── Marks this patch as "Bad" with reason "CrashRecovery"
         │       └── It will NEVER be loaded again (even if server offers it)
         │
         └── state.queue_event(__patch_install_failure__)
             └── Queued to be sent to server on next successful network call
                 └── Message: "crash_recovery: patch 3 failed to boot
                              (detected_at=1714000000,
                               boot_started_at=1713999990,
                               file_ok=true,
                               file_size=3148000)"
Why this works
The trick is simple: before loading a patch, report_launch_start() writes "I'm about to boot patch N" to disk. After the Dart VM is fully running, report_launch_success() clears that flag. If the app crashes in between (bad Dart code, segfault, ANR kill), the flag stays on disk. Next launch, handle_prior_boot_failure sees the flag and says "that patch killed us — never load it again."

4.4 next_boot_patch() — Which File to Load?

shorebird_next_boot_patch_path() in C
    │
    ▼
updater::next_boot_patch()
    │
    ├── Lock UPDATER_STATE mutex
    │
    ├── state.next_boot_patch()
    │   │
    │   ├── Check patches lifecycle for latest "Installed" patch
    │   │   that is NOT in the "Bad" list
    │   │
    │   ├── Found? → Return PatchInfo { number: 3,
    │   │              path: "/data/.../shorebird_updater/patches/3/dlc.vmcode" }
    │   │
    │   └── Not found? → Return None
    │
    ├── If Some(patch) → convert path to C string, return pointer
    │
    └── If None → return NULL pointer

    ↓ Back in shorebird.cc:
    If non-NULL → swap application_library_path
    If NULL → keep original libapp.so

4.5 report_launch_start() — The Safety Net

shorebird_report_launch_start() in C     ← called ONCE per process (std::once_flag)
    │
    ▼
updater::report_launch_start()
    │
    ├── state.next_boot_patch() → get the patch we're about to boot
    │
    ├── state.set_running_patch(patch_number)
    │   └── In-memory only: "this process is running patch 3"
    │       (used by the Dart FFI: shorebird_current_boot_patch_number)
    │
    └── state.record_boot_start_for_patch(patch_number)
        └── WRITES TO DISK: sets "currently_booting_patch = 3"
            and "boot_started_at = now"
            └── This is the flag that handle_prior_boot_failure checks
                If we crash before report_launch_success(), this flag
                persists → next launch will see it → rollback

4.6 report_launch_success() — "We're Safe"

shorebird_report_launch_success()        ← called when Dart VM finishes booting
    │
    ▼
updater::report_launch_success()
    │
    ├── state.currently_booting_patch() → get patch 3
    │
    ├── state.last_successfully_booted_patch() → get patch 2 (previous)
    │
    ├── state.record_boot_success()
    │   └── WRITES TO DISK:
    │       - Clears "currently_booting_patch" (the safety flag)
    │       - Sets "last_successfully_booted_patch = 3"
    │       └── Now the crash recovery won't trigger for patch 3
    │
    ├── Did the patch number change? (3 != 2? Yes)
    │   └── Spawn a thread to report __patch_install_success__ event
    │       └── POST /api/v1/patches/events (fire-and-forget, in background)
    │
    └── Return OK

4.7 start_update_thread() — Background Check for Next Patch

shorebird_start_update_thread() in C
    │
    ▼
updater::start_update_thread()
    │
    └── std::thread::spawn(move || {
            let result = update(None);    ← calls the full update cycle
            // logs success or error, thread exits
        });

update(channel: None)
    │
    ├── Acquire UPDATER_THREAD_LOCK (only one update at a time)
    │   └── If already locked → return UpdateInProgress
    │
    └── update_internal()    ← see next section

4.8 update_internal() — The Full Update Cycle

This is the longest function — the complete check → download → install pipeline:

update_internal()
    │
    ├── ①  SEND QUEUED EVENTS (max 3)
    │      for event in state.copy_events(3):
    │          POST /api/v1/patches/events → { app_id, type, patch_number, ... }
    │      state.clear_events()
    │
    ├── ②  BUILD CHECK REQUEST
    │      PatchCheckRequest {
    │          app_id: "abc-123",
    │          release_version: "1.0.0+1",
    │          platform: "android",
    │          arch: "aarch64",
    │          channel: "stable",
    │          client_id: "device-uuid",
    │          patch_number: 2  (currently installed, if any)
    │      }
    │
    ├── ③  POST /api/v1/patches/check
    │      Response: {
    │          patch_available: true,
    │          patch: { number: 3, download_url: "https://...",
    │                   hash: "sha256...", hash_signature: null },
    │          rolled_back_patch_numbers: [1]
    │      }
    │
    ├── ④  PROCESS ROLLBACKS
    │      for num in rolled_back_patch_numbers:
    │          state.uninstall_patch(num)
    │          └── Deletes dlc.vmcode file + removes from lifecycle
    │
    ├── ⑤  DECIDE: Should we download?
    │      lifecycle.decide_start(patch_3, url, hash)
    │      │
    │      ├── KnownBad → SKIP (this patch crashed before, never retry)
    │      ├── AlreadyInstalled → SKIP (same number + same hash)
    │      ├── Resume {offset} → RESUME download from byte N
    │      ├── Complete → SKIP download (already downloaded, go to install)
    │      └── Download → START fresh download
    │
    ├── ⑥  DOWNLOAD (if needed)
    │      lifecycle.record_download_started(number, url, hash)
    │      download_to_path(url, cache/patches/3/download.bin, resume_offset)
    │      └── HTTP GET with Range header support
    │      Verify: actual_bytes == Content-Length (if provided)
    │      lifecycle.record_download_complete(number, total_bytes)
    │
    ├── ⑦  INSTALL
    │      install_downloaded_patch(&config, &patch, &download_path)
    │      │
    │      ├── inflate(download.bin, bundled_libapp, patches/3/dlc.vmcode)
    │      │   ├── Thread 1: zstd decompress (download.bin → pipe)
    │      │   └── Thread 2: bipatch(pipe + bundled_libapp → dlc.vmcode)
    │      │
    │      ├── check_hash(dlc.vmcode, expected_hash)
    │      │   ├── Match → Continue ✓
    │      │   └── Mismatch → Mark Bad(InstallHashMismatch), return error
    │      │
    │      ├── lifecycle.record_install_complete(number, file_size)
    │      │
    │      ├── lifecycle.promote_to_next_boot(number)
    │      │   └── WRITES TO DISK: "next_boot_patch = 3"
    │      │
    │      └── Queue __patch_download__ event for server
    │
    └── ⑧  DONE — Return UpdateStatus::UpdateInstalled
            Next time the app launches, shorebird_next_boot_patch_path()
            will return patches/3/dlc.vmcode

4.9 report_launch_failure() — Emergency Rollback

updater::report_launch_failure()     ← called by engine if Dart VM fails to start
    │
    ├── state.currently_booting_patch() → get patch 3
    │
    ├── state.record_boot_failure_for_patch(3)
    │   └── Marks patch 3 as Bad (same as crash recovery)
    │
    └── state.queue_event(__patch_install_failure__)
        └── Message: "engine_report: patch 3 failed to launch"

    After this, the engine will likely abort() the process.
    On next launch, handle_prior_boot_failure won't even see the
    currently_booting_patch flag (already cleared by record_boot_failure)
    but patch 3 is in the Bad list → next_boot_patch() skips it.

5. Complete Timeline — Everything in Order

Here's every function call from the moment the user taps your app icon to when they see the UI, with exact timing:

TIME     WHO              FUNCTION                         WHAT HAPPENS
─────    ─────            ─────────                        ─────────────
0ms      Android          Process created                  System spawns the app process
5ms      Java             FlutterActivity.onCreate()       Flutter Android embedding starts
10ms     Java → JNI       FlutterJNI.nativeInit()          Reads shorebird.yaml from assets
12ms     C++ (shorebird)  ConfigureShorebird()             Entry point
13ms     C++              CreateDirectory()                Creates shorebird_updater/ dirs
15ms     C++ → Rust       shorebird_init()                 Parses YAML, sets global config
16ms     Rust             init()                           
17ms     Rust             handle_prior_boot_failure()      Checks for crash flag on disk
         Rust             └── if flag set: mark bad        (takes ~1ms to read state.json)
18ms     C++ → Rust       shorebird_next_boot_patch_path() Asks "which .so to load?"
19ms     Rust             next_boot_patch()                Reads lifecycle, returns path or NULL
20ms     C++              ⭐ SWAP library path              settings.application_library_path = patch
22ms     C++ → Rust       shorebird_report_launch_start()  Writes "booting patch 3" to disk
23ms     C++ → Rust       shorebird_start_update_thread()  Spawns background Rust thread
25ms     C++ (Engine)     Dart VM starts                   Loads .so from application_library_path
         │                                                 (loads dlc.vmcode if patch, else libapp.so)
         │
50ms     Dart             main() runs                      YOUR patched Dart code executes!
         │                                                 ← User sees splash screen
         │
         │  Meanwhile, in the background Rust thread:
         │
25ms     Rust (bg)        update_internal()                Starts checking
26ms     Rust (bg)        send queued events               Reports pending __patch_install__ etc.
30ms     Rust (bg)        POST /patches/check              Asks server for newer patch
150ms    Rust (bg)        Response received                "patch 4 available at https://..."
200ms    Rust (bg)        decide_start()                   "Fresh download needed"
210ms    Rust (bg)        download_to_path()               Downloading patch 4 (~150 KB)
800ms    Rust (bg)        inflate()                        Decompress + bipatch → dlc.vmcode
900ms    Rust (bg)        check_hash()                     SHA256 verify ✓
910ms    Rust (bg)        promote_to_next_boot()           next_boot_patch = 4
         │
         │  Meanwhile, in the main thread:
         │
200ms    C++ (Engine)     DartIsolate created              Dart VM finished initializing
250ms    C++ → Rust       shorebird_report_launch_success() "Patch 3 booted OK" ✓
         Rust             └── Clears currently_booting flag
         Rust             └── Sends __patch_install_success__ event (background thread)
         │
300ms+   Dart             App is running normally          User interacts with the app
         │
         │  Next app launch: will boot patch 4 (not 3)
Notice the timing
The entire Shorebird overhead is ~15ms (from ConfigureShorebird entry to start_update_thread). The background download happens completely independently — the user never waits for it. They see the current patch immediately, and the next patch downloads silently for the next launch.

6. What's On Disk — The State Files

/data/data/com.example.app/
├── files/shorebird_updater/                    ← PERSISTENT (survives cache clear)
│   ├── state.json                              ← Global state
│   │   {
│   │     "client_id": "550e8400-e29b-41d4-a716-446655440000",
│   │     "release_version": "1.0.0+1"
│   │   }
│   │
│   ├── pointers.json                           ← Which patch to boot
│   │   {
│   │     "next_boot": 3,
│   │     "last_successfully_booted": 3,
│   │     "currently_booting": null,            ← null = safe state
│   │     "boot_started_at": null
│   │   }
│   │
│   └── patches/
│       ├── 2/
│       │   ├── state.json                      ← Patch lifecycle
│       │   │   { "status": "installed", "hash": "abc...", "size": 3148000 }
│       │   └── dlc.vmcode                      ← THE PATCHED libapp.so (3.1 MB)
│       │
│       └── 3/
│           ├── state.json
│           │   { "status": "installed", "hash": "def...", "size": 3149000 }
│           └── dlc.vmcode                      ← LATEST PATCH (loaded on boot)
│
└── cache/shorebird_updater/                    ← TEMPORARY (can be cleared)
    └── patches/
        └── 4/
            └── download.bin                    ← In-progress download (compressed diff)

───────────────────────────────────────────────────────
The ORIGINAL libapp.so is inside the APK at:
/data/app/com.example.app-XXXX/lib/arm64/libapp.so
This file is READ-ONLY (part of the APK). It's used as the
BASE for bipatch — the diff is applied against this file.

7. Complete Function Reference

shorebird.cc (C++ — 298 lines)

FunctionLinesCalled ByPurpose
ConfigureShorebird(args, patch_path)~50Android flutter_main.ccMain entry: init → get path → swap → start update
ConfigureShorebird(settings, yaml, ...)~60iOS/other platformsSame logic, different parameter format
SetBaseSnapshot(settings)~10ConfigureShorebird (iOS)Captures VM/isolate snapshot mappings for interpreter mode
ShorebirdFileCallbacks()~8ConfigureShorebirdReturns C function pointers for Rust to read the bundled binary
FileCallbacksImpl::Open/Read/Seek/Close~20Rust updater via FFIIn-memory file abstraction over snapshot mappings

updater.rs (Rust — 3,665 lines, 15 public functions)

FunctionCalled ByThreadPurpose
init()shorebird.cc via FFIMainParse YAML, set config, crash recovery
handle_prior_boot_failure()init()MainDetect + handle crashed patches
next_boot_patch()shorebird.cc via FFIMainReturn path to best available patch
report_launch_start()shorebird.cc via FFIMainWrite "booting patch N" safety flag to disk
report_launch_success()Engine (after Dart VM boots)MainClear safety flag, send success event
report_launch_failure()Engine (on Dart VM failure)MainMark patch bad, queue failure event
should_auto_update()shorebird.cc via FFIMainRead auto_update from YAML config
start_update_thread()shorebird.cc via FFIMain→spawns BGSpawn thread that calls update()
update(channel)start_update_thread / Dart FFIBackgroundAcquire lock, call update_internal
update_internal()update()BackgroundFull cycle: check→download→inflate→verify→install
install_downloaded_patch()update_internal()Backgroundinflate + hash check + promote
inflate()install_downloaded_patch()BG + spawns threadZstd decompress → bipatch → output
check_hash()install_downloaded_patch()BackgroundSHA256 verify reconstructed file
roll_back_patches_if_needed()update_internal()BackgroundUninstall server-rolled-back patches
validate_next_boot_patch()shorebird.cc via FFIMainIntegrity check on patch file
running_patch()Dart FFIAnyReturn which patch this process is running
check_for_downloadable_update()Dart FFIIsolateCheck server without downloading

Post a Comment

Previous Post Next Post

Subscribe Us


Get tutorials, Flutter news and other exclusive content delivered to your inbox. Join 1000+ growth-oriented Flutter developers subscribed to the newsletter

100% value, 0% spam. Unsubscribe anytime