siding 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CLAUDE.md +248 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +182 -0
- data/Rakefile +51 -0
- data/exe/siding +5 -0
- data/lib/siding/boot_component.rb +18 -0
- data/lib/siding/cli.rb +459 -0
- data/lib/siding/client.rb +417 -0
- data/lib/siding/error.rb +5 -0
- data/lib/siding/invocation.rb +35 -0
- data/lib/siding/life_cycle.rb +114 -0
- data/lib/siding/load_manifest.rb +475 -0
- data/lib/siding/logger.rb +89 -0
- data/lib/siding/platform.rb +37 -0
- data/lib/siding/project_key.rb +64 -0
- data/lib/siding/protocol.rb +154 -0
- data/lib/siding/restarter.rb +205 -0
- data/lib/siding/runtime.rb +165 -0
- data/lib/siding/server.rb +398 -0
- data/lib/siding/staleness.rb +191 -0
- data/lib/siding/version.rb +5 -0
- data/lib/siding/watch.rb +115 -0
- data/lib/siding/worker.rb +266 -0
- data/lib/siding.rb +27 -0
- metadata +97 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "error"
|
|
4
|
+
require_relative "protocol"
|
|
5
|
+
require_relative "life_cycle"
|
|
6
|
+
require_relative "staleness"
|
|
7
|
+
require_relative "invocation"
|
|
8
|
+
|
|
9
|
+
module Siding
|
|
10
|
+
class Worker
|
|
11
|
+
PRESERVED_KEYS = %w[
|
|
12
|
+
SIDING_SERVER
|
|
13
|
+
].freeze
|
|
14
|
+
|
|
15
|
+
RESOLUTION_KEY = Invocation::RESOLUTION_KEY
|
|
16
|
+
REVISION_KEY = Invocation::REVISION_KEY
|
|
17
|
+
BOOT_SECONDS_KEY = Invocation::BOOT_SECONDS_KEY
|
|
18
|
+
|
|
19
|
+
# A backtrace line belongs to the tool itself, not the application, if it is either a frame
|
|
20
|
+
# inside this file's own directory or the `-e:` frame `Server::BOOTSTRAP` runs as (server.rb
|
|
21
|
+
# spawns the server with `ruby -e`, so that frame has no path of its own to match). Shared
|
|
22
|
+
# between the uncaught-exception path and the rspec path so the two cannot drift apart.
|
|
23
|
+
HARNESS_FRAME_PATTERN = /\A(?:#{Regexp.escape(__dir__)}\/|-e:)/
|
|
24
|
+
|
|
25
|
+
attr_reader :connection, :message, :project_key, :manifest, :verdict, :revision_label, :boot_seconds
|
|
26
|
+
|
|
27
|
+
def initialize(connection:, message:, project_key:, manifest:, verdict:, boot_seconds: nil)
|
|
28
|
+
@connection = connection
|
|
29
|
+
@message = message
|
|
30
|
+
@project_key = project_key
|
|
31
|
+
@manifest = manifest
|
|
32
|
+
@verdict = verdict
|
|
33
|
+
@revision_label = verdict.revision_label
|
|
34
|
+
@boot_seconds = boot_seconds
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def run
|
|
38
|
+
detach_from_server
|
|
39
|
+
streams = Protocol.receive_streams(connection)
|
|
40
|
+
install_streams(streams)
|
|
41
|
+
apply_environment
|
|
42
|
+
apply_working_directory
|
|
43
|
+
LifeCycle.repair_after_fork
|
|
44
|
+
refresh_application
|
|
45
|
+
announce_state
|
|
46
|
+
watch_the_client
|
|
47
|
+
execute
|
|
48
|
+
rescue Protocol::TruncatedMessage
|
|
49
|
+
exit! 1
|
|
50
|
+
rescue SystemExit => e
|
|
51
|
+
exit e.status
|
|
52
|
+
rescue SignalException => e
|
|
53
|
+
report_uncaught(e)
|
|
54
|
+
die_of(e)
|
|
55
|
+
rescue Exception => e
|
|
56
|
+
report_uncaught(e)
|
|
57
|
+
exit! 1
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def detach_from_server
|
|
63
|
+
Process.setpgrp
|
|
64
|
+
rescue SystemCallError
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def install_streams(streams)
|
|
69
|
+
$stdin.reopen(streams[:stdin])
|
|
70
|
+
$stdout.reopen(streams[:stdout])
|
|
71
|
+
$stderr.reopen(streams[:stderr])
|
|
72
|
+
|
|
73
|
+
$stdout.sync = true
|
|
74
|
+
$stderr.sync = true
|
|
75
|
+
|
|
76
|
+
close_inherited_console
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def close_inherited_console
|
|
80
|
+
IO.console(:close) if IO.respond_to?(:console)
|
|
81
|
+
rescue StandardError
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def apply_environment
|
|
86
|
+
incoming = message["env"]
|
|
87
|
+
return unless incoming.is_a?(Hash)
|
|
88
|
+
|
|
89
|
+
preserved = PRESERVED_KEYS.to_h { |key| [key, ENV.fetch(key, nil)] }
|
|
90
|
+
ENV.replace(incoming)
|
|
91
|
+
preserved.each { |key, value| value.nil? ? ENV.delete(key) : ENV[key] = value }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def apply_working_directory
|
|
95
|
+
cwd = message["cwd"]
|
|
96
|
+
Dir.chdir(cwd) if cwd && File.directory?(cwd)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def refresh_application
|
|
100
|
+
return unless verdict.reloadable?
|
|
101
|
+
|
|
102
|
+
reloader = application_reloader
|
|
103
|
+
return if reloader.nil?
|
|
104
|
+
|
|
105
|
+
reloader.reload!
|
|
106
|
+
@revision_label = Staleness.validate(manifest).revision_label
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def application_reloader
|
|
110
|
+
return nil unless defined?(::Rails) && ::Rails.respond_to?(:application)
|
|
111
|
+
|
|
112
|
+
application = ::Rails.application
|
|
113
|
+
return nil unless application.respond_to?(:reloader)
|
|
114
|
+
|
|
115
|
+
reloader = application.reloader
|
|
116
|
+
reloader.respond_to?(:reload!) ? reloader : nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def announce_state
|
|
120
|
+
ENV[RESOLUTION_KEY] = resolution
|
|
121
|
+
ENV[REVISION_KEY] = revision_label
|
|
122
|
+
ENV[BOOT_SECONDS_KEY] = format("%.3f", boot_seconds) if boot_seconds
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def resolution
|
|
126
|
+
message["restarted"] ? "rebuild" : verdict.resolution
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def watch_the_client
|
|
130
|
+
Thread.new do
|
|
131
|
+
loop do
|
|
132
|
+
message = Protocol.read_message(connection)
|
|
133
|
+
break if message.nil?
|
|
134
|
+
|
|
135
|
+
relay(message["name"]) if message.type == Protocol::SIGNAL
|
|
136
|
+
end
|
|
137
|
+
rescue StandardError
|
|
138
|
+
nil
|
|
139
|
+
ensure
|
|
140
|
+
terminate_process_group
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def relay(name)
|
|
145
|
+
return if name.nil?
|
|
146
|
+
|
|
147
|
+
Process.kill(name, 0)
|
|
148
|
+
rescue ArgumentError, SystemCallError
|
|
149
|
+
nil
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def terminate_process_group
|
|
153
|
+
Process.kill("TERM", 0)
|
|
154
|
+
rescue StandardError
|
|
155
|
+
nil
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def die_of(error)
|
|
159
|
+
number = error.signo
|
|
160
|
+
exit!(1) if number.nil?
|
|
161
|
+
|
|
162
|
+
Signal.trap(number, "SYSTEM_DEFAULT")
|
|
163
|
+
Process.kill(number, Process.pid)
|
|
164
|
+
sleep 0.05
|
|
165
|
+
exit! 128 + number
|
|
166
|
+
rescue ArgumentError, SystemCallError, NoMethodError
|
|
167
|
+
exit! 1
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def execute
|
|
171
|
+
argv = Array(message["argv"])
|
|
172
|
+
executable = argv.first
|
|
173
|
+
args = argv[1..] || []
|
|
174
|
+
|
|
175
|
+
$PROGRAM_NAME = executable
|
|
176
|
+
|
|
177
|
+
case executable
|
|
178
|
+
when "rails" then run_rails(args)
|
|
179
|
+
when "rake" then run_rake(args)
|
|
180
|
+
when "rspec" then run_rspec(args)
|
|
181
|
+
when "test" then run_rails(["test", *args])
|
|
182
|
+
else
|
|
183
|
+
raise Error, "no in-process runner for #{executable.inspect}"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
exit 0
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def run_rails(args)
|
|
190
|
+
require "rails/command"
|
|
191
|
+
|
|
192
|
+
aliases = {
|
|
193
|
+
"g" => "generate",
|
|
194
|
+
"d" => "destroy",
|
|
195
|
+
"c" => "console",
|
|
196
|
+
"s" => "server",
|
|
197
|
+
"db" => "dbconsole",
|
|
198
|
+
"r" => "runner",
|
|
199
|
+
"t" => "test"
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
ARGV.replace(args)
|
|
203
|
+
|
|
204
|
+
command = args.shift
|
|
205
|
+
command = aliases[command] || command
|
|
206
|
+
define_app_path if command == "server"
|
|
207
|
+
|
|
208
|
+
::Rails::Command.invoke(command, args)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def define_app_path
|
|
212
|
+
return if defined?(::APP_PATH)
|
|
213
|
+
|
|
214
|
+
Object.const_set(:APP_PATH, File.join(File.realpath(project_key.app_root), "config", "application"))
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def run_rake(args)
|
|
218
|
+
require "rake"
|
|
219
|
+
ARGV.replace(args)
|
|
220
|
+
app = ::Rake.application
|
|
221
|
+
app.standard_exception_handling do
|
|
222
|
+
app.init("rake", args)
|
|
223
|
+
suppress_worker_frames(app)
|
|
224
|
+
app.load_rakefile
|
|
225
|
+
app.top_level
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def suppress_worker_frames(app)
|
|
230
|
+
existing = app.options.suppress_backtrace_pattern || ::Rake::Backtrace::SUPPRESS_PATTERN
|
|
231
|
+
app.options.suppress_backtrace_pattern = Regexp.union(existing, /\A#{Regexp.escape(__dir__)}/)
|
|
232
|
+
rescue StandardError
|
|
233
|
+
nil
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def run_rspec(args)
|
|
237
|
+
require "rspec/core"
|
|
238
|
+
::RSpec::Core::Runner.disable_autorun! if ::RSpec::Core::Runner.respond_to?(:disable_autorun!)
|
|
239
|
+
suppress_worker_frames_in_rspec
|
|
240
|
+
exit ::RSpec::Core::Runner.run(args, $stderr, $stdout)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def suppress_worker_frames_in_rspec
|
|
244
|
+
::RSpec.configure { |config| config.backtrace_exclusion_patterns << HARNESS_FRAME_PATTERN }
|
|
245
|
+
rescue StandardError
|
|
246
|
+
nil
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def report_uncaught(error)
|
|
250
|
+
error.set_backtrace(application_frames(error.backtrace)) if error.backtrace
|
|
251
|
+
$stderr.write(error.full_message)
|
|
252
|
+
rescue StandardError
|
|
253
|
+
nil
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def application_frames(backtrace)
|
|
257
|
+
frames = backtrace.dup
|
|
258
|
+
frames.pop while frames.last && harness_frame?(frames.last)
|
|
259
|
+
frames
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def harness_frame?(frame)
|
|
263
|
+
HARNESS_FRAME_PATTERN.match?(frame)
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
data/lib/siding.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "siding/error"
|
|
4
|
+
require_relative "siding/version"
|
|
5
|
+
require_relative "siding/platform"
|
|
6
|
+
require_relative "siding/project_key"
|
|
7
|
+
require_relative "siding/runtime"
|
|
8
|
+
require_relative "siding/logger"
|
|
9
|
+
require_relative "siding/protocol"
|
|
10
|
+
require_relative "siding/life_cycle"
|
|
11
|
+
require_relative "siding/invocation"
|
|
12
|
+
require_relative "siding/boot_component"
|
|
13
|
+
|
|
14
|
+
module Siding
|
|
15
|
+
class << self
|
|
16
|
+
def accelerated? = Invocation.accelerated?
|
|
17
|
+
def resolution = Invocation.resolution
|
|
18
|
+
def revision = Invocation.revision
|
|
19
|
+
def boot_seconds = Invocation.boot_seconds
|
|
20
|
+
def invocation = Invocation.to_h
|
|
21
|
+
|
|
22
|
+
def before_fork(name = nil, &block) = LifeCycle.before_fork(name, &block)
|
|
23
|
+
def after_fork(name = nil, &block) = LifeCycle.after_fork(name, &block)
|
|
24
|
+
|
|
25
|
+
def boot_component(*paths) = BootComponent.declare(*paths)
|
|
26
|
+
end
|
|
27
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: siding
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Yuji Yaginuma
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '8.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '8.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: watchcat
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.6'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.6'
|
|
40
|
+
email:
|
|
41
|
+
- yuuji.yaginuma@gmail.com
|
|
42
|
+
executables:
|
|
43
|
+
- siding
|
|
44
|
+
extensions: []
|
|
45
|
+
extra_rdoc_files: []
|
|
46
|
+
files:
|
|
47
|
+
- CLAUDE.md
|
|
48
|
+
- CODE_OF_CONDUCT.md
|
|
49
|
+
- LICENSE.txt
|
|
50
|
+
- README.md
|
|
51
|
+
- Rakefile
|
|
52
|
+
- exe/siding
|
|
53
|
+
- lib/siding.rb
|
|
54
|
+
- lib/siding/boot_component.rb
|
|
55
|
+
- lib/siding/cli.rb
|
|
56
|
+
- lib/siding/client.rb
|
|
57
|
+
- lib/siding/error.rb
|
|
58
|
+
- lib/siding/invocation.rb
|
|
59
|
+
- lib/siding/life_cycle.rb
|
|
60
|
+
- lib/siding/load_manifest.rb
|
|
61
|
+
- lib/siding/logger.rb
|
|
62
|
+
- lib/siding/platform.rb
|
|
63
|
+
- lib/siding/project_key.rb
|
|
64
|
+
- lib/siding/protocol.rb
|
|
65
|
+
- lib/siding/restarter.rb
|
|
66
|
+
- lib/siding/runtime.rb
|
|
67
|
+
- lib/siding/server.rb
|
|
68
|
+
- lib/siding/staleness.rb
|
|
69
|
+
- lib/siding/version.rb
|
|
70
|
+
- lib/siding/watch.rb
|
|
71
|
+
- lib/siding/worker.rb
|
|
72
|
+
homepage: https://github.com/y-yagi/siding
|
|
73
|
+
licenses:
|
|
74
|
+
- MIT
|
|
75
|
+
metadata:
|
|
76
|
+
allowed_push_host: https://rubygems.org
|
|
77
|
+
homepage_uri: https://github.com/y-yagi/siding
|
|
78
|
+
source_code_uri: https://github.com/y-yagi/siding
|
|
79
|
+
rubygems_mfa_required: 'true'
|
|
80
|
+
rdoc_options: []
|
|
81
|
+
require_paths:
|
|
82
|
+
- lib
|
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: 3.2.0
|
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
|
+
requirements:
|
|
90
|
+
- - ">="
|
|
91
|
+
- !ruby/object:Gem::Version
|
|
92
|
+
version: '0'
|
|
93
|
+
requirements: []
|
|
94
|
+
rubygems_version: 4.0.17
|
|
95
|
+
specification_version: 4
|
|
96
|
+
summary: A Rails application preloader that never serves stale code.
|
|
97
|
+
test_files: []
|