How Modly Notifies the Renderer Process About Python Backend Crashes via IPC
Modly broadcasts Python backend crashes to the Electron renderer by registering an exit listener on the FastAPI child process in electron/main/python-bridge.ts, then sending a python:crashed IPC message via webContents.send() when an unexpected termination is detected.
Managing a Python FastAPI backend within an Electron application requires reliable inter-process communication (IPC) to handle failures gracefully. In the lightningpixel/modly repository, the main process detects when the Python child process terminates unexpectedly and notifies the renderer through a dedicated IPC channel. This mechanism allows the React-based UI to display error toasts, attempt restarts, or enter a degraded state when the backend becomes unavailable.
Python Bridge Architecture and Process Spawning
The PythonBridge class in electron/main/python-bridge.ts manages the lifecycle of the FastAPI server as a Node.js ChildProcess. When the bridge initializes, it spawns the Python executable and maintains a reference to the process instance.
This architecture separates the Python backend from the renderer, requiring the main process to act as an intermediary. The bridge tracks process state through an internal ready flag and an intentionalStop boolean that distinguishes between user-requested shutdowns and actual crashes.
Detecting Unexpected Terminations in the Main Process
To identify genuine crashes, the bridge registers an exit event listener on the spawned process. The listener validates both the ready state and the intentionalStop flag before declaring a crash event.
When the Python process exits, the handler captures the exit code and confirms the termination was unexpected. This prevents false positives during normal application shutdown sequences.
this.process.on('exit', (code) => {
const wasReady = this.ready
console.log('[PythonBridge] Process exited with code', code)
this.ready = false
this.process = null
if (wasReady && !this.intentionalStop) {
const win = this.getWindow?.()
const contents = win?.webContents
if (contents && !contents.isDestroyed()) {
// Notify renderer of an unexpected crash
contents.send('python:crashed', { code })
}
}
})
Broadcasting Crashes via IPC to the Renderer
When an unexpected exit is confirmed, the bridge retrieves the BrowserWindow instance hosting the renderer and accesses its webContents object. It then emits the python:crashed channel with a payload containing the numeric exit code.
The hard-coded channel name ensures consistent communication across operating systems. The payload structure { code } provides the renderer with diagnostic information while maintaining a minimal API surface.
Receiving Notifications in the Renderer Process
The preload script at electron/preload/electron-api.ts exposes a type-safe API that wraps ipcRenderer. This abstraction prevents direct ipcRenderer access in the renderer while providing a clean interface for crash subscription.
The python.onCrashed method registers a callback that fires whenever the main process broadcasts a crash event. A corresponding offCrashed method allows components to clean up listeners during unmount.
python: {
// …other methods
onCrashed: (cb: (data: { code: number | null }) => void) => {
ipcRenderer.on('python:crashed', (_event, data) => cb(data as { code: number | null }))
},
offCrashed: () => ipcRenderer.removeAllListeners('python:crashed'),
}
Handling Crashes in React Components
Renderer processes typically subscribe to crash notifications within useEffect hooks or equivalent lifecycle methods. The API attaches to window.electron.python, making it accessible throughout the component tree.
When a crash occurs, the callback receives the exit code and can trigger UI updates such as error toasts, modal dialogs, or automatic restart sequences via the bridge's restart methods.
useEffect(() => {
window.electron.python.onCrashed(({ code }) => {
// Show an error toast and optionally restart the bridge
showToast(`Python backend crashed (exit code ${code ?? 'unknown'}).`, 8000)
// Optionally: window.electron.python.restart()
})
return () => window.electron.python.offCrashed()
}, [])
Summary
electron/main/python-bridge.tsspawns the Python FastAPI process and monitors itsexitevents to detect unexpected terminations.- The bridge uses an
intentionalStopflag to filter out deliberate shutdowns from genuine crashes. - Crashes are broadcast via
webContents.send('python:crashed', { code })to reach the renderer process. electron/preload/electron-api.tsexposespython.onCrashed()andpython.offCrashed()for type-safe subscription management.- The renderer receives exit codes as
{ code: number | null }, enabling detailed error reporting and recovery logic in the UI.
Frequently Asked Questions
What IPC channel does Modly use for Python backend crash notifications?
Modly uses the python:crashed channel name, hard-coded in both electron/main/python-bridge.ts and electron/preload/electron-api.ts. This consistency ensures the main process and renderer communicate reliably across macOS, Windows, and Linux.
How does Modly distinguish between intentional shutdowns and crashes?
The PythonBridge class maintains an intentionalStop boolean flag. When the user or application logic requests a normal shutdown, this flag is set to true. The exit event listener checks !this.intentionalStop before emitting the crash notification, preventing false alerts during routine closures.
Can the renderer process restart the Python backend after receiving a crash notification?
Yes. While the crash notification itself only transmits the exit code, the preload API typically exposes additional methods such as python.restart(). The renderer can invoke these methods within the onCrashed callback to attempt automatic recovery without user intervention.
Where is the crash handling logic implemented in the preload script?
The subscription logic resides in electron/preload/electron-api.ts within the python object. It wraps ipcRenderer.on('python:crashed', ...) inside the onCrashed method and provides offCrashed to call ipcRenderer.removeAllListeners, preventing memory leaks in single-page applications.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →