Skip to content

REPL cannot survive an uncatchable fatal #16

Description

@MarcelloDuarte

An uncatchable fatal ends the session and loses all state. The common way to hit it is loading a file twice:

phunkie > :load helpers.php
// file helpers.php loaded
phunkie > :load helpers.php
Error: Cannot redeclare greet()
$ 

installDiagnosticsRendering() (src/Repl/ReplLoop.php) already renders these cleanly via a shutdown handler instead of dumping a raw stack trace, but formatting is all it can do: E_ERROR and the compile-time fatals reach neither try/catch nor a custom error handler. Surviving one means each evaluation running somewhere the REPL can outlive.

Prior exploration ruled out the psysh-style "child evaluates, returns state" model. Session state cannot cross a process boundary: ReplSession->variables is an ImmMap backed by SplObjectStorage holding closures, resources, PDO handles and Function1 (which wraps ReflectionFunction), none of which serialize. State is also split, with variables and user functions on the immutable session while classes, interfaces, traits and enums are eval'd into the global runtime with nothing replayable recorded.

Three models remain:

A. Supervisor plus replay log (recommended). A thin supervisor spawns the REPL as a worker via proc_open, and logs each successfully evaluated input as source. On a fatal it prints the error, respawns, silently replays the log to rebuild state, and carries on. Survives every fatal and the log is plain strings, so nothing needs serializing. Cost: side effects re-run once during the rare recovery, and it must be gated off the in-process test harness (DirectReplManager).

B. Supervisor plus restart fresh. Survives fatals but resets the session to empty. Least work, weakest recovery.

C. Prevent rather than isolate. Detect the redeclaration before eval so :load twice is a clean error with zero loss. Solves the reported pain and nothing else.

PHP 8.5 shrinks the problem slightly: a missing trait is now a catchable Error rather than a fatal, so that is one fewer way to lose a session.

Whichever model is chosen, verification is the same: :load a file twice, then confirm the REPL is still alive and prior variables and classes survived.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions