Repository navigation
Commit 2a2a4c1
authored
Give reflex run one owner for shutdown (#7650)
* Give reflex run one owner for shutdown
`reflex run` had no single place that stops its process tree. Each layer
reacted to whatever signal reached it and relied on the terminal's process
group, Python's KeyboardInterrupt, or exit-code conventions to reach the
others. Every delivery route that differed from a terminal Ctrl-C became
its own bug: a pid-targeted SIGTERM left `npm run`'s node server behind
and hung the CLI on its pipe (#7485), the JSON supervisor's drain cap
started only after the child exited so a stalled consumer blocked exit
forever and a slow one got a cut record (#7510), and a second Ctrl-C
could interrupt the cleanup the first one started.
The shutdown now converges on one path at each layer:
- `reflex_base.utils.processes.stop_process_tree` signals a process and
every descendant it can find (/proc on Linux, ps elsewhere, taskkill /T
on Windows), waits for all of them against one deadline and kills what
is left. Processes stay in the CLI's process group, so a terminal
Ctrl-C, Ctrl-Z or a group kill still reach them directly.
- `_run_dev` installs idempotent SIGINT/SIGTERM handlers around the run:
the first signal unwinds into cleanup, repeats are ignored. Its cleanup
stops the whole frontend tree with that helper, on every platform, and
`stream_logs` no longer reports an exit the CLI asked for as a failure.
- `supervise_output` forwards every SIGINT/SIGTERM to the command, arms a
15 s deadline from the first one after which the command tree is
killed, anchors the 30 s drain wall at the request, and writes record
by record while stopping so the drain ends between two complete records.
Regression coverage: unit tests for the tree helper, the no-TTY run with
a frontend that does not forward signals, the supervisor with a stalled
consumer during chatty cleanup, a repeated signal, a command that ignores
shutdown, and a slow consumer at the drain cut; plus an integration matrix
that stops a real `reflex run` by group SIGINT, pid SIGINT and pid SIGTERM
in plain, --json and npm configurations and checks exit time, survivors
and error output.
Closes #7485
Closes #7510
Claude-Session: https://claude.ai/code/session_012Ps5W65XWRr7LRi3c4DvQU
* Write JSON output in pipe-atomic chunks and treat zombies as exited
Review findings on #7650:
- A batch blocked in the reader thread when shutdown started was still
one write, so an abandoned write could cut a record. Records are now
grouped into writes of at most PIPE_BUF bytes at every stage, which a
pipe accepts whole or not at all; the per-record mode after a stop
request goes away with it. Write counts and throughput for a chatty
child match main.
- A descendant that exited but was never reaped, as happens for orphans
under a non-reaping PID 1, counted as alive and cost the whole stop
timeout. _alive now reaps orphans handed to this process and reads the
Linux process state, so a zombie counts as gone.
Claude-Session: https://claude.ai/code/session_012Ps5W65XWRr7LRi3c4DvQU
* Stop the whole tree on Windows and guard the run's cleanup window
Review findings on #7650 and the Windows unit-test failure:
- The JSON supervisor forwarded SIGTERM on Windows as TerminateProcess on
the command alone, leaving its workers behind; it now stops the whole
tree with taskkill at once, since Windows has no graceful signal.
- The run's SIGINT/SIGTERM guard is installed on every platform, and the
run marks the start of its cleanup so a first signal arriving during
the frontend stop is ignored instead of aborting it.
- Test helpers no longer race a process that exits between the liveness
probe and the /proc read, the tree test checks ordering by membership,
and the Windows cleanup uses SIGTERM, which exists there.
Claude-Session: https://claude.ai/code/session_012Ps5W65XWRr7LRi3c4DvQU
* Use a Windows-safe pipe write size in the JSON supervisor
select.PIPE_BUF exists only on POSIX; reading it on Windows raised in the
reader thread as soon as a batch held two records. The size is now a
module constant with the Linux value as fallback, the test reads that
constant, and the supervisor docstring scopes the record-boundary
guarantee to records that fit one write.
Claude-Session: https://claude.ai/code/session_012Ps5W65XWRr7LRi3c4DvQU
* Keep the new test imports at module level
The slow-consumer drain test no longer shrinks the pipe with fcntl: the
default pipe already holds far less than the test writes, so the cut
happens regardless, and the test now runs on every POSIX platform. psutil
is imported at the top of the tree test module like elsewhere in tests.
Claude-Session: https://claude.ai/code/session_012Ps5W65XWRr7LRi3c4DvQU
* End the drain grace with the write and drop the dead psutil kill
The grace after the drain deadline waited for the reader thread to exit,
so a descendant that still held the pipe open cost the full two seconds
even after the write finished. It now waits for the write in progress.
exec.kill lost its only caller when the frontend tree stop replaced the
Windows workaround; remove it. Parse /proc/<pid>/stat in one helper.
* Floor reflex-base at 0.10.2.dev0 for the process-tree helper
reflex imports reflex_base.utils.processes, which is unreleased. The
floor was 0.10.1.dev0, which the published reflex-base 0.10.1 satisfies
without that module, so check-min-deps resolved to it and failed.
Claude-Session: https://claude.ai/code/session_012Ps5W65XWRr7LRi3c4DvQU
* Move the output supervisor out of log.py
The JSON supervisor shares no code with the logging pipeline, so it now
lives in reflex_base.utils.supervisor with its tests. The process tree
helper is renamed to reflex_base.utils.process_tree so it no longer
shares a name with reflex.utils.processes. No logic changes.
* Force the exit on a later repeat of the stop signal
A repeat within a second of the first SIGINT or SIGTERM is still the same
request delivered twice and is ignored. A later one means the cleanup is
stuck, for example on a frontend pipe that a process outside the tree
holds open: every descendant is killed and reflex run exits at once.
* Make the forced exit kill the saved tree and never block on stderr
A forced exit found descendants by a fresh scan below reflex run, so a
server whose wrapper had already exited during the cleanup, the npm case
this branch fixes, survived it and kept its port. The cleanup now records
everything below the process when it begins, and the forced exit kills
that set as well as whatever a fresh scan finds.
The forced exit also wrote its message before killing anything, so a
stderr pipe nobody read could hold it up. The message is written only
when stderr accepts it without blocking, after the kill.
Claude-Session: https://claude.ai/code/session_012Ps5W65XWRr7LRi3c4DvQU
* Bound the forced-exit tests' reads and note the Windows limit
The scripted children are read line by line with a deadline from an
unbuffered pipe, so a child that never prints fails the test instead of
hanging the suite. The test that keeps a killed grandchild as a zombie
under its living parent runs on Linux only, where the zombie is seen as
gone. The docstrings say that on Windows a forced exit ends only the CLI,
since a console Ctrl-C already reached every process on the console.
Claude-Session: https://claude.ai/code/session_012Ps5W65XWRr7LRi3c4DvQU
* Restore the shutdown handlers when the cleanup begins
granian installs its own SIGINT/SIGTERM handlers while it serves and
never restores ours, so after the backend returned a later Ctrl-C only
set granian's flag and the forced exit never ran in a real app.
begin_cleanup now puts the handlers back.
* Spare a reused process ID on a forced exit
The forced exit kills the process IDs recorded when the cleanup began.
Between then and a later repeat of the signal an ID can be freed and
taken by an unrelated process. A recorded ID is now killed only while it
is still in the root's process group, which every process reflex run
starts stays in; one that left the group belongs to someone else.
Claude-Session: https://claude.ai/code/session_012Ps5W65XWRr7LRi3c4DvQU
* Give the kill_descendants tests a root of their own
The two tests for recorded IDs used the test runner as the root, so the
kill reached every live descendant of pytest. They now kill below a
child started for the purpose.
Claude-Session: https://claude.ai/code/session_012Ps5W65XWRr7LRi3c4DvQU1 parent bac5d98 commit 2a2a4c1
15 files changed
Lines changed: 2242 additions & 719 deletions
File tree
- news
- packages/reflex-base
- news
- src/reflex_base/utils
- reflex
- utils
- tests
- integration
- units
- reflex_base/utils
- utils
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
29 | | - | |
30 | 29 | | |
31 | | - | |
32 | | - | |
33 | 30 | | |
34 | | - | |
35 | 31 | | |
36 | 32 | | |
37 | 33 | | |
| |||
264 | 260 | | |
265 | 261 | | |
266 | 262 | | |
267 | | - | |
268 | | - | |
269 | | - | |
270 | | - | |
271 | | - | |
272 | | - | |
273 | | - | |
274 | | - | |
275 | | - | |
276 | | - | |
277 | | - | |
278 | | - | |
279 | | - | |
280 | | - | |
281 | | - | |
282 | | - | |
283 | | - | |
284 | | - | |
285 | | - | |
286 | | - | |
287 | | - | |
288 | | - | |
289 | | - | |
290 | | - | |
291 | | - | |
292 | | - | |
293 | | - | |
294 | | - | |
295 | | - | |
296 | | - | |
297 | | - | |
298 | | - | |
299 | | - | |
300 | | - | |
301 | | - | |
302 | | - | |
303 | | - | |
304 | | - | |
305 | | - | |
306 | | - | |
307 | | - | |
308 | | - | |
309 | | - | |
310 | | - | |
311 | | - | |
312 | | - | |
313 | | - | |
314 | | - | |
315 | | - | |
316 | | - | |
317 | | - | |
318 | | - | |
319 | | - | |
320 | | - | |
321 | | - | |
322 | | - | |
323 | | - | |
324 | | - | |
325 | | - | |
326 | | - | |
327 | | - | |
328 | | - | |
329 | | - | |
330 | | - | |
331 | | - | |
332 | | - | |
333 | | - | |
334 | | - | |
335 | | - | |
336 | | - | |
337 | | - | |
338 | | - | |
339 | | - | |
340 | | - | |
341 | | - | |
342 | | - | |
343 | | - | |
344 | | - | |
345 | | - | |
346 | | - | |
347 | | - | |
348 | | - | |
349 | | - | |
350 | | - | |
351 | | - | |
352 | | - | |
353 | | - | |
354 | | - | |
355 | | - | |
356 | | - | |
357 | | - | |
358 | | - | |
359 | | - | |
360 | | - | |
361 | | - | |
362 | | - | |
363 | | - | |
364 | | - | |
365 | | - | |
366 | | - | |
367 | | - | |
368 | | - | |
369 | | - | |
370 | | - | |
371 | | - | |
372 | | - | |
373 | | - | |
374 | | - | |
375 | | - | |
376 | | - | |
377 | | - | |
378 | | - | |
379 | | - | |
380 | | - | |
381 | | - | |
382 | | - | |
383 | | - | |
384 | | - | |
385 | | - | |
386 | | - | |
387 | | - | |
388 | | - | |
389 | | - | |
390 | | - | |
391 | | - | |
392 | | - | |
393 | | - | |
394 | | - | |
395 | | - | |
396 | | - | |
397 | | - | |
398 | | - | |
399 | | - | |
400 | | - | |
401 | | - | |
402 | | - | |
403 | | - | |
404 | | - | |
405 | | - | |
406 | | - | |
407 | | - | |
408 | | - | |
409 | | - | |
410 | | - | |
411 | | - | |
412 | | - | |
413 | | - | |
414 | | - | |
415 | | - | |
416 | | - | |
417 | | - | |
418 | | - | |
419 | | - | |
420 | | - | |
421 | | - | |
422 | | - | |
423 | | - | |
424 | | - | |
425 | | - | |
426 | | - | |
427 | | - | |
428 | | - | |
429 | | - | |
430 | | - | |
431 | | - | |
432 | | - | |
433 | | - | |
434 | | - | |
435 | | - | |
436 | | - | |
437 | | - | |
438 | | - | |
439 | | - | |
440 | | - | |
441 | | - | |
442 | | - | |
443 | | - | |
444 | | - | |
445 | | - | |
446 | | - | |
447 | | - | |
448 | | - | |
449 | | - | |
450 | | - | |
451 | | - | |
452 | | - | |
453 | | - | |
454 | | - | |
455 | | - | |
456 | | - | |
457 | | - | |
458 | | - | |
459 | | - | |
460 | | - | |
461 | | - | |
462 | | - | |
463 | | - | |
464 | | - | |
465 | | - | |
466 | | - | |
467 | | - | |
468 | | - | |
469 | | - | |
470 | | - | |
471 | | - | |
472 | | - | |
473 | | - | |
474 | | - | |
475 | | - | |
476 | | - | |
477 | | - | |
478 | | - | |
479 | | - | |
480 | | - | |
481 | | - | |
482 | | - | |
483 | | - | |
484 | | - | |
485 | | - | |
486 | | - | |
487 | | - | |
488 | | - | |
489 | | - | |
490 | | - | |
491 | | - | |
492 | | - | |
493 | | - | |
494 | | - | |
495 | | - | |
496 | | - | |
497 | | - | |
498 | | - | |
499 | | - | |
500 | | - | |
501 | | - | |
502 | | - | |
503 | | - | |
504 | | - | |
505 | | - | |
506 | | - | |
507 | | - | |
508 | | - | |
509 | | - | |
510 | | - | |
511 | | - | |
512 | | - | |
513 | | - | |
514 | | - | |
515 | | - | |
516 | | - | |
517 | | - | |
518 | | - | |
519 | | - | |
520 | | - | |
521 | | - | |
522 | | - | |
523 | | - | |
524 | | - | |
525 | | - | |
526 | | - | |
527 | | - | |
528 | | - | |
529 | | - | |
530 | | - | |
531 | | - | |
532 | | - | |
533 | | - | |
534 | | - | |
535 | | - | |
536 | | - | |
537 | 263 | | |
538 | 264 | | |
539 | 265 | | |
| |||
0 commit comments