Repository navigation
fix(reminder): SQLite 连接生命周期崩溃 + 围栏半径/轮询策略重设计 - #357
Merged
LUPENGHAN merged 4 commits intoAug 24, 2026
Merged
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
LUPENGHAN
force-pushed
the
pr2-sqlite-geofence-redesign
branch
from
August 24, 2026 08:36
697728a to
e6b73c8
Compare
…ce radius/polling Root-cause fix for the recurring SQLite NPE crash: expo-modules-core assigns a new SharedObjectId to the same native connection on every openDatabaseAsync() cache hit, so when the second JS-side proxy is later GC'd, it closes the shared native connection out from under the first, still-in-use proxy. Fixed with a true JS-level singleton connection (sqlite.ts) plus a strict FIFO access queue (new accessGate.ts) serializing all database access, since the connection is now genuinely shared across every independent call site. Also: - AlarmModule.presentNow() now waits for a real success/failure/timeout signal from AlarmSoundService instead of resolving true immediately. - LocalReminderApplication rescues reminders stuck in a pending disposition for over two minutes while the session is alive. - Default geofence radius raised 200m -> 400m; guard poll interval is now keyed to distance-to-boundary (radius as the near threshold) instead of distance-to-center. - ReminderGuardCoordinator checks the real native hasStartedLocationUpdatesAsync state instead of a stale local interval-change heuristic before re-registering, avoiding a race with the guard task's own execution window. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ix polling-distance approximation Fixes review findings on this PR: - Cross-account data leakage (P1): none of the headless queries in reminderGuardTask.ts (runHeadlessLocationPass/runTimeFallbackPass/ runStuckPendingPass/refreshGuardRegistration) filtered by account_id, unlike the foreground read path (SqliteLocalScheduleReader -> scheduleLocalRepository.listSchedules(accountId)). Logout never deletes the old account's local_schedules rows (the only DELETE is per-row, user-initiated), so once account B logs in on the same device, the guard task would keep evaluating/presenting/mutating account A's still-present reminders. Added a currentAccountId() helper reading the persisted SecureAuthSessionStore session (works headlessly, same dynamic-import pattern already used for expo-sqlite/ expo-notifications in this file), threaded it through all four queries, and made the whole headless pass a no-op when there is no persisted session. - Polling-distance approximation (P2): resolveNextPollIntervalMs() converted both lat/lng deltas to meters using a flat 111km/degree factor, which overestimates distance at higher latitudes (longitude degrees shrink toward the poles) and can under-tighten the polling interval near a boundary. Switched to the already-imported distanceMeters() (same helper the geofence-eval diagnostic log next to it already uses), which the file also already imports. Also fixed two real integration-test gaps this review prompted me to go find: `npm run test:vitest` was never run against this branch during earlier verification (only `npx jest` was), so two vitest integration tests were left asserting pre-PR2 values (LocationScheduleView no longer carries latitude/longitude after the earlier panel-cleanup commit; DEFAULT_GEOFENCE_RADIUS_METERS is now 400, not 200). Finding 1 (P1, dynamic interval re-registration silently failing when the background task callback runs while the app isn't foregrounded -- expo-location's own ForegroundServiceStartNotAllowedException check) is not addressed here; it needs a product decision on trade-offs before implementing and is being discussed separately. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tion Re-registering the guard task from inside its own headless callback to adjust the poll interval could crash with ForegroundServiceStartNotAllowedException whenever the app process wasn't foregrounded at that moment, since expo-location's native side checks AppForegroundedSingleton.isForegrounded unconditionally whenever foregroundService is passed. Background location permission is now a required grant by design, so the re-registration call can drop foregroundService entirely and take the plain background-permission path instead, which doesn't depend on foreground state. The initial registration in ReminderGuardCoordinator still passes foregroundService to establish the persistent notification.
…ranches Move the istanbul-ignore hints in reminderGuardTask to function level so the native-bound headless passes are actually excluded from coverage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LUPENGHAN
force-pushed
the
pr2-sqlite-geofence-redesign
branch
from
August 24, 2026 10:50
3e743fa to
17317a5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
关联 Issue
Closes #356
改动
sqlite.ts:改成真正的 JS 侧单例连接,替代之前每个调用点各自openDatabaseAsync()的做法。accessGate.ts:严格 FIFO 队列串行化所有数据库访问。AlarmModule.presentNow():等待AlarmSoundService回报的真实成功/失败/超时信号,不再调用后立即resolve(true)。LocalReminderApplication:新增救回机制,提醒 disposition 卡在pending超过 2 分钟(会话存活期间)时主动重新判定。ReminderGuardCoordinator.ensureLocationUpdates():先查Location.hasStartedLocationUpdatesAsync()的原生真实状态,只有确认没在跑时才重新调用startLocationUpdatesAsync(),不再用本地间隔变化阈值同步触发重新注册。验证
npm run check:eslint / prettier / tsc / jest 全绿(671 个用例)本轮不含(见 Issue Out of Scope)