Class: Coatepec::Spec::GuardedForkStrategy

Inherits:
ForkStrategy show all
Defined in:
lib/coatepec/spec/guarded_fork_strategy.rb

Overview

Opt-in macOS fork strategy: attempts Process.fork like ForkStrategy (reusing the warm worker's boot), but only after two cheap guard checks pass, and transparently falls back to a fresh SpawnStrategy run -- for this call only -- when a guard fails or the forked child crashes. See docs/superpowers/specs/2026-07-31-macos-guarded-fork-design.md.

Constant Summary collapse

BUILTIN_UNSAFE_GEMS =

Intentionally empty at ship time: the one documented crash this guards against didn't name a specific culprit gem, just "something ObjC-initializing on another thread." This is an extension point for real incidents (refine via a project's own .coatepec.yml macos_fork_unsafe_gems), not a researched-and-complete list.

[].freeze
THREAD_COUNT_TOLERANCE =

Live thread count is allowed to exceed the post-boot baseline by this much before the guard treats it as "something is mid-init."

1
CRASH_SIGNAL_NAMES =
%w[ABRT SEGV BUS].freeze
MIN_RETRY_TIMEOUT_SECONDS =

A crash retry shares one timeout budget with the fork attempt that preceded it: WorkerManager only gives the whole dispatch timeout_seconds + 10 before Client#read_response raises DisconnectedError and the warm worker is restarted from scratch -- exactly the boot this feature exists to preserve. The floor keeps a nearly-exhausted budget from turning the retry into a guaranteed timeout kill; a few seconds is enough for a fast spec to still land.

5
MAX_CRASH_STDERR_BYTES =

The crashed child's stderr carries the macOS crash report -- the only evidence that could ever populate BUILTIN_UNSAFE_GEMS from a real incident. Kept far below Result::MAX_OUTPUT_BYTES because it rides along with a whole second result inside MCP::Response's 1 MiB cap.

4 * 1024

Instance Method Summary collapse

Constructor Details

#initialize(project_root, project: nil, rails_runtime: nil) ⇒ GuardedForkStrategy

Returns a new instance of GuardedForkStrategy.



39
40
41
42
# File 'lib/coatepec/spec/guarded_fork_strategy.rb', line 39

def initialize(project_root, project: nil, rails_runtime: nil)
  super
  @spawn_strategy = SpawnStrategy.new(project_root)
end

Instance Method Details

#run(args, timeout_seconds) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/coatepec/spec/guarded_fork_strategy.rb', line 44

def run(args, timeout_seconds)
  return fallback_result(args, timeout_seconds, "spawn_fallback") unless guard_passes?

  started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  begin
    result = super
  rescue SystemCallError
    # Process.fork itself failed (Errno::EAGAIN/ENOMEM under
    # process-table pressure), so no child was ever produced -- from the
    # caller's side that is indistinguishable from a failed guard, hence
    # the same mode. Opting into macos_fork must never surface an error
    # that plain SpawnStrategy wouldn't have.
    return fallback_result(args, timeout_seconds, "spawn_fallback")
  end
  return result.merge(execution_mode: "fork") unless crashed?(result)

  retry_after_crash(args, timeout_seconds, started_at, result)
end