Class: RailsConsole::Broker

Inherits:
Object
  • Object
show all
Includes:
Logging
Defined in:
lib/rails-console/broker.rb

Overview

Owns the single PTY console session per container. Puma workers reach it over a local Unix socket, so any worker can serve the browser's WebSocket and hit the same session.

Constant Summary collapse

REDRAW =

Ctrl-L: tells the console to redraw its prompt so a just-attached client sees it.

"\f"
RELOAD_MARKER =

Sent to clients when the session is replaced (safe!/unsafe!), so the frontend resets the screen and shows the booting spinner again. NUL-delimited so it never collides with real terminal output.

"\x00CONSOLE_RELOAD\x00"
SAFE_MARKER =

The console prints this to ask for sandbox (safe) mode. NUL bytes never appear in terminal output, so it can't be triggered by accident.

"\x00CONSOLE_SAFE_REQUEST\x00"
SPAWN_TIMEOUT =

Seconds to wait for a freshly spawned broker to start listening.

15
UNSAFE_MARKER =

The console prints this to ask for write (unsafe) mode. NUL bytes never appear in terminal output, so it can't be triggered by accident.

"\x00CONSOLE_UNSAFE_REQUEST\x00"

Constants included from Logging

Logging::PREFIX

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Logging

#log, #log_warn

Constructor Details

#initialize(safe_command: nil, socket_path: nil, unsafe_command: nil) ⇒ Broker

Returns a new instance of Broker.



62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/rails-console/broker.rb', line 62

def initialize(safe_command: nil, socket_path: nil, unsafe_command: nil)
  @clients = []
  @console_session = nil
  @idle_timer = nil
  @mutex = Mutex.new
  @owner_label = nil
  @pty_session = nil
  @safe_command = safe_command
  @server = nil
  @shutting_down = false
  @socket_path = socket_path || RailsConsole.socket_path
  @unsafe_command = unsafe_command
end

Instance Attribute Details

#console_sessionObject (readonly)

Returns the value of attribute console_session.



32
33
34
# File 'lib/rails-console/broker.rb', line 32

def console_session
  @console_session
end

#owner_labelObject (readonly)

Returns the value of attribute owner_label.



32
33
34
# File 'lib/rails-console/broker.rb', line 32

def owner_label
  @owner_label
end

Class Method Details

.boot(socket_path: nil) ⇒ Object

rubocop:disable Naming/PredicateMethod



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/rails-console/broker.rb', line 34

def self.boot(socket_path: nil) # rubocop:disable Naming/PredicateMethod
  socket_path ||= RailsConsole.socket_path

  return true if running?(socket_path:)

  broker_bin = Gem.bin_path('rails-console', 'rails_console')

  Process.spawn('bundle', 'exec', broker_bin, chdir: Rails.root.to_s)

  SPAWN_TIMEOUT.times do
    return true if running?(socket_path:)

    sleep(1)
  end

  false
end

.running?(socket_path: nil) ⇒ Boolean

Returns:

  • (Boolean)


52
53
54
55
56
57
58
59
60
# File 'lib/rails-console/broker.rb', line 52

def self.running?(socket_path: nil)
  socket_path ||= RailsConsole.socket_path

  UNIXSocket.new(socket_path.to_s).close

  true
rescue Errno::ENOENT, Errno::ECONNREFUSED
  false
end

Instance Method Details

#runObject



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/rails-console/broker.rb', line 76

def run
  return log("Already listening on #{@socket_path}, aborting.") if already_running?

  prepare_socket_path

  @server = UNIXServer.new(@socket_path)

  log("Listening on #{@socket_path}")

  loop { Thread.new(@server.accept) { |client| handle_client(client) } }
rescue IOError
  nil
ensure
  @server&.close
end

#shutdownObject



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/rails-console/broker.rb', line 92

def shutdown
  @shutting_down = true

  @mutex.synchronize do
    @idle_timer&.kill
    @idle_timer = nil

    @clients.each do |item|
      item.close
    rescue IOError
      nil
    end

    @clients.clear

    @pty_session&.kill!
  end

  @server&.close
  @server = nil
end