einhorn 0.4.9 → 0.5.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.
- data/History.txt +4 -0
- data/bin/einhornsh +31 -17
- data/lib/einhorn/client.rb +6 -6
- data/lib/einhorn/command/interface.rb +40 -25
- data/lib/einhorn/command.rb +4 -3
- data/lib/einhorn/event/connection.rb +26 -0
- data/lib/einhorn/event/persistent.rb +1 -1
- data/lib/einhorn/event.rb +13 -0
- data/lib/einhorn/version.rb +1 -1
- data/lib/einhorn/worker.rb +1 -1
- data/lib/einhorn.rb +13 -7
- data/test/unit/einhorn/client.rb +1 -4
- data/test/unit/einhorn/event.rb +6 -6
- metadata +9 -3
data/History.txt
ADDED
data/bin/einhornsh
CHANGED
@@ -10,11 +10,33 @@ require 'einhorn'
|
|
10
10
|
|
11
11
|
module Einhorn
|
12
12
|
class EinhornSH
|
13
|
+
|
13
14
|
def initialize(path_to_socket)
|
14
15
|
@path_to_socket = path_to_socket
|
16
|
+
@request_id = 0
|
15
17
|
reconnect
|
16
18
|
end
|
17
19
|
|
20
|
+
def send_command(hash)
|
21
|
+
begin
|
22
|
+
@client.send_command(hash)
|
23
|
+
while response = @client.receive_message
|
24
|
+
if response.kind_of?(Hash)
|
25
|
+
yield response['message']
|
26
|
+
return unless response['wait']
|
27
|
+
else
|
28
|
+
puts "Invalid response type #{response.class}: #{response.inspect}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
rescue Errno::EPIPE => e
|
32
|
+
emit("einhornsh: Error communicating with Einhorn: #{e} (#{e.class})")
|
33
|
+
emit("einhornsh: Attempting to reconnect...")
|
34
|
+
reconnect
|
35
|
+
|
36
|
+
retry
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
18
40
|
def run
|
19
41
|
emit("Enter 'help' if you're not sure what to do.")
|
20
42
|
emit
|
@@ -26,25 +48,16 @@ module Einhorn
|
|
26
48
|
emit("Goodbye!")
|
27
49
|
return
|
28
50
|
end
|
29
|
-
|
30
|
-
|
31
|
-
response = @client.command({'command' => command, 'args' => args})
|
32
|
-
rescue Errno::EPIPE => e
|
33
|
-
emit("einhornsh: Error communicating with Einhorn: #{e} (#{e.class})")
|
34
|
-
emit("einhornsh: Attempting to reconnect...")
|
35
|
-
reconnect
|
36
|
-
|
37
|
-
retry
|
38
|
-
end
|
39
|
-
|
40
|
-
if response.kind_of?(Hash)
|
41
|
-
puts response['message']
|
42
|
-
else
|
43
|
-
puts "Invalid response type #{response.class}: #{response.inspect}"
|
51
|
+
send_command({'id' => request_id, 'command' => command, 'args' => args}) do |message|
|
52
|
+
puts message
|
44
53
|
end
|
45
54
|
end
|
46
55
|
end
|
47
56
|
|
57
|
+
def request_id
|
58
|
+
@request_id += 1
|
59
|
+
end
|
60
|
+
|
48
61
|
def parse_command(line)
|
49
62
|
command, *args = Shellwords.shellsplit(line)
|
50
63
|
[command, args]
|
@@ -69,8 +82,9 @@ EOF
|
|
69
82
|
end
|
70
83
|
|
71
84
|
def ehlo
|
72
|
-
|
73
|
-
|
85
|
+
send_command({'command' => "ehlo", 'user' => ENV['USER']}) do |message|
|
86
|
+
emit(message)
|
87
|
+
end
|
74
88
|
end
|
75
89
|
|
76
90
|
def self.emit(message=nil, force=false)
|
data/lib/einhorn/client.rb
CHANGED
@@ -7,6 +7,9 @@ module Einhorn
|
|
7
7
|
# Keep this in this file so client can be loaded entirely
|
8
8
|
# standalone by user code.
|
9
9
|
module Transport
|
10
|
+
|
11
|
+
ParseError = defined?(Psych::SyntaxError) ? Psych::SyntaxError : ArgumentError
|
12
|
+
|
10
13
|
def self.send_message(socket, message)
|
11
14
|
line = serialize_message(message)
|
12
15
|
socket.write(line)
|
@@ -29,8 +32,6 @@ module Einhorn
|
|
29
32
|
end
|
30
33
|
end
|
31
34
|
|
32
|
-
@@responseless_commands = Set.new(['worker:ack'])
|
33
|
-
|
34
35
|
def self.for_path(path_to_socket)
|
35
36
|
socket = UNIXSocket.open(path_to_socket)
|
36
37
|
self.new(socket)
|
@@ -45,13 +46,12 @@ module Einhorn
|
|
45
46
|
@socket = socket
|
46
47
|
end
|
47
48
|
|
48
|
-
def
|
49
|
+
def send_command(command_hash)
|
49
50
|
Transport.send_message(@socket, command_hash)
|
50
|
-
Transport.receive_message(@socket) if expect_response?(command_hash)
|
51
51
|
end
|
52
52
|
|
53
|
-
def
|
54
|
-
|
53
|
+
def receive_message
|
54
|
+
Transport.receive_message(@socket)
|
55
55
|
end
|
56
56
|
|
57
57
|
def close
|
@@ -202,38 +202,50 @@ module Einhorn::Command
|
|
202
202
|
end
|
203
203
|
|
204
204
|
def self.process_command(conn, command)
|
205
|
-
|
206
|
-
|
207
|
-
|
205
|
+
begin
|
206
|
+
request = Einhorn::Client::Transport.deserialize_message(command)
|
207
|
+
rescue Einhorn::Client::Transport::ParseError
|
208
|
+
end
|
209
|
+
unless request.kind_of?(Hash)
|
210
|
+
send_message(conn, "Could not parse command")
|
211
|
+
return
|
212
|
+
end
|
213
|
+
|
214
|
+
message = generate_message(conn, request)
|
215
|
+
if !message.nil?
|
216
|
+
send_message(conn, message, request['id'], true)
|
208
217
|
else
|
209
218
|
conn.log_debug("Got back nil response, so not responding to command.")
|
210
219
|
end
|
211
220
|
end
|
212
221
|
|
213
|
-
def self.
|
214
|
-
|
215
|
-
|
222
|
+
def self.send_tagged_message(tag, message, last=false)
|
223
|
+
Einhorn::Event.connections.each do |conn|
|
224
|
+
if id = conn.subscription(tag)
|
225
|
+
self.send_message(conn, message, id, last)
|
226
|
+
conn.unsubscribe(tag) if last
|
227
|
+
end
|
216
228
|
end
|
217
|
-
Einhorn::Client::Transport.send_message(conn, response)
|
218
229
|
end
|
219
230
|
|
220
|
-
def self.
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
}
|
231
|
+
def self.send_message(conn, message, request_id=nil, last=false)
|
232
|
+
if request_id
|
233
|
+
response = {'message' => message, 'request_id' => request_id }
|
234
|
+
response['wait'] = true unless last
|
235
|
+
else
|
236
|
+
# support old-style protocol
|
237
|
+
response = {'message' => message}
|
227
238
|
end
|
239
|
+
Einhorn::Client::Transport.send_message(conn, response)
|
240
|
+
end
|
228
241
|
|
242
|
+
def self.generate_message(conn, request)
|
229
243
|
unless command_name = request['command']
|
230
|
-
return
|
231
|
-
'message' => 'No "command" parameter provided; not sure what you want me to do.'
|
232
|
-
}
|
244
|
+
return 'No "command" parameter provided; not sure what you want me to do.'
|
233
245
|
end
|
234
246
|
|
235
247
|
if command_spec = @@commands[command_name]
|
236
|
-
conn.log_debug("Received command: #{
|
248
|
+
conn.log_debug("Received command: #{request.inspect}")
|
237
249
|
begin
|
238
250
|
return command_spec[:code].call(conn, request)
|
239
251
|
rescue StandardError => e
|
@@ -242,7 +254,7 @@ module Einhorn::Command
|
|
242
254
|
return msg
|
243
255
|
end
|
244
256
|
else
|
245
|
-
conn.log_debug("Received unrecognized command: #{
|
257
|
+
conn.log_debug("Received unrecognized command: #{request.inspect}")
|
246
258
|
return unrecognized_command(conn, request)
|
247
259
|
end
|
248
260
|
end
|
@@ -295,7 +307,7 @@ EOF
|
|
295
307
|
YAML.dump(Einhorn::Command.dumpable_state)
|
296
308
|
end
|
297
309
|
|
298
|
-
command 'reload', 'Reload Einhorn' do |conn,
|
310
|
+
command 'reload', 'Reload Einhorn' do |conn, request|
|
299
311
|
# TODO: make reload actually work (command socket reopening is
|
300
312
|
# an issue). Would also be nice if user got a confirmation that
|
301
313
|
# the reload completed, though that's not strictly necessary.
|
@@ -303,7 +315,7 @@ EOF
|
|
303
315
|
# In the normal case, this will do a write
|
304
316
|
# synchronously. Otherwise, the bytes will be stuck into the
|
305
317
|
# buffer and lost upon reload.
|
306
|
-
send_message(conn, 'Reloading, as commanded')
|
318
|
+
send_message(conn, 'Reloading, as commanded', request['id'], true)
|
307
319
|
Einhorn::Command.reload
|
308
320
|
end
|
309
321
|
|
@@ -323,10 +335,13 @@ EOF
|
|
323
335
|
Einhorn::Command.louder
|
324
336
|
end
|
325
337
|
|
326
|
-
command 'upgrade', 'Upgrade all Einhorn workers. This may result in Einhorn reloading its own code as well.' do |conn,
|
327
|
-
#
|
328
|
-
|
329
|
-
#
|
338
|
+
command 'upgrade', 'Upgrade all Einhorn workers. This may result in Einhorn reloading its own code as well.' do |conn, request|
|
339
|
+
# send first message directly for old clients that don't support request
|
340
|
+
# ids or subscriptions. Everything else is sent tagged with request id
|
341
|
+
# for new clients.
|
342
|
+
send_message(conn, 'Upgrading, as commanded', request['id'])
|
343
|
+
conn.subscribe(:upgrade, request['id'])
|
344
|
+
# If the app is preloaded this doesn't return.
|
330
345
|
Einhorn::Command.full_upgrade
|
331
346
|
nil
|
332
347
|
end
|
data/lib/einhorn/command.rb
CHANGED
@@ -301,10 +301,10 @@ module Einhorn
|
|
301
301
|
|
302
302
|
def self.upgrade_workers
|
303
303
|
if Einhorn::State.upgrading
|
304
|
-
Einhorn.log_info("Currently upgrading (#{Einhorn::WorkerPool.ack_count} / #{Einhorn::WorkerPool.ack_target} ACKs; bumping version and starting over)...")
|
304
|
+
Einhorn.log_info("Currently upgrading (#{Einhorn::WorkerPool.ack_count} / #{Einhorn::WorkerPool.ack_target} ACKs; bumping version and starting over)...", :upgrade)
|
305
305
|
else
|
306
306
|
Einhorn::State.upgrading = true
|
307
|
-
Einhorn.log_info("Starting upgrade
|
307
|
+
Einhorn.log_info("Starting upgrade from version #{Einhorn::State.version}...", :upgrade)
|
308
308
|
end
|
309
309
|
|
310
310
|
# Reset this, since we've just upgraded to a new universe (I'm
|
@@ -323,7 +323,8 @@ module Einhorn
|
|
323
323
|
|
324
324
|
if Einhorn::State.upgrading && acked >= target
|
325
325
|
Einhorn::State.upgrading = false
|
326
|
-
Einhorn.log_info("
|
326
|
+
Einhorn.log_info("Upgraded successfully to version #{Einhorn::State.version} (Einhorn #{Einhorn::VERSION}).", :upgrade)
|
327
|
+
Einhorn.send_tagged_message(:upgrade, "Upgrade done", true)
|
327
328
|
end
|
328
329
|
|
329
330
|
old_workers = Einhorn::WorkerPool.old_workers
|
@@ -2,6 +2,11 @@ module Einhorn::Event
|
|
2
2
|
class Connection < AbstractTextDescriptor
|
3
3
|
include Persistent
|
4
4
|
|
5
|
+
def initialize(*args)
|
6
|
+
@subscriptions = {}
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
5
10
|
def parse_record
|
6
11
|
split = @read_buffer.split("\n", 2)
|
7
12
|
if split.length > 1
|
@@ -20,6 +25,7 @@ module Einhorn::Event
|
|
20
25
|
# Don't include by default because it's not that pretty
|
21
26
|
state[:read_buffer] = @read_buffer if @read_buffer.length > 0
|
22
27
|
state[:write_buffer] = @write_buffer if @write_buffer.length > 0
|
28
|
+
state[:subscriptions] = @subscriptions
|
23
29
|
state
|
24
30
|
end
|
25
31
|
|
@@ -29,16 +35,36 @@ module Einhorn::Event
|
|
29
35
|
conn = self.open(socket)
|
30
36
|
conn.read_buffer = state[:read_buffer] if state[:read_buffer]
|
31
37
|
conn.write_buffer = state[:write_buffer] if state[:write_buffer]
|
38
|
+
# subscriptions could be empty if upgrading from an older version of einhorn
|
39
|
+
state.fetch(:subscriptions, {}).each do |tag, id|
|
40
|
+
conn.subscribe(tag, id)
|
41
|
+
end
|
32
42
|
conn
|
33
43
|
end
|
34
44
|
|
45
|
+
def subscribe(tag, request_id)
|
46
|
+
if request_id
|
47
|
+
@subscriptions[tag] = request_id
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def subscription(tag)
|
52
|
+
@subscriptions[tag]
|
53
|
+
end
|
54
|
+
|
55
|
+
def unsubscribe(tag)
|
56
|
+
@subscriptions.delete(tag)
|
57
|
+
end
|
58
|
+
|
35
59
|
def register!
|
36
60
|
log_info("client connected")
|
61
|
+
Einhorn::Event.register_connection(self, @socket.fileno)
|
37
62
|
super
|
38
63
|
end
|
39
64
|
|
40
65
|
def deregister!
|
41
66
|
log_info("client disconnected") if Einhorn::TransientState.whatami == :master
|
67
|
+
Einhorn::Event.deregister_connection(@socket.fileno)
|
42
68
|
super
|
43
69
|
end
|
44
70
|
end
|
@@ -11,7 +11,7 @@ module Einhorn::Event
|
|
11
11
|
if klass = @@persistent[klass_name]
|
12
12
|
klass.from_state(state)
|
13
13
|
else
|
14
|
-
Einhorn.log_error("Unrecognized persistent descriptor class #{klass_name.inspect}. Ignoring. This most likely indicates that your Einhorn version has upgraded. Everything should still be working, but it may be worth a restart.")
|
14
|
+
Einhorn.log_error("Unrecognized persistent descriptor class #{klass_name.inspect}. Ignoring. This most likely indicates that your Einhorn version has upgraded. Everything should still be working, but it may be worth a restart.", :upgrade)
|
15
15
|
nil
|
16
16
|
end
|
17
17
|
end
|
data/lib/einhorn/event.rb
CHANGED
@@ -7,6 +7,7 @@ module Einhorn
|
|
7
7
|
@@signal_actions = []
|
8
8
|
@@readable = {}
|
9
9
|
@@writeable = {}
|
10
|
+
@@connections = {}
|
10
11
|
@@timers = {}
|
11
12
|
|
12
13
|
def self.cloexec!(fd)
|
@@ -88,6 +89,18 @@ module Einhorn
|
|
88
89
|
writers
|
89
90
|
end
|
90
91
|
|
92
|
+
def self.register_connection(connection, fd)
|
93
|
+
@@connections[fd] = connection
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.deregister_connection(fd)
|
97
|
+
@@connections.delete(fd)
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.connections
|
101
|
+
@@connections.values
|
102
|
+
end
|
103
|
+
|
91
104
|
def self.register_timer(timer)
|
92
105
|
@@timers[timer.expires_at] ||= Set.new
|
93
106
|
@@timers[timer.expires_at] << timer
|
data/lib/einhorn/version.rb
CHANGED
data/lib/einhorn/worker.rb
CHANGED
data/lib/einhorn.rb
CHANGED
@@ -169,11 +169,17 @@ module Einhorn
|
|
169
169
|
def self.log_debug(msg)
|
170
170
|
$stderr.puts("#{log_tag} DEBUG: #{msg}") if Einhorn::State.verbosity <= 0
|
171
171
|
end
|
172
|
-
def self.log_info(msg)
|
172
|
+
def self.log_info(msg, tag=nil)
|
173
173
|
$stderr.puts("#{log_tag} INFO: #{msg}") if Einhorn::State.verbosity <= 1
|
174
|
+
self.send_tagged_message(tag, msg) if tag
|
174
175
|
end
|
175
|
-
def self.log_error(msg)
|
176
|
+
def self.log_error(msg, tag=nil)
|
176
177
|
$stderr.puts("#{log_tag} ERROR: #{msg}") if Einhorn::State.verbosity <= 2
|
178
|
+
self.send_tagged_message(tag, "ERROR: #{msg}") if tag
|
179
|
+
end
|
180
|
+
|
181
|
+
def self.send_tagged_message(tag, message, last=false)
|
182
|
+
Einhorn::Command::Interface.send_tagged_message(tag, message, last)
|
177
183
|
end
|
178
184
|
|
179
185
|
private
|
@@ -221,20 +227,20 @@ module Einhorn
|
|
221
227
|
begin
|
222
228
|
# If it's not going to be requireable, then load it.
|
223
229
|
if !path.end_with?('.rb') && File.exists?(path)
|
224
|
-
log_info("Loading #{path} (if this hangs, make sure your code can be properly loaded as a library)")
|
230
|
+
log_info("Loading #{path} (if this hangs, make sure your code can be properly loaded as a library)", :upgrade)
|
225
231
|
load path
|
226
232
|
else
|
227
|
-
log_info("Requiring #{path} (if this hangs, make sure your code can be properly loaded as a library)")
|
233
|
+
log_info("Requiring #{path} (if this hangs, make sure your code can be properly loaded as a library)", :upgrade)
|
228
234
|
require path
|
229
235
|
end
|
230
236
|
rescue Exception => e
|
231
|
-
log_info("Proceeding with postload -- could not load #{path}: #{e} (#{e.class})\n #{e.backtrace.join("\n ")}")
|
237
|
+
log_info("Proceeding with postload -- could not load #{path}: #{e} (#{e.class})\n #{e.backtrace.join("\n ")}", :upgrade)
|
232
238
|
else
|
233
239
|
if defined?(einhorn_main)
|
234
|
-
log_info("Successfully loaded #{path}")
|
240
|
+
log_info("Successfully loaded #{path}", :upgrade)
|
235
241
|
Einhorn::TransientState.preloaded = true
|
236
242
|
else
|
237
|
-
log_info("Proceeding with postload -- loaded #{path}, but no einhorn_main method was defined")
|
243
|
+
log_info("Proceeding with postload -- loaded #{path}, but no einhorn_main method was defined", :upgrade)
|
238
244
|
end
|
239
245
|
end
|
240
246
|
end
|
data/test/unit/einhorn/client.rb
CHANGED
@@ -59,12 +59,9 @@ class ClientTest < EinhornTestCase
|
|
59
59
|
|
60
60
|
it "raises an error when deserializing invalid YAML" do
|
61
61
|
invalid_serialized = "-%0A\t-"
|
62
|
-
expected = [ArgumentError]
|
63
|
-
expected << Psych::SyntaxError if defined?(Psych::SyntaxError) # 1.9
|
64
|
-
|
65
62
|
begin
|
66
63
|
Einhorn::Client::Transport.deserialize_message(invalid_serialized)
|
67
|
-
rescue
|
64
|
+
rescue Einhorn::Client::Transport::ParseError
|
68
65
|
end
|
69
66
|
end
|
70
67
|
end
|
data/test/unit/einhorn/event.rb
CHANGED
@@ -24,8 +24,8 @@ class EventTest < EinhornTestCase
|
|
24
24
|
end
|
25
25
|
|
26
26
|
it "selects on readable descriptors" do
|
27
|
-
sock1 =
|
28
|
-
sock2 =
|
27
|
+
sock1 = stub(:fileno => 4)
|
28
|
+
sock2 = stub(:fileno => 5)
|
29
29
|
|
30
30
|
conn1 = Einhorn::Event::Connection.open(sock1)
|
31
31
|
conn2 = Einhorn::Event::Connection.open(sock2)
|
@@ -41,8 +41,8 @@ class EventTest < EinhornTestCase
|
|
41
41
|
end
|
42
42
|
|
43
43
|
it "selects on writeable descriptors" do
|
44
|
-
sock1 =
|
45
|
-
sock2 =
|
44
|
+
sock1 = stub(:fileno => 4)
|
45
|
+
sock2 = stub(:fileno => 5)
|
46
46
|
|
47
47
|
conn1 = Einhorn::Event::Connection.open(sock1)
|
48
48
|
conn2 = Einhorn::Event::Connection.open(sock2)
|
@@ -61,8 +61,8 @@ class EventTest < EinhornTestCase
|
|
61
61
|
end
|
62
62
|
|
63
63
|
it "runs callbacks for ready selectables" do
|
64
|
-
sock1 =
|
65
|
-
sock2 =
|
64
|
+
sock1 = stub(:fileno => 4)
|
65
|
+
sock2 = stub(:fileno => 5)
|
66
66
|
|
67
67
|
conn1 = Einhorn::Event::Connection.open(sock1)
|
68
68
|
conn2 = Einhorn::Event::Connection.open(sock2)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: einhorn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-08-
|
12
|
+
date: 2013-08-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -74,6 +74,7 @@ files:
|
|
74
74
|
- .gitignore
|
75
75
|
- .travis.yml
|
76
76
|
- Gemfile
|
77
|
+
- History.txt
|
77
78
|
- LICENSE
|
78
79
|
- README.md
|
79
80
|
- README.md.in
|
@@ -129,12 +130,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
129
130
|
- - ! '>='
|
130
131
|
- !ruby/object:Gem::Version
|
131
132
|
version: '0'
|
133
|
+
segments:
|
134
|
+
- 0
|
135
|
+
hash: 3689317997291483105
|
132
136
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
137
|
none: false
|
134
138
|
requirements:
|
135
139
|
- - ! '>='
|
136
140
|
- !ruby/object:Gem::Version
|
137
141
|
version: '0'
|
142
|
+
segments:
|
143
|
+
- 0
|
144
|
+
hash: 3689317997291483105
|
138
145
|
requirements: []
|
139
146
|
rubyforge_project:
|
140
147
|
rubygems_version: 1.8.23
|
@@ -149,4 +156,3 @@ test_files:
|
|
149
156
|
- test/unit/einhorn/command/interface.rb
|
150
157
|
- test/unit/einhorn/event.rb
|
151
158
|
- test/unit/einhorn/worker_pool.rb
|
152
|
-
has_rdoc:
|