Class: Clacky::Server::EPIPESafeIO

Inherits:
SimpleDelegator
  • Object
show all
Defined in:
lib/clacky/server/epipe_safe_io.rb

Overview

EPIPESafeIO — wraps an IO ($stdout / $stderr) so that writes never raise Errno::EPIPE / IOError to the calling code.

Why this exists:

The server worker process inherits fd 0/1/2 from the Master. If the
Master itself was launched in a way where its stdout/stderr eventually
becomes a broken pipe (e.g. launched by an installer that exits, or by
a GUI/IDE process that closes its end), the worker's first `puts` after
that pipe breaks raises Errno::EPIPE. Unhandled, this kills the worker
— taking all in-memory sessions, agent loops, and SSE connections down
with it, and triggering a crash loop because the new worker inherits
the same broken fd.

Behavior:

- Healthy state: delegates every method to the underlying IO. Users
  see normal terminal output (banner, request logs, etc.).
- First broken-pipe failure: catches Errno::EPIPE / IOError, swaps
  the underlying IO to /dev/null permanently, and returns silently.
  Subsequent writes succeed (into /dev/null) so the worker stays alive.
- Session state, agent loops, SSE connections all preserved.

Scope:

We only wrap $stdout / $stderr (the global variables that Kernel#puts,
Kernel#print, Kernel#warn, etc. use under the hood). We do NOT touch
the STDOUT / STDERR constants — a codebase audit confirmed nothing in
Clacky writes to those constants directly (only `STDOUT.flush` which
cannot raise EPIPE).

Constant Summary collapse

WRITE_METHODS =

Methods that perform writes and may raise Errno::EPIPE. We override each one to rescue and degrade gracefully.

%i[write write_nonblock syswrite puts print printf << putc].freeze

Instance Method Summary collapse

Instance Method Details

#fell_back?Boolean

Whether this wrapper has already fallen back to /dev/null. Useful for tests and diagnostics.

Returns:

  • (Boolean)


73
74
75
# File 'lib/clacky/server/epipe_safe_io.rb', line 73

def fell_back?
  @fell_back == true
end

#flushObject

Some callers do ‘$stdout.flush`. Make it safe too.



64
65
66
67
68
69
# File 'lib/clacky/server/epipe_safe_io.rb', line 64

def flush
  __getobj__.flush
rescue Errno::EPIPE, IOError => e
  fall_back_to_null!(e)
  nil
end