fix: start background service from FCM handler when service is dead#489
fix: start background service from FCM handler when service is dead#489AndreaDiazCorreia wants to merge 3 commits intomainfrom
Conversation
- Add fcm-wake handler to background service to acknowledge wake signals - When FCM push arrives and service is running, send fcm-wake signal - When service is dead, start it and send app settings from SharedPreferences - Add 3-second timeout with 100ms polling to wait for service initialization - Set fcm.pending_fetch flag before starting service
…andler - Wrap jsonDecode in try-catch to prevent crashes on malformed settings - Only invoke service.start if both service is running AND settings decoded successfully - Add debug print for decode failures
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review infoConfiguration used: Organization UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
WalkthroughBackground startup and FCM wake flow were changed: background event handlers (including a new Changes
Sequence Diagram(s)sequenceDiagram
participant FCM as Firebase Cloud Messaging
participant Handler as FCM Background Handler
participant Prefs as SharedPreferences
participant BgService as Background Service
participant DB as Database/EventStorage
FCM->>Handler: Background message arrives
Handler->>BgService: Is service running?
alt Service running
Handler->>BgService: Emit 'fcm-wake'
BgService->>BgService: Handle wake (subscriptions active)
else Service not running
Handler->>Handler: set fcm.pending_fetch
Handler->>Prefs: Load `appSettings`
Prefs-->>Handler: Return settings JSON
Handler->>Handler: Decode JSON (safe)
Handler->>BgService: Start service with settings
Handler->>Handler: Wait up to 3s for readiness
BgService->>DB: After start, DB open / EventStorage init
BgService->>BgService: Initialize subscriptions with settings
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
lib/services/fcm_service.dart (1)
56-56:startService()return value is silently discarded.
startService()returnsFuture<bool>. If it returnsfalse(e.g., Android denied service start due to background restrictions or battery optimization), the code falls through to the polling loop, burns the full 3-second budget, and exits with the service not running andfcm.pending_fetchalready set — which is acceptable but could be made explicit.✨ Optional early-exit improvement
- await service.startService(); + final started = await service.startService(); + if (!started) return; // fcm.pending_fetch is already set as fallback🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/services/fcm_service.dart` at line 56, The call to service.startService() (in fcm_service.dart) ignores its Future<bool> result so failures (false) fall through to the polling loop; change the code to await the boolean, and if it is false perform an explicit early exit: log or record the failure (update fcm.pending_fetch or processLogger as appropriate) and return immediately instead of entering the 3-second polling loop; ensure the check references service.startService() and the existing fcm.pending_fetch handling so the behavior stays consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/services/fcm_service.dart`:
- Line 66: Replace the debugPrint call with the project logger singleton: import
the logger from 'package:mostro_mobile/services/logger_service.dart' and call
logger.error(...) (or logger.w/ logger.d as appropriate) instead of debugPrint;
because this runs in an isolate where the logger transport may not be
registered, add a defensive fallback that uses debugPrint only if the logger
singleton is not available/initialized; also update the other two occurrences in
this file (the debugPrints at the existing lines ~85 and ~90) to follow the same
pattern so all FCM handler logging uses the logger singleton with a debugPrint
fallback.
- Around line 72-80: Move the service event handler registrations so they run
before the database open awaits to avoid lost events: inside serviceMain,
register service.on('start') and service.on('fcm-wake') listeners before calling
await openMostroDatabase(...); the start handler can still call
nostrService.init(settings) (it doesn't depend on db/eventStore), so reorder the
code so the on('start') and on('fcm-wake') listen() calls occur prior to await
openMostroDatabase(...) to eliminate the race where invoke('start', ...) is
delivered before the listener is attached.
---
Nitpick comments:
In `@lib/services/fcm_service.dart`:
- Line 56: The call to service.startService() (in fcm_service.dart) ignores its
Future<bool> result so failures (false) fall through to the polling loop; change
the code to await the boolean, and if it is false perform an explicit early
exit: log or record the failure (update fcm.pending_fetch or processLogger as
appropriate) and return immediately instead of entering the 3-second polling
loop; ensure the check references service.startService() and the existing
fcm.pending_fetch handling so the behavior stays consistent.
…revent race condition - Move service.on() registrations above openMostroDatabase() call - Prevents losing 'start' events invoked by FCM handler during db initialization - Add comment explaining the ordering requirement - Keep db and eventStore initialization after handlers are registered
There was a problem hiding this comment.
Good fix for a real problem — FCM pushes arriving with a dead background service were silently lost. The approach (start service + send settings) is correct. The race condition fix in commit 80ae0cc (registering listeners before await openMostroDatabase) was the right call. Two issues remain:
Must Fix
1. debugPrint instead of logger singleton
Per AGENTS.md: "Always use the pre-configured singleton logger instance. Direct instantiation of Logger() is no longer permitted."
Line 66 uses debugPrint('FCM: Failed to decode settings: $e'). The two pre-existing debugPrint calls (lines 85, 90) were already there, but adding a new one violates the rule. I know the FCM background handler runs in a separate isolate where the logger singleton might not be available — if that's the case, document it with a comment explaining why debugPrint is used here as an exception. Don't just silently break the convention.
2. fcm.pending_fetch flag is set but never useful in the new flow
Line 56: await sharedPrefs.setBool('fcm.pending_fetch', true) is set before startService(). But the new code immediately starts the service and sends settings — so pending_fetch is never consumed in this path. Looking at the codebase, _checkPendingFetch in FCMService only clears the flag without triggering any real action. This is dead logic:
- If
startService()+invoke('start')succeeds → the service processes events directly,pending_fetchis never read - If
startService()fails → the catch block setspending_fetchagain (line 87), which is also never consumed meaningfully
Either remove the flag from this flow (it's misleading), or make _checkPendingFetch actually do something useful as a fallback.
Minor
3. 3-second busy-wait poll in a background handler
The while (!(await service.isRunning())) loop with 100ms delays is a busy-wait in an FCM background handler. Android gives FCM handlers ~20 seconds, so 3 seconds isn't a timeout risk. But it's wasteful — consider using service.on('service-ready') as a signal instead of polling, or at least use exponential backoff (100ms → 200ms → 400ms) to reduce unnecessary isRunning() IPC calls.
4. No settings = silent failure
If settingsJson is null (fresh install, cleared data), the service starts but invoke('start') is never called. The service runs as a zombie — no relay connections, no notifications. At minimum log a warning so this is diagnosable.
What looks good
- ✅ CI passes, no conflicts, mergeable
- ✅ The listener reordering fix (
80ae0cc) correctly eliminates the main race condition - ✅
fcm-wakefor already-running services is lightweight and correct - ✅ Settings are read from SharedPreferences (correct approach for background isolate)
- ✅ try/catch around JSON decode prevents crash on corrupted settings
Summary
can connect to relays and process events
Test plan
Summary by CodeRabbit