[sse] Memory leak and missing cleanup when app-level retries are exhausted
## Bug Report
**Package:** `@solid-primitives/sse`
**File:** [`packages/sse/src/sse.ts`](https://github.com/solidjs-community/solid-primitives/blob/af34b836baba599c525d0db4b1c9871dd0b13f27/packages/sse/src/sse.ts#L252)
---
### Description
In `createSSE`, the `handleError` function inside `_open` correctly guards app-level reconnects behind the `es.readyState === SSEReadyState.CLOSED` check. However, when `retriesLeft` reaches `0` and the browser has permanently closed the connection, there is **no cleanup performed**. This causes:
1. **`currentCleanup` is never called** — `source.close()` and `removeEventListener` calls inside the `makeSSE` cleanup are never invoked, leaving the `EventSource` instance and its event listeners alive in memory.
2. **`source` signal is never set to `undefined`** — callers continue to see a stale `SSESourceHandle`.
3. **`currentCleanup` reference is never cleared** — the closure is retained unnecessarily.
---
### Relevant Code
```ts
// packages/sse/src/sse.ts ~L258
if (es.readyState === SSEReadyState.CLOSED && retriesLeft > 0) {
retriesLeft--;
reconnectTimer = setTimeout(() => _open(resolvedUrl), reconnectConfig.delay ?? 3000);
}
// ❌ No else branch: when retriesLeft === 0, cleanup is never called
```
---
### Expected Behavior
When retries are exhausted and `es.readyState === CLOSED`, the library should:
- Call `currentCleanup()` to invoke `source.close()` and remove all event listeners
- Set `currentCleanup = undefined`
- Set `source` signal to `undefined`
### Suggested Fix
```ts
if (es.readyState === SSEReadyState.CLOSED && retriesLeft > 0) {
retriesLeft--;
reconnectTimer = setTimeout(() => _open(resolvedUrl), reconnectConfig.delay ?? 3000);
} else if (es.readyState === SSEReadyState.CLOSED) {
// Retries exhausted — clean up fully to avoid memory/listener leaks
currentCleanup?.();
currentCleanup = undefined;
setSource(undefined);
}
```
---
### Steps to Reproduce
```ts
const { readyState } = createSSE("https://unreachable.example.com/events", {
reconnect: { retries: 3, delay: 500 },
});
// After 3 failed attempts, event listeners remain attached and
// currentCleanup is never called.
```
0 条评论