You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(session): watchdog respects per-session TTL and suppress test noise (#64) (#64)
Watchdog used global TIMEOUT (5 min) to kill all sessions, ignoring
per-session timeout query param. Pydoll creates 1-hour persistent
sessions that got killed every ~6 minutes, causing scrape failures.
- Store timeout query param in session.ttl (was hardcoded to 0)
- Watchdog uses per-session TTL when set, falls back to global default
- Upgrade KILLING log from info→warn for production visibility
- Capture pydoll subprocess stderr (only surface on failure)
- Suppress debug logger in unit tests via env override
Copy file name to clipboardExpand all lines: docs/SESSION_LEAK_POSTMORTEM.md
+45Lines changed: 45 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -115,6 +115,51 @@ If you see memory climbing again:
115
115
```
116
116
Safe — active sessions reference dirs by handle, not path.
117
117
118
+
## Part 2: Watchdog vs Per-Session Timeout (2026-03-06)
119
+
120
+
### Summary
121
+
122
+
After deploying the `destroySession` fix (Part 1), the watchdog correctly cleaned up sessions. But sessions were STILL going stale — the watchdog fired every 60s, killing ~2 sessions aged 367-420s. The `destroySession` fix treated the symptom (orphaned data dirs); this fix addresses the root cause (why sessions go stale in the first place).
123
+
124
+
### Root Cause
125
+
126
+
The watchdog used global `TIMEOUT` env var (300s = 5 min) instead of per-session `ttl`.
127
+
128
+
Pydoll's AhrefsSessionManager creates persistent Chrome sessions with `timeout=3600000` (1 hour) via the WebSocket query param. The limiter (queue library) correctly used this as the job timeout. But the watchdog ignored it entirely and used `TIMEOUT + 60s` = 360s as the kill threshold.
129
+
130
+
**The math:**
131
+
- Watchdog threshold: `TIMEOUT + 60s` = 360s
132
+
- Watchdog poll interval: 60s
133
+
- Expected stale age: 360-420s (threshold + poll variance)
134
+
- Observed stale ages: 367-420s — exact match
135
+
136
+
### The Cascade
137
+
138
+
```
139
+
t=0: Pydoll connects with timeout=3600000 (1 hour)
Sessions with no explicit timeout (`ttl=0`) still use the global `TIMEOUT + 60s` default (unchanged behavior). Persistent sessions (e.g., Ahrefs `ttl=3600000`) now have watchdog threshold of 3,660s (1 hour + 60s buffer).
155
+
156
+
### Corrected Causal Chain
157
+
158
+
The Part 1 postmortem incorrectly attributed scrape failures to "10+ GB working set starving Chrome for memory." The VM has 50 GB RAM and the container has no memory limit. The real cause of scrape failures was the watchdog killing persistent sessions every ~6 minutes:
159
+
160
+
1. Orphaned data dirs → memory growth (real, but not the cause of scrape failures)
161
+
2. Watchdog killing persistent sessions → scrape failures (the actual root cause)
162
+
118
163
## Effect v4 Lesson Learned
119
164
120
165
`Effect.promise` treats rejections as DEFECTS (unrecoverable). `Effect.ignore` only catches typed ERRORS.
0 commit comments