Files
shorebird-updater/library/Cargo.toml
T
Eric Seidel 8649c75206 perf: mmap libapp.so out of the APK instead of buffering in RAM (#354)
* perf: mmap libapp.so out of the APK instead of buffering in RAM

`open_base_lib` previously read the entire decompressed libapp.so into a
Vec<u8> via `read_to_end` and handed bipatch a Cursor over that buffer.
For large apps libapp.so can be tens of megabytes, and that allocation
happens immediately after the patch download is buffered to disk — a
plausible OOM trigger on memory-constrained devices (we have at least
one customer report on OnePlus where the patch install appears to halt
silently right after the download completes).

Modern AGP (3.6+) defaults to extractNativeLibs=false, which keeps
libapp.so STORED uncompressed inside the APK so the dynamic linker can
mmap it directly. When that's the case, do the same: find the entry's
data offset via the zip crate, drop the archive, reopen the APK, and
mmap the entry's slice. Cursor<Mmap> implements Read + Seek, which is
what bipatch's `Reader::new(patch, base)` requires.

When the entry isn't stored uncompressed (older builds, or builds that
explicitly compress native libs), fall back to the previous buffered
read so we always succeed.

Mmap doesn't change the peak working set when bipatch traverses the
whole base linearly, but file-backed mappings are clean and reclaimable
under memory pressure where an anonymous Vec is not, and we avoid the
~2x transient allocation peak from `read_to_end` growing the buffer.

Tests cover both paths (stored → mmap, deflated → buffered) on the host.

* ci: add mmap, memmap, SIGBUS to cspell dictionary
2026-05-05 17:33:22 -07:00

87 lines
3.1 KiB
TOML

[package]
name = "updater"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
# "lib" is used by the "cli" target for testing from Rust
# "cdylib" is used by the "dart_cli" target for testing from Dart
# "staticlib" is used by the engine build for linking into libflutter.so
crate-type = ["lib", "cdylib", "staticlib"]
[features]
# Exposes a small set of internal Rust items (e.g. `testing_reset_config`)
# to sibling crates that need to drive the updater from a test harness.
# Production builds (the cdylib/staticlib that ships in the engine) do not
# enable this feature; only `library_test_hooks` does.
test-hooks = []
[dependencies]
# Used for error handling for now.
anyhow = "1.0.69"
base64 = "0.22.0"
# For inflating compressed patch files.
bipatch = "1.0.0"
# Used for exposing C API
# comde is a wrapper around several compression libraries.
# We only use zstd and could depend on it directly instead.
comde = { version = "0.3.1", default-features = false, features = [
"zstandard",
] }
dyn-clone = "1.0.16"
# For decoding the hex-encoded hashes in Patch network responses.
hex = "0.4.3"
libc = "0.2.98"
# For error!(), info!(), etc macros. `print` will not show up on Android.
log = "0.4.14"
# For mmap'ing libapp.so out of the APK without buffering it in RAM. This
# matters on memory-constrained devices: reading the whole library into a
# Vec<u8> can cost tens of MB and trigger OOM kills mid-patch.
memmap2 = "0.9.10"
# For implementing thread-local-storage of ResolvedConfig object.
once_cell = "1.17.1"
# Json serialization/de-serialization.
# Pipe is a simple in-memory pipe implementation, there might be a std way too?
pipe = "0.4.0"
# Used for networking. ureq is a synchronous-only HTTP client that avoids
# pulling in tokio/hyper/async machinery, significantly reducing binary size.
ureq = { version = "3", features = ["json", "rustls"] }
ring = "0.17.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.93"
# For computing hashes of patch files for validation.
sha2 = "0.11.0"
uuid = { version = "1.18.1", features = ["v4"] }
# For decompressing .apk files.
zip = { version = "8.5.1", default-features = false, features = ["deflate"] }
[target.'cfg(target_os = "android")'.dependencies]
# For logging to Android logcat.
android_logger = "0.15.0"
# Send panics to log (instead of stderr), thus logcat on Android.
log-panics = "2"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
# Send panics to syslog (instead of stderr).
log-panics = "2"
# Support for logging on iOS (https://developer.apple.com/documentation/oslog)
oslog = "0.2.0"
[target.'cfg(any(target_os = "linux", target_os = "windows"))'.dependencies]
simple_logger = "5.0.0"
[dev-dependencies]
mockall = "0.14.0"
mockito = "1.2.0"
mock_instant = "0.6.0"
# Gives #[serial] attribute for locking all of our shorebird_init
# tests to a single thread so they don't conflict with each other.
serial_test = "3.2.0"
tempfile = "3"
# <https://github.com/eqrion/cbindgen/blob/master/docs.md#buildrs>
[build-dependencies]
cbindgen = "0.29.2"