einhorn 0.4.9 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|