Files
shorebird-updater/library/include/updater.h
T
Eric Seidel 563f1b773a fix: return UpdateInProgress status instead of erroring when another update is running (#335)
When `update()` is called while another update (typically the automatic
updater thread) is already running, the Rust updater previously bailed
with `UpdateError::UpdateAlreadyInProgress`, which surfaced in Dart as
`UpdateException: Update already in progress (unknown)`. This is the
single highest-volume `UpdateException` in customer telemetry, yet the
underlying situation is benign — the in-flight update continues on its
own, the caller simply did not start a new one.

Add a new `UpdateStatus::UpdateInProgress` variant and matching C
status code `SHOREBIRD_UPDATE_IN_PROGRESS = 4`. `updater::update()`
catches the `UpdateAlreadyInProgress` error from the lock helper and
maps it to `Ok(UpdateStatus::UpdateInProgress)`. The Dart wrapper
treats the new status as a successful return alongside
`SHOREBIRD_UPDATE_INSTALLED`, so `update()` no longer throws for this
case.

Update the existing `usage_during_hung_update` c_api test to assert the
new contract, and add a Dart test covering the in-progress return path.

Version skew: new Dart on an old engine still sees the legacy
`SHOREBIRD_UPDATE_ERROR` + "Update already in progress" message and
will still throw. The fix lands once both sides ship.

Partially addresses shorebirdtech/shorebird#3682 — does not resolve the
broader asymmetry of `update()` semantics (it still does not wait for
someone else's in-flight update to finish), which remains as v2 design
work in shorebirdtech/shorebird#3684.
2026-04-08 01:22:04 +00:00

247 lines
7.4 KiB
C

#ifndef updater_h
#define updater_h
/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#ifdef _WIN32
#define SHOREBIRD_EXPORT __declspec(dllexport)
#else
#define SHOREBIRD_EXPORT __attribute__((visibility("default")))
#endif
/**
* An unknown error occurred while updating. The update was not installed.
* This is a catch-all for errors that don't fit into the other categories.
*/
#define SHOREBIRD_UPDATE_ERROR -1
/**
* No update is available (e.g. the app is already up-to-date)
*/
#define SHOREBIRD_NO_UPDATE 0
/**
* An update was installed successfully. It will boot from the update on the
* next app launch.
*/
#define SHOREBIRD_UPDATE_INSTALLED 1
/**
* An error occurred while updating. The update was not installed.
*/
#define SHOREBIRD_UPDATE_HAD_ERROR 2
/**
* The downloaded patch was not installed because it was invalid.
*/
#define SHOREBIRD_UPDATE_IS_BAD_PATCH 3
/**
* Another update was already in progress when this call was made. The
* already-running update will continue; the caller did not start a new one.
* This is a benign outcome, not an error.
*/
#define SHOREBIRD_UPDATE_IN_PROGRESS 4
/**
* Struct containing configuration parameters for the updater.
* Passed to all updater functions.
* NOTE: If this struct is changed all language bindings must be updated.
*/
typedef struct AppParameters {
/**
* release_version, required. Named version of the app, off of which
* updates are based. Can be either a version number or a hash.
*/
const char *release_version;
/**
* Array of paths to the original aot library, required. For Flutter apps
* these are the paths to the bundled libapp.so. May be used for
* compression downloaded artifacts.
*/
const char *const *original_libapp_paths;
/**
* Length of the original_libapp_paths array.
*/
int original_libapp_paths_size;
/**
* Path to app storage directory where the updater will store serialized
* state and other data that persists between releases.
*/
const char *app_storage_dir;
/**
* Path to cache directory where the updater will store downloaded
* artifacts and data that can be deleted when a new release is detected.
*/
const char *code_cache_dir;
} AppParameters;
typedef struct FileCallbacks {
/**
* Opens the "file" (actually an in-memory buffer) and returns a handle.
*/
void *(*open)(void);
/**
* Reads count bytes from the file into buffer. Returns the number of
* bytes read.
*/
uintptr_t (*read)(void *file_handle, uint8_t *buffer, uintptr_t count);
/**
* Moves the file pointer to the given offset relative from whence (one of
* libc::SEEK_SET, libc::SEEK_CUR, or libc::SEEK_END). Returns the new
* offset relative to the start of the file.
*/
int64_t (*seek)(void *file_handle, int64_t offset, int32_t whence);
/**
* Closes and frees the file handle.
*/
void (*close)(void *file_handle);
} FileCallbacks;
typedef struct UpdateResult {
int32_t status;
const char *message;
} UpdateResult;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* Configures updater. First parameter is a struct containing configuration
* from the running app. Second parameter is a YAML string containing
* configuration compiled into the app. Returns true on success and false on
* failure. If false is returned, the updater library will not be usable.
*/
SHOREBIRD_EXPORT
bool shorebird_init(const struct AppParameters *c_params,
struct FileCallbacks c_file_callbacks,
const char *c_yaml);
/**
* Returns if the app should run the updater automatically on launch.
*/
SHOREBIRD_EXPORT bool shorebird_should_auto_update(void);
/**
* The currently running patch number, or 0 if the release has not been
* patched.
*/
SHOREBIRD_EXPORT uintptr_t shorebird_current_boot_patch_number(void);
/**
* The patch number that will boot on the next run of the app, or 0 if there is
* no next patch.
*/
SHOREBIRD_EXPORT uintptr_t shorebird_next_boot_patch_number(void);
/**
* Performs integrity checks on the next boot patch. If the patch fails these checks, the patch
* will be deleted and the next boot patch will be set to the last successfully booted patch or
* the base release if there is no last successfully booted patch.
*/
SHOREBIRD_EXPORT
void shorebird_validate_next_boot_patch(void);
/**
* The path to the patch that will boot on the next run of the app, or NULL if
* there is no next patch.
*/
SHOREBIRD_EXPORT char *shorebird_next_boot_patch_path(void);
/**
* Free a string returned by the updater library.
* # Safety
*
* If this function is called with a non-null pointer, it must be a pointer
* returned by the updater library.
*/
SHOREBIRD_EXPORT void shorebird_free_string(const char *c_string);
/**
* Frees an `UpdateResult` previously returned by `shorebird_check_for_update`.
*
* # Safety
*
* `result` must be a valid pointer returned by `shorebird_check_for_update`,
* or null (in which case this is a no-op).
*/
SHOREBIRD_EXPORT void shorebird_free_update_result(struct UpdateResult *result);
/**
* Check for an update. Returns true if an update is available.
*/
SHOREBIRD_EXPORT bool shorebird_check_for_update(void);
/**
* Check for an update on the first non-null channel of:
* 1. `c_channel`
* 2. The channel specified in shorebird.yaml
* 3. The default "stable" channel
*
* Returns true if an update exists that has not yet been downloaded.
*/
SHOREBIRD_EXPORT
bool shorebird_check_for_downloadable_update(const char *c_channel);
/**
* Synchronously download an update if one is available.
*/
SHOREBIRD_EXPORT void shorebird_update(void);
/**
* Synchronously download an update on the first non-null channel of:
* 1. `c_channel`
* 2. The channel specified in shorebird.yaml
* 3. The default "stable" channel
*
* Returns an [UpdateResult] indicating whether the update was successful.
*/
SHOREBIRD_EXPORT
const struct UpdateResult *shorebird_update_with_result(const char *c_channel);
/**
* Start a thread to download an update if one is available.
*/
SHOREBIRD_EXPORT void shorebird_start_update_thread(void);
/**
* Tell the updater that we're launching from what it told us was the
* next patch to boot from. This will copy the next boot patch to be the
* `current_boot` patch.
*
* It is required to call this function before calling
* `shorebird_report_launch_success` or `shorebird_report_launch_failure`.
*/
SHOREBIRD_EXPORT void shorebird_report_launch_start(void);
/**
* Report that the app failed to launch. This will cause the updater to
* attempt to roll back to the previous version if this version has not
* been launched successfully before.
*/
SHOREBIRD_EXPORT void shorebird_report_launch_failure(void);
/**
* Report that the app launched successfully. This will mark the current
* as having been launched successfully. We don't currently do anything
* with this information, but it could be used to record a point at which
* we will not roll back from.
*
* This is not currently wired up to be called from the Engine. It's unclear
* where best to connect it. Expo waits 5 seconds after the app launches
* and then marks the launch as successful. We could do something similar.
*/
SHOREBIRD_EXPORT void shorebird_report_launch_success(void);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* updater_h */