Explicitly close realms on shutdown.#5933
Conversation
BundleMonFiles updated (1)
Unchanged files (3)
Total files change +467B 0% Final result: ✅ View report in BundleMon website ➡️ |
| obs.NodeObs.RemoveVolmeterCallback(); | ||
| obs.NodeObs.OBS_service_removeCallback(); | ||
| obs.IPC.disconnect(); | ||
| this.realmService.close(); |
There was a problem hiding this comment.
This only closes the worker window's Realm instance. To fully fix the stale-reader/unclean-close risk described in the PR, shutdown needs to close Realm in every renderer that opened it, or move the connection so only the worker opens it.
There was a problem hiding this comment.
Pull request overview
This PR improves shutdown hygiene by explicitly releasing long-lived runtime resources (Realm databases and websocket-related subscriptions/connections) during the app’s shutdown sequence to reduce startup latency and avoid stale LMDB reader entries.
Changes:
- Added
RealmService.close()and invoked it late inAppService.shutdownHandler()to close both persistent and ephemeral Realm instances. - Added
WebsocketService.disconnect()and called it during shutdown. - Added
RecentEventsService.shutdown()to unsubscribe from websocket events during shutdown.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| app/services/realm.ts | Adds a close() hook to close both Realm databases. |
| app/services/app/app.ts | Wires shutdown calls to recent-events/websocket cleanup and Realm close. |
| app/services/websocket.ts | Adds an explicit disconnect() helper for the socket.io client. |
| app/services/recent-events.ts | Adds a shutdown() method to stop listening for websocket events. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| close() { | ||
| this.persistentDb?.close(); | ||
| this.ephemeralDb?.close(); | ||
| } |
| obs.NodeObs.RemoveVolmeterCallback(); | ||
| obs.NodeObs.OBS_service_removeCallback(); | ||
| obs.IPC.disconnect(); | ||
| this.realmService.close(); |
| import { NavigationService } from 'services/navigation'; | ||
| import { StreamingService } from 'services/streaming'; | ||
| import { VirtualWebcamService } from 'services/virtual-webcam'; | ||
| import { WebsocketService } from 'services/websocket'; |
Close Realm Databases on Shutdown
Issues
RealmServiceopens two Realm databases at startup viaconnect():persistentDb— file-backed LMDB database (persistent.realm, schema version 3)ephemeralDb— in-memory Realm (ephemeral.realm)The class had no
close(),shutdown(), ordestroy()method. Neither database was ever explicitly closed. The shutdown sequence inAppServicemade no attempt to close either instance.Realm uses LMDB (Lightning Memory-Mapped Database) internally. LMDB maps database files into the process's virtual address space via
mmapon macOS and a memory-mapped section object on Windows. LMDB is crash-safe by design and the copy-on-write structure means the data file itself is not corrupted by an unclean close. The risk is the stale reader entry on next startup and the possibility of a discarded in-flight write transaction.Fixes
Added
close()toRealmServiceand calledthis.realmService.close()inAppService.shutdownHandler()afterobs.IPC.disconnect()and beforecrashReporterService.endShutdown(), making it the last local resource released before the shutdown complete signal is sent.Files changed:
app/services/realm.ts,app/services/app/app.tsImplications of Not Fixing
Stale reader lock entry. LMDB maintains a reader lock table in a separate
.lockfile. Each open Realm connection registers an entry in this table keyed by PID. When the process exits without callingRealm.close(), the entry is not removed. On the next startup, LMDB must scan the reader table for dead PIDs and clean up stale entries before the environment can be opened. This adds latency to every app launch following an unclean exit and is a known source ofMDB_READERS_FULLerrors in environments with many restart cycles.In-flight write transaction discarded.
Realm.close()commits or rolls back any open write transaction before releasing the environment. If the process exits without closing, any write that was in-progress at the moment of shutdown is silently discarded. Whether this is a real risk depends on whether any write is ever left open across an async boundary where the shutdown IPC could arrive but the behavior on unclean exit is silent data loss with no error.Page flush not guaranteed.
Realm.close()callsmdb_env_close()internally, which flushes dirty pages from the OS page cache to disk viamsync/FlushViewOfFile. Without this call, dirty pages are left to the OS to flush at its discretion. On a system under memory pressure, this increases the window during which committed data that has not yet reached physical storage is at risk.Performance Implications
mmaponprocess exitPIDs after unclean exitmsync/FlushViewOfFilecalled by LMDB close