einhorn 0.7.4 → 1.0.0
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/Changes.md +10 -0
- data/README.md +36 -30
- data/bin/einhorn +17 -2
- data/einhorn.gemspec +23 -21
- data/example/pool_worker.rb +1 -1
- data/example/thin_example +8 -8
- data/example/time_server +5 -5
- data/lib/einhorn/client.rb +8 -9
- data/lib/einhorn/command/interface.rb +100 -95
- data/lib/einhorn/command.rb +167 -88
- data/lib/einhorn/compat.rb +7 -7
- data/lib/einhorn/event/abstract_text_descriptor.rb +31 -35
- data/lib/einhorn/event/ack_timer.rb +2 -2
- data/lib/einhorn/event/command_server.rb +7 -9
- data/lib/einhorn/event/connection.rb +1 -3
- data/lib/einhorn/event/loop_breaker.rb +2 -1
- data/lib/einhorn/event/persistent.rb +2 -2
- data/lib/einhorn/event/timer.rb +4 -4
- data/lib/einhorn/event.rb +29 -20
- data/lib/einhorn/prctl.rb +26 -0
- data/lib/einhorn/prctl_linux.rb +48 -0
- data/lib/einhorn/safe_yaml.rb +17 -0
- data/lib/einhorn/version.rb +1 -1
- data/lib/einhorn/worker.rb +67 -49
- data/lib/einhorn/worker_pool.rb +9 -9
- data/lib/einhorn.rb +155 -126
- metadata +42 -137
- data/.gitignore +0 -17
- data/.travis.yml +0 -10
- data/CONTRIBUTORS +0 -6
- data/Gemfile +0 -11
- data/History.txt +0 -4
- data/README.md.in +0 -76
- data/Rakefile +0 -27
- data/test/_lib.rb +0 -12
- data/test/integration/_lib/fixtures/env_printer/env_printer.rb +0 -26
- data/test/integration/_lib/fixtures/exit_during_upgrade/exiting_server.rb +0 -22
- data/test/integration/_lib/fixtures/exit_during_upgrade/upgrade_reexec.rb +0 -6
- data/test/integration/_lib/fixtures/upgrade_project/upgrading_server.rb +0 -22
- data/test/integration/_lib/helpers/einhorn_helpers.rb +0 -143
- data/test/integration/_lib/helpers.rb +0 -4
- data/test/integration/_lib.rb +0 -6
- data/test/integration/startup.rb +0 -31
- data/test/integration/upgrading.rb +0 -157
- data/test/unit/einhorn/client.rb +0 -88
- data/test/unit/einhorn/command/interface.rb +0 -49
- data/test/unit/einhorn/command.rb +0 -21
- data/test/unit/einhorn/event.rb +0 -89
- data/test/unit/einhorn/worker_pool.rb +0 -39
- data/test/unit/einhorn.rb +0 -58
- /data/{LICENSE → LICENSE.txt} +0 -0
data/lib/einhorn.rb
CHANGED
@@ -1,18 +1,36 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
1
|
+
require "fcntl"
|
2
|
+
require "optparse"
|
3
|
+
require "pp"
|
4
|
+
require "set"
|
5
|
+
require "socket"
|
6
|
+
require "tmpdir"
|
7
|
+
require "yaml"
|
8
|
+
require "shellwords"
|
9
|
+
require "einhorn/safe_yaml"
|
9
10
|
|
10
11
|
module Einhorn
|
11
12
|
module AbstractState
|
12
|
-
def default_state
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
def default_state
|
14
|
+
raise NotImplementedError.new("Override in extended modules")
|
15
|
+
end
|
16
|
+
|
17
|
+
def state
|
18
|
+
@state ||= default_state
|
19
|
+
end
|
20
|
+
|
21
|
+
def state=(v)
|
22
|
+
@state = v
|
23
|
+
end
|
24
|
+
|
25
|
+
def dumpable_state
|
26
|
+
state
|
27
|
+
end
|
28
|
+
|
29
|
+
def respond_to_missing?(name)
|
30
|
+
((name.to_s =~ /(.*)=$/) && state.has_key?($1.to_sym)) ||
|
31
|
+
state.has_key?(name) ||
|
32
|
+
default_state.has_key?(name)
|
33
|
+
end
|
16
34
|
|
17
35
|
def method_missing(name, *args)
|
18
36
|
if (name.to_s =~ /(.*)=$/) && state.has_key?($1.to_sym)
|
@@ -37,37 +55,39 @@ module Einhorn
|
|
37
55
|
# about backwards/forwards compatibility for upgrades/downgrades
|
38
56
|
def self.default_state
|
39
57
|
{
|
40
|
-
:
|
41
|
-
:
|
42
|
-
:
|
43
|
-
:
|
44
|
-
:
|
45
|
-
:
|
46
|
-
:
|
47
|
-
:
|
48
|
-
:
|
49
|
-
:
|
50
|
-
:
|
51
|
-
:
|
52
|
-
:
|
53
|
-
:
|
54
|
-
:
|
55
|
-
:
|
56
|
-
:
|
57
|
-
:
|
58
|
-
:
|
59
|
-
:
|
60
|
-
:
|
61
|
-
:
|
62
|
-
:
|
63
|
-
:
|
64
|
-
:
|
65
|
-
:
|
66
|
-
:
|
67
|
-
:
|
68
|
-
:
|
69
|
-
:
|
70
|
-
:
|
58
|
+
children: {},
|
59
|
+
config: {number: 1, backlog: 100, seconds: 1},
|
60
|
+
versions: {},
|
61
|
+
version: 0,
|
62
|
+
sockets: {},
|
63
|
+
orig_cmd: nil,
|
64
|
+
bind: [],
|
65
|
+
bind_fds: [],
|
66
|
+
bound_ports: [],
|
67
|
+
cmd: nil,
|
68
|
+
script_name: nil,
|
69
|
+
respawn: true,
|
70
|
+
upgrading: false,
|
71
|
+
smooth_upgrade: false,
|
72
|
+
reloading_for_upgrade: false,
|
73
|
+
path: nil,
|
74
|
+
cmd_name: nil,
|
75
|
+
verbosity: 1,
|
76
|
+
generation: 0,
|
77
|
+
last_spinup: nil,
|
78
|
+
ack_mode: {type: :timer, timeout: 1},
|
79
|
+
kill_children_on_exit: false,
|
80
|
+
command_socket_as_fd: false,
|
81
|
+
socket_path: nil,
|
82
|
+
pidfile: nil,
|
83
|
+
lockfile: nil,
|
84
|
+
consecutive_deaths_before_ack: 0,
|
85
|
+
last_upgraded: nil,
|
86
|
+
nice: {master: nil, worker: nil, renice_cmd: "/usr/bin/renice"},
|
87
|
+
reexec_commandline: nil,
|
88
|
+
drop_environment_variables: [],
|
89
|
+
signal_timeout: nil,
|
90
|
+
preloaded: false
|
71
91
|
}
|
72
92
|
end
|
73
93
|
end
|
@@ -76,21 +96,20 @@ module Einhorn
|
|
76
96
|
extend AbstractState
|
77
97
|
def self.default_state
|
78
98
|
{
|
79
|
-
:
|
80
|
-
:
|
81
|
-
:
|
82
|
-
:
|
83
|
-
:
|
84
|
-
:
|
85
|
-
:stateful => nil,
|
99
|
+
whatami: :master,
|
100
|
+
script_name: nil,
|
101
|
+
argv: [],
|
102
|
+
environ: {},
|
103
|
+
has_outstanding_spinup_timer: false,
|
104
|
+
stateful: nil,
|
86
105
|
# Holds references so that the GC doesn't go and close your sockets.
|
87
|
-
:
|
106
|
+
socket_handles: Set.new
|
88
107
|
}
|
89
108
|
end
|
90
109
|
end
|
91
110
|
|
92
111
|
def self.restore_state(state)
|
93
|
-
parsed =
|
112
|
+
parsed = SafeYAML.load(state)
|
94
113
|
updated_state, message = update_state(Einhorn::State, "einhorn", parsed[:state])
|
95
114
|
Einhorn::State.state = updated_state
|
96
115
|
Einhorn::Event.restore_persistent_descriptors(parsed[:persistent_descriptors])
|
@@ -112,15 +131,13 @@ module Einhorn
|
|
112
131
|
# them all
|
113
132
|
dead = []
|
114
133
|
updated_state[:children].each do |pid, v|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
dead << pid
|
120
|
-
end
|
134
|
+
pid = Process.wait(pid, Process::WNOHANG)
|
135
|
+
dead << pid if pid
|
136
|
+
rescue Errno::ECHILD
|
137
|
+
dead << pid
|
121
138
|
end
|
122
139
|
Einhorn::Event::Timer.open(0) do
|
123
|
-
dead.each {|pid| Einhorn::Command.
|
140
|
+
dead.each { |pid| Einhorn::Command.cleanup(pid) }
|
124
141
|
end
|
125
142
|
end
|
126
143
|
|
@@ -129,12 +146,12 @@ module Einhorn
|
|
129
146
|
deleted_keys = updated_state.keys - default.keys
|
130
147
|
return [updated_state, message.first] if added_keys.length == 0 && deleted_keys.length == 0
|
131
148
|
|
132
|
-
added_keys.each {|key| updated_state[key] = default[key]}
|
133
|
-
deleted_keys.each {|key| updated_state.delete(key)}
|
149
|
+
added_keys.each { |key| updated_state[key] = default[key] }
|
150
|
+
deleted_keys.each { |key| updated_state.delete(key) }
|
134
151
|
|
135
152
|
message << "adding default values for #{added_keys.inspect}"
|
136
153
|
message << "deleting values for #{deleted_keys.inspect}"
|
137
|
-
message = "State format for #{store_name} has changed: #{message.join(
|
154
|
+
message = "State format for #{store_name} has changed: #{message.join(", ")}"
|
138
155
|
|
139
156
|
# Can't print yet, since state hasn't been set, so we pass along the message.
|
140
157
|
[updated_state, message]
|
@@ -149,42 +166,45 @@ module Einhorn
|
|
149
166
|
sd = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
150
167
|
Einhorn::Compat.cloexec!(sd, false)
|
151
168
|
|
152
|
-
if flags.include?(
|
169
|
+
if flags.include?("r") || flags.include?("so_reuseaddr")
|
153
170
|
sd.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
|
154
171
|
end
|
155
172
|
|
156
173
|
sd.bind(Socket.pack_sockaddr_in(port, addr))
|
157
174
|
sd.listen(Einhorn::State.config[:backlog])
|
158
175
|
|
159
|
-
if flags.include?(
|
176
|
+
if flags.include?("n") || flags.include?("o_nonblock")
|
160
177
|
fl = sd.fcntl(Fcntl::F_GETFL)
|
161
178
|
sd.fcntl(Fcntl::F_SETFL, fl | Fcntl::O_NONBLOCK)
|
162
179
|
end
|
163
180
|
|
164
181
|
Einhorn::TransientState.socket_handles << sd
|
165
|
-
sd.fileno
|
182
|
+
[sd.fileno, sd.local_address.ip_port]
|
166
183
|
end
|
167
184
|
|
168
185
|
# Implement these ourselves so it plays nicely with state persistence
|
169
|
-
def self.log_debug(msg, tag=nil)
|
170
|
-
|
171
|
-
|
186
|
+
def self.log_debug(msg, tag = nil)
|
187
|
+
warn("#{log_tag} DEBUG: #{msg}\n") if Einhorn::State.verbosity <= 0
|
188
|
+
$stderr.flush
|
189
|
+
send_tagged_message(tag, msg) if tag
|
172
190
|
end
|
173
|
-
|
174
|
-
|
175
|
-
|
191
|
+
|
192
|
+
def self.log_info(msg, tag = nil)
|
193
|
+
warn("#{log_tag} INFO: #{msg}\n") if Einhorn::State.verbosity <= 1
|
194
|
+
$stderr.flush
|
195
|
+
send_tagged_message(tag, msg) if tag
|
176
196
|
end
|
177
|
-
|
178
|
-
|
179
|
-
|
197
|
+
|
198
|
+
def self.log_error(msg, tag = nil)
|
199
|
+
warn("#{log_tag} ERROR: #{msg}\n") if Einhorn::State.verbosity <= 2
|
200
|
+
$stderr.flush
|
201
|
+
send_tagged_message(tag, "ERROR: #{msg}") if tag
|
180
202
|
end
|
181
203
|
|
182
|
-
def self.send_tagged_message(tag, message, last=false)
|
204
|
+
def self.send_tagged_message(tag, message, last = false)
|
183
205
|
Einhorn::Command::Interface.send_tagged_message(tag, message, last)
|
184
206
|
end
|
185
207
|
|
186
|
-
private
|
187
|
-
|
188
208
|
def self.log_tag
|
189
209
|
case whatami = Einhorn::TransientState.whatami
|
190
210
|
when :master
|
@@ -197,17 +217,16 @@ module Einhorn
|
|
197
217
|
"[UNKNOWN (#{whatami.inspect}) #{$$}]"
|
198
218
|
end
|
199
219
|
end
|
200
|
-
|
201
|
-
public
|
220
|
+
private_class_method :log_tag
|
202
221
|
|
203
222
|
def self.which(cmd)
|
204
|
-
if cmd.include?(
|
205
|
-
return cmd if File.
|
223
|
+
if cmd.include?("/")
|
224
|
+
return cmd if File.exist?(cmd)
|
206
225
|
raise "Could not find #{cmd}"
|
207
226
|
else
|
208
|
-
ENV[
|
227
|
+
ENV["PATH"].split(":").each do |f|
|
209
228
|
abs = File.join(f, cmd)
|
210
|
-
return abs if File.
|
229
|
+
return abs if File.exist?(abs)
|
211
230
|
end
|
212
231
|
raise "Could not find #{cmd} in PATH"
|
213
232
|
end
|
@@ -217,29 +236,33 @@ module Einhorn
|
|
217
236
|
def self.is_script(file)
|
218
237
|
File.open(file) do |f|
|
219
238
|
bytes = f.read(2)
|
220
|
-
bytes ==
|
239
|
+
bytes == "#!"
|
221
240
|
end
|
222
241
|
end
|
223
242
|
|
224
243
|
def self.preload
|
225
|
-
if path = Einhorn::State.path
|
244
|
+
if (path = Einhorn::State.path)
|
226
245
|
set_argv(Einhorn::State.cmd, false)
|
227
246
|
|
228
247
|
begin
|
248
|
+
# Reset preloaded state to false - this allows us to monitor for failed preloads during reloads.
|
249
|
+
Einhorn::State.preloaded = false
|
229
250
|
# If it's not going to be requireable, then load it.
|
230
|
-
if !path.end_with?(
|
251
|
+
if !path.end_with?(".rb") && File.exist?(path)
|
231
252
|
log_info("Loading #{path} (if this hangs, make sure your code can be properly loaded as a library)", :upgrade)
|
232
253
|
load path
|
233
254
|
else
|
234
255
|
log_info("Requiring #{path} (if this hangs, make sure your code can be properly loaded as a library)", :upgrade)
|
235
256
|
require path
|
257
|
+
|
258
|
+
force_move_to_oldgen if Einhorn::State.config[:gc_before_fork]
|
236
259
|
end
|
237
|
-
rescue
|
260
|
+
rescue StandardError, LoadError => e
|
238
261
|
log_info("Proceeding with postload -- could not load #{path}: #{e} (#{e.class})\n #{e.backtrace.join("\n ")}", :upgrade)
|
239
262
|
else
|
240
263
|
if defined?(einhorn_main)
|
241
264
|
log_info("Successfully loaded #{path}", :upgrade)
|
242
|
-
Einhorn::
|
265
|
+
Einhorn::State.preloaded = true
|
243
266
|
else
|
244
267
|
log_info("Proceeding with postload -- loaded #{path}, but no einhorn_main method was defined", :upgrade)
|
245
268
|
end
|
@@ -247,10 +270,26 @@ module Einhorn
|
|
247
270
|
end
|
248
271
|
end
|
249
272
|
|
273
|
+
# Make the GC more copy-on-write friendly by forcibly incrementing the generation
|
274
|
+
# counter on all objects to its maximum value. Learn more at: https://github.com/ko1/nakayoshi_fork
|
275
|
+
def self.force_move_to_oldgen
|
276
|
+
log_info("Starting GC to improve copy-on-write memory sharing", :upgrade)
|
277
|
+
|
278
|
+
GC.start
|
279
|
+
3.times do
|
280
|
+
GC.start(full_mark: false)
|
281
|
+
end
|
282
|
+
|
283
|
+
GC.compact if GC.respond_to?(:compact)
|
284
|
+
|
285
|
+
log_info("Finished GC after preloading", :upgrade)
|
286
|
+
end
|
287
|
+
private_class_method :force_move_to_oldgen
|
288
|
+
|
250
289
|
def self.set_argv(cmd, set_ps_name)
|
251
290
|
# TODO: clean up this hack
|
252
291
|
idx = 0
|
253
|
-
if cmd[0]
|
292
|
+
if /(^|\/)ruby$/.match?(cmd[0])
|
254
293
|
idx = 1
|
255
294
|
elsif !is_script(cmd[0])
|
256
295
|
log_info("WARNING: Going to set $0 to #{cmd[idx]}, but it doesn't look like a script")
|
@@ -267,7 +306,7 @@ module Einhorn
|
|
267
306
|
$0 = worker_ps_name
|
268
307
|
end
|
269
308
|
|
270
|
-
ARGV[0..-1] = cmd[idx+1..-1]
|
309
|
+
ARGV[0..-1] = cmd[idx + 1..-1]
|
271
310
|
log_info("Set#{set_ps_name ? " $0 = #{$0.inspect}, " : nil} ARGV = #{ARGV.inspect}")
|
272
311
|
end
|
273
312
|
|
@@ -280,15 +319,15 @@ module Einhorn
|
|
280
319
|
end
|
281
320
|
|
282
321
|
def self.worker_ps_name
|
283
|
-
Einhorn::State.cmd_name ? "ruby #{Einhorn::State.cmd_name}" : Einhorn::State.orig_cmd.join(
|
322
|
+
Einhorn::State.cmd_name ? "ruby #{Einhorn::State.cmd_name}" : Einhorn::State.orig_cmd.join(" ")
|
284
323
|
end
|
285
324
|
|
286
325
|
def self.renice_self
|
287
326
|
whatami = Einhorn::TransientState.whatami
|
288
|
-
return unless nice = Einhorn::State.nice[whatami]
|
327
|
+
return unless (nice = Einhorn::State.nice[whatami])
|
289
328
|
pid = $$
|
290
329
|
|
291
|
-
unless nice.
|
330
|
+
unless nice.is_a?(Integer)
|
292
331
|
raise "Nice must be a fixnum: #{nice.inspect}"
|
293
332
|
end
|
294
333
|
|
@@ -304,32 +343,15 @@ module Einhorn
|
|
304
343
|
|
305
344
|
def self.socketify_env!
|
306
345
|
Einhorn::State.bind.each do |host, port, flags|
|
307
|
-
fd = bind(host, port, flags)
|
346
|
+
fd, actual_port = bind(host, port, flags)
|
308
347
|
Einhorn::State.bind_fds << fd
|
309
|
-
|
310
|
-
end
|
311
|
-
|
312
|
-
# This duplicates some code from the environment path, but is
|
313
|
-
# deprecated so that's ok.
|
314
|
-
def self.socketify!(cmd)
|
315
|
-
cmd.map! do |arg|
|
316
|
-
if arg =~ /^(.*=|)srv:([^:]+):(\d+)((?:,\w+)*)$/
|
317
|
-
log_error("Using deprecated command-line configuration for Einhorn; should upgrade to the environment variable interface.")
|
318
|
-
opt = $1
|
319
|
-
host = $2
|
320
|
-
port = $3
|
321
|
-
flags = $4.split(',').select {|flag| flag.length > 0}.map {|flag| flag.downcase}
|
322
|
-
fd = (Einhorn::State.sockets[[host, port]] ||= bind(host, port, flags))
|
323
|
-
"#{opt}#{fd}"
|
324
|
-
else
|
325
|
-
arg
|
326
|
-
end
|
348
|
+
Einhorn::State.bound_ports << actual_port
|
327
349
|
end
|
328
350
|
end
|
329
351
|
|
330
352
|
# Construct and a command and args that can be used to re-exec
|
331
353
|
# Einhorn for upgrades.
|
332
|
-
def self.upgrade_commandline(einhorn_flags=[])
|
354
|
+
def self.upgrade_commandline(einhorn_flags = [])
|
333
355
|
cmdline = []
|
334
356
|
if Einhorn::State.reexec_commandline
|
335
357
|
cmdline += Einhorn::State.reexec_commandline
|
@@ -337,7 +359,7 @@ module Einhorn
|
|
337
359
|
cmdline << Einhorn::TransientState.script_name
|
338
360
|
end
|
339
361
|
cmdline += einhorn_flags
|
340
|
-
cmdline <<
|
362
|
+
cmdline << "--"
|
341
363
|
cmdline += Einhorn::State.cmd
|
342
364
|
[cmdline[0], cmdline[1..-1]]
|
343
365
|
end
|
@@ -349,7 +371,7 @@ module Einhorn
|
|
349
371
|
upgrade_sentinel = fork do
|
350
372
|
Einhorn::TransientState.whatami = :upgrade_sentinel
|
351
373
|
Einhorn.initialize_reload_environment
|
352
|
-
Einhorn::Compat.exec(*Einhorn.upgrade_commandline([
|
374
|
+
Einhorn::Compat.exec(*Einhorn.upgrade_commandline(["--upgrade-check"]))
|
353
375
|
end
|
354
376
|
Process.wait(upgrade_sentinel)
|
355
377
|
$?.exitstatus.zero?
|
@@ -374,10 +396,10 @@ module Einhorn
|
|
374
396
|
# startup. Currently, this means the bundler and rbenv versions.
|
375
397
|
def self.dump_environment_info
|
376
398
|
log_info("Running under Ruby #{RUBY_VERSION}", :environment)
|
377
|
-
log_info("Rbenv ruby version: #{ENV[
|
399
|
+
log_info("Rbenv ruby version: #{ENV["RBENV_VERSION"]}", :environment) if ENV["RBENV_VERSION"]
|
378
400
|
begin
|
379
|
-
bundler_gem = Gem::Specification.find_by_name(
|
380
|
-
log_info("Using Bundler #{bundler_gem.version
|
401
|
+
bundler_gem = Gem::Specification.find_by_name("bundler")
|
402
|
+
log_info("Using Bundler #{bundler_gem.version}", :environment)
|
381
403
|
rescue Gem::LoadError
|
382
404
|
end
|
383
405
|
end
|
@@ -398,7 +420,6 @@ module Einhorn
|
|
398
420
|
# TODO: don't actually alter ARGV[0]?
|
399
421
|
Einhorn::State.cmd[0] = which(Einhorn::State.cmd[0])
|
400
422
|
socketify_env!
|
401
|
-
socketify!(Einhorn::State.cmd)
|
402
423
|
end
|
403
424
|
|
404
425
|
set_master_ps_name
|
@@ -411,6 +432,14 @@ module Einhorn
|
|
411
432
|
Einhorn::State.reloading_for_upgrade = false
|
412
433
|
end
|
413
434
|
|
435
|
+
# If setting a signal-timeout, timeout the event loop
|
436
|
+
# in the same timeframe, ensuring processes are culled
|
437
|
+
# on a regular basis.
|
438
|
+
if Einhorn::State.signal_timeout
|
439
|
+
Einhorn::Event.default_timeout = Einhorn::Event.default_timeout.nil? ?
|
440
|
+
Einhorn::State.signal_timeout : [Einhorn::State.signal_timeout, Einhorn::Event.default_timeout].min
|
441
|
+
end
|
442
|
+
|
414
443
|
while Einhorn::State.respawn || Einhorn::State.children.size > 0
|
415
444
|
log_debug("Entering event loop")
|
416
445
|
|
@@ -425,10 +454,10 @@ module Einhorn
|
|
425
454
|
end
|
426
455
|
end
|
427
456
|
|
428
|
-
require
|
429
|
-
require
|
430
|
-
require
|
431
|
-
require
|
432
|
-
require
|
433
|
-
require
|
434
|
-
require
|
457
|
+
require "einhorn/command"
|
458
|
+
require "einhorn/compat"
|
459
|
+
require "einhorn/client"
|
460
|
+
require "einhorn/event"
|
461
|
+
require "einhorn/worker"
|
462
|
+
require "einhorn/worker_pool"
|
463
|
+
require "einhorn/version"
|