Files
OSGKeyboard/docs/keyboard-memory-budget.md
T
Rocky 0f9280bd00 fix(keyboard): stabilize Flow startup under memory pressure
Delay competing Rime work, add extension memory telemetry and stress coverage, and refresh the 1.8 release assets and metadata for build 79.
2026-08-18 10:24:02 +08:00

82 lines
3.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Keyboard memory budget — acceptance (OSGDiag)
Phase 0 / 2 memory work is validated on a **physical iPhone** with Console
filtering for `OSGDiag`. Unit tests and `xcodebuild` cover compile-time
wiring; jetsam behavior is device-only.
## Hybrid Flow (product default)
- Foreground host is **light by default**: orphan Live Activity cleanup only —
**no** auto `startSession` / continuous capture on appear.
- Capture starts only on explicit Start / `osgkeyboard://startflow` / mic press.
- Idle background capture is stopped so a parked host does not jetsam the keyboard.
- **Do not** stack CLM + Rime + ASR in the same second after onboarding.
- ASR warmup runs on **first mic press** (`beginUtterance`), gated by
`HostMemoryBudget` (~260 MB RSS). `hostHeavy` is set only while heavy work
actually runs, then cleared. A sticky `hostHeavy` (host died mid-work)
expires after `hostHeavyMaxAge` (~120 s) and is cleared on
`clearFlowState` / host-launch reconciliation so typing 中文/EN is not
permanently blocked.
## Console checklist
Filter Console by process separately: host `OSGKeyboard` vs extension
`com.osgkeyboard.ios.keyboard`. Host-only filter will never show `KVC.*`.
| Scenario | Expect |
|----------|--------|
| **Force-quit host**, open Notes, switch to OSG | First `dyld.constructor`, then `KVC.init``viewDidLoad``viewDidAppear` |
| Host foreground right after launch | `skip capture` + `postOnboardingWarmup scheduled … delay=45s` (no immediate Rime/CLM) |
| ~45 s later, host still active | Serial `rime.installIfNeeded` then `clm.prepare` |
| First mic press | `scheduleASRWarmup`; `hostHeavy` only while work runs |
| Switch to typing | Single `rime.prepare` / `englishPrepare` |
If neither `dyld.constructor` nor `KVC.init` appears after force-quitting the host,
the extension is dying in dyld (Shared≈9.4 MB + librime) — next lever is splitting
Rime out of Shared.
## RSS comparison (optional)
Record `OSGDiag` `rss=` tags for:
1. Cold voice surface only
2. Cold typing after prepare
3. Host foreground + extension
Target: typing peak below the old “voice + eager Librime construct” baseline.
## Extension physical-footprint budget
`phys_footprint` is the primary extension metric because device jetsam follows
it more closely than RSS. `KeyboardExtensionMemoryTelemetry` records structured
`[OSGDiag/memory] extMemory` lines at lifecycle and heavy-resource milestones,
plus 50 ms samples during the first four seconds:
- **Normal:** below 36 MiB
- **Warning:** 3640 MiB
- **High:** 4048 MiB; 40 MiB is the internal safe peak
- **Critical:** 48 MiB or above
The approximate 60 MiB device boundary is not a public Apple contract. The
40 MiB target deliberately reserves room for transient SwiftUI, Rime, and
system-framework pages. Telemetry is observation-only: crossing a band logs
`crossed=1` but does not change the selected surface or unload resources.
Each record includes the current and peak footprint, delta from process start,
elapsed startup time, surface/language, Full Access, clipboard state, and
operation-specific context. Filter Console by `OSGDiag/memory` and compare:
1. `KVC.viewDidLoad.afterInstallServices`
2. `KVC.viewDidLoad.afterInstallSwiftUI`
3. `typing.englishPrepare.done`
4. `typing.rimePrepare.done`
5. `clipboard.reload.done`
6. `KVC.viewDidAppear.done`
## Structural split
- Extension links **OSGKeyboardShared** only (no Charts / StoreKit / Speech / HostSupport).
- Host embeds **OSGKeyboardHostSupport** (ASR, CLM, CloudASR, tip/charts UI).
- Heavy assets (`osg_pinyin.dict.yaml`, CLM bin, licenses, local-asr catalog)
ship in the **host app** bundle; extension reads Rime from App Group after deploy.