Class: Mxrb::DomainDiagram::Lifecycle
- Inherits:
-
Object
- Object
- Mxrb::DomainDiagram::Lifecycle
- Defined in:
- lib/mxrb/domain_diagram/lifecycle.rb
Overview
Background lifecycle for the ER editor. Control happens through a token-authenticated loopback endpoint rather than a potentially stale PID. rubocop:disable Metrics
Defined Under Namespace
Classes: Status
Constant Summary collapse
- STATE_ROOT_ENV =
'MXRB_DIAGRAM_STATE_ROOT'- START_TIMEOUT =
8- STOP_TIMEOUT =
8
Instance Attribute Summary collapse
-
#executable ⇒ Object
readonly
Returns the value of attribute executable.
-
#source ⇒ Object
readonly
Returns the value of attribute source.
-
#state_root ⇒ Object
readonly
Returns the value of attribute state_root.
Class Method Summary collapse
Instance Method Summary collapse
- #destroy(confirm: false) ⇒ Object
- #down ⇒ Object
-
#initialize(source, state_root: self.class.default_state_root, executable: $PROGRAM_NAME) ⇒ Lifecycle
constructor
A new instance of Lifecycle.
-
#run_worker(output:, modules:, port:, token:) ⇒ Object
Internal worker entrypoint used by
mxrb diagram-er __serve. - #status ⇒ Object
- #up(output: nil, modules: nil, port: nil, force: false) ⇒ Object
Constructor Details
#initialize(source, state_root: self.class.default_state_root, executable: $PROGRAM_NAME) ⇒ Lifecycle
Returns a new instance of Lifecycle.
28 29 30 31 32 |
# File 'lib/mxrb/domain_diagram/lifecycle.rb', line 28 def initialize(source, state_root: self.class.default_state_root, executable: $PROGRAM_NAME) @source = File.(source) @state_root = File.(state_root) @executable = File.(executable) end |
Instance Attribute Details
#executable ⇒ Object (readonly)
Returns the value of attribute executable.
26 27 28 |
# File 'lib/mxrb/domain_diagram/lifecycle.rb', line 26 def executable @executable end |
#source ⇒ Object (readonly)
Returns the value of attribute source.
26 27 28 |
# File 'lib/mxrb/domain_diagram/lifecycle.rb', line 26 def source @source end |
#state_root ⇒ Object (readonly)
Returns the value of attribute state_root.
26 27 28 |
# File 'lib/mxrb/domain_diagram/lifecycle.rb', line 26 def state_root @state_root end |
Class Method Details
.default_state_root ⇒ Object
34 35 36 37 38 39 |
# File 'lib/mxrb/domain_diagram/lifecycle.rb', line 34 def self.default_state_root configured = ENV[STATE_ROOT_ENV] return configured unless configured.to_s.empty? File.join(Dir.home, '.local', 'state', 'mxrb', 'diagram-er') end |
Instance Method Details
#destroy(confirm: false) ⇒ Object
98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/mxrb/domain_diagram/lifecycle.rb', line 98 def destroy(confirm: false) raise ArgumentError, 'destroy requires --yes' unless confirm state = read_state or raise ArgumentError, "no managed diagram for #{source}" current = status down if %w[running starting].include?(current.state) Array(state['managed_paths']).reverse_each do |path| remove_managed_path(path, state.fetch('output')) end FileUtils.remove_entry_secure(instance_dir) if File.exist?(instance_dir) Status.new('absent', source, nil, nil, nil, nil) end |
#down ⇒ Object
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/mxrb/domain_diagram/lifecycle.rb', line 81 def down state = read_state or raise ArgumentError, "no managed diagram for #{source}" unless running?(state) raise ArgumentError, 'managed diagram is still starting; retry in a moment' if starting?(state) mark_stopped(state.fetch('token')) return status end response = request(state, Net::HTTP::Post, '/api/admin/shutdown') raise ArgumentError, 'managed diagram refused the shutdown request' unless response.is_a?(Net::HTTPSuccess) wait_until(STOP_TIMEOUT) { !running?(read_state) } mark_stopped(state.fetch('token')) status end |
#run_worker(output:, modules:, port:, token:) ⇒ Object
Internal worker entrypoint used by mxrb diagram-er __serve.
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/mxrb/domain_diagram/lifecycle.rb', line 112 def run_worker(output:, modules:, port:, token:) state = read_state or raise ArgumentError, 'diagram lifecycle state is missing' raise ArgumentError, 'diagram lifecycle token does not match' unless state['token'] == token server = Server.new( source, output:, modules:, port:, reuse: true, lifecycle_token: token ) trap('INT') { Thread.new { server.shutdown } } trap('TERM') { Thread.new { server.shutdown } } server.start do write_state(state.merge( 'pid' => Process.pid, 'state' => 'running', 'started_at' => Time.now.utc.iso8601 )) end ensure mark_stopped(token) if defined?(token) && token end |
#status ⇒ Object
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/mxrb/domain_diagram/lifecycle.rb', line 65 def status state = read_state return Status.new('absent', source, nil, nil, nil, nil) unless state lifecycle_state = if running?(state) 'running' elsif starting?(state) 'starting' else 'stopped' end Status.new( lifecycle_state, source, state['output'], url_for(state), state['pid'], state['log'] ) end |
#up(output: nil, modules: nil, port: nil, force: false) ⇒ Object
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/mxrb/domain_diagram/lifecycle.rb', line 41 def up(output: nil, modules: nil, port: nil, force: false) previous = read_state raise ArgumentError, "diagram already running at #{url_for(previous)}" if running?(previous) raise ArgumentError, 'managed diagram is already starting' if starting?(previous) settings = settings_for(previous, output:, modules:, port:) managed_paths = prepare_output(previous, settings, force:) token = SecureRandom.hex(24) state = settings.merge( 'version' => 1, 'source' => source, 'token' => token, 'state' => 'starting', 'pid' => nil, 'log' => log_path, 'managed_paths' => managed_paths, 'created_at' => Time.now.utc.iso8601 ) write_state(state) pid = spawn_worker(state) Process.detach(pid) write_state(read_state.merge('pid' => pid)) wait_until(START_TIMEOUT) { running?(read_state, token:) } status rescue StandardError mark_stopped(token) if defined?(token) && token raise end |