apache / apache/maka

Desktop transcript consumers survive Renderer loss and accumulate WebContents listeners

Open
#5,136 3 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
TypeScript
Stars
5.4k
Forks
502
Avg merge
1d 2h
Merged PRs (30d)
716

Description

## What happened

Desktop transcript registrations are cleaned up when their target `WebContents` emits `destroyed`, but main Renderer crash/reload replaces the renderer frame/process without destroying that `WebContents`. Registrations owned by the retired renderer generation therefore remain in `RuntimeHostSessionObservationRegistry`.

When the recovered Renderer opens a transcript with a new consumer ID, another `once('destroyed')` listener is added to the same `WebContents`. Repeated recovery/reload cycles accumulate transcript registrations and listeners until Node reports:

```text
MaxListenersExceededWarning: Possible EventEmitter memory leak detected.
11 destroyed listeners added to [WebContents].
```

Expected behavior: transcript consumers should be retired when the renderer document/process that created them is gone. Recovery should not retain obsolete Host transcript consumers or accumulate `destroyed` listeners, and cleanup must not close consumers belonging to the replacement renderer generation.

## How to reproduce

The following was verified with Electron and the repository's `RuntimeHostSessionObservationRegistry`:

1. Create a hidden `BrowserWindow` and load a page.
2. Create 11 distinct transcript consumers through `RuntimeHostSessionObservationRegistry.openTranscript(...)`, all targeting that window's `webContents` (the same target used by `sessions:transcript:open`).
3. Record `webContents.listenerCount('destroyed')`.
4. Call `webContents.forcefullyCrashRenderer()` and await `render-process-gone`.
5. Load a replacement page into the same `WebContents` and record the listener count again.

Observed:

```json
{
"webContentsDestroyed": false,
"destroyedEvents": 0,
"listenersBeforeCrash": 13,
"listenersAfterCrash": 13,
"listenersAfterReload": 13,
"destroyedEventsAfterReload": 0,
"listenersAfterRegistryClose": 2
}
```

The two non-transcript listeners are the harness baseline. The 11 listeners added by `openTranscript()` survived both renderer-process loss and replacement-page load; explicit registry close removed them. `--trace-warnings` points the warning directly to `RuntimeHostSessionObservationRegistry.openTranscript`.

## Environment

- Maka commit: `a9b4f21afb17e0ef310d076382e69ddda3a441d7`
- Electron: `43.4.1`
- Original observation: Windows source checkout, Desktop, `npm run dev`
- Deterministic verification: Linux x86_64 (`7.0.0-30-generic`), Desktop under Xvfb
- Node.js: `v26.3.0`

## Logs, screenshots, or additional context

Current-main ownership chain:

- `runtime-host-session-execution-ipc-main.ts:204-211` passes `event.sender` as the transcript target.
- `runtime-host-session-observation-registry.ts:356-380` stores the registration and attaches `target.once('destroyed', ...)`.
- Normal close and registry close remove that listener (`:462-493`, `:641-646`).
- Main Renderer loss is observed as `render-process-gone` and recovery reloads the existing main window (`main-window.ts:220-235`, `runtime-host-boot.ts:508-528`), but transcript registrations have no equivalent renderer-generation cleanup.
- Preload allocates a fresh random consumer ID for each `transcripts.open()`, so a replacement renderer cannot reuse or explicitly close the retired generation's consumer by identity.

This is independent from the stderr flood caused by broadcasts targeting a disposed frame; it needs its own lifecycle cleanup and regression coverage.

Contributor guide

Open the contributing guide

Research direction

Start with runtime-host-session-execution-ipc-main.ts:204-211 and runtime-host-session-observation-registry.ts:356-380, then trace renderer-loss recovery in main-window.ts:220-235 and runtime-host-boot.ts:508-528. Reproduce the crash and reload sequence with openTranscript() and inspect listener counts. Done means retired renderer consumers and their destroyed listeners are cleaned up without closing consumers belonging to the replacement renderer generation, with regression coverage for repeated recovery.

Written by the indexing model from the issue text.

Assessment

Tech stack
electron, typescript
Domain
desktop
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.