expedite 0.0.2 → 0.1.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 +4 -4
- data/README.md +37 -30
- data/lib/expedite/{command/basic.rb → action/block.rb} +2 -2
- data/lib/expedite/{command → action}/boot.rb +6 -6
- data/lib/expedite/{commands.rb → actions.rb} +13 -13
- data/lib/expedite/agents.rb +101 -0
- data/lib/expedite/cli/server.rb +2 -2
- data/lib/expedite/cli/status.rb +34 -0
- data/lib/expedite/cli/stop.rb +3 -3
- data/lib/expedite/cli.rb +2 -0
- data/lib/expedite/client/exec.rb +124 -0
- data/lib/expedite/client/invoke.rb +155 -0
- data/lib/expedite/env.rb +2 -1
- data/lib/expedite/errors.rb +3 -0
- data/lib/expedite/protocol.rb +18 -0
- data/lib/expedite/server/agent.rb +322 -0
- data/lib/expedite/{application/boot.rb → server/agent_boot.rb} +3 -3
- data/lib/expedite/server/agent_manager.rb +191 -0
- data/lib/expedite/server/controller.rb +211 -0
- data/lib/expedite/syntax.rb +26 -0
- data/lib/expedite/version.rb +1 -1
- data/lib/expedite.rb +26 -10
- metadata +16 -14
- data/lib/expedite/application.rb +0 -316
- data/lib/expedite/application_manager.rb +0 -187
- data/lib/expedite/client.rb +0 -235
- data/lib/expedite/command/info.rb +0 -49
- data/lib/expedite/send_json.rb +0 -10
- data/lib/expedite/server.rb +0 -188
- data/lib/expedite/variants.rb +0 -99
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7c3af01b23cd76fd62beb91411f18fd9535c49cca41b530496f2031abc294b67
|
|
4
|
+
data.tar.gz: 260e78502a54f80f3c101f0880a79f738d68bf414adc401253d5cfec0a2837b9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0b0fdaa31cf55dd0fde167748208e5f1f1be5b8cb1b2def68cae75516df458614855e4c1c2bb4affec937dc7d7c43c4d8f04bb830998ff5c9ab7d6c6b65bf9e7
|
|
7
|
+
data.tar.gz: d59acca092ec657cadc977c2af264cf811b4cf1b601e5bd8074bd21d91424b3fc07e81930bde3443bdce51e05965541dd132a819a1d5fa4812f6fd32b0809b95
|
data/README.md
CHANGED
|
@@ -8,69 +8,76 @@ derivatives to start faster.
|
|
|
8
8
|
|
|
9
9
|
## Usage
|
|
10
10
|
|
|
11
|
-
To use expedite you need to
|
|
11
|
+
To use expedite you need to define agents and actions in an `expedite_helper.rb`
|
|
12
12
|
that is placed in the root directory of your application. The sample discussed
|
|
13
13
|
in this section is in the [examples/simple](examples/simple) folder.
|
|
14
14
|
|
|
15
|
-
This is the "parent"
|
|
15
|
+
This is the "parent" agent:
|
|
16
16
|
```
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
Expedite.define do
|
|
18
|
+
agent :parent do
|
|
19
|
+
$parent_var = 1
|
|
20
|
+
end
|
|
21
21
|
end
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
You can
|
|
24
|
+
You can define agents that are based on other agents. You can also have wildcard
|
|
25
25
|
matchers.
|
|
26
|
+
|
|
26
27
|
```
|
|
27
|
-
Expedite
|
|
28
|
-
|
|
28
|
+
Expedite.define do
|
|
29
|
+
agent "development/*", parent: :parent do |name|
|
|
30
|
+
$development_var = name
|
|
31
|
+
end
|
|
29
32
|
end
|
|
30
33
|
```
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
this defines a `custom` command.
|
|
35
|
+
The following defines an `info` action.
|
|
34
36
|
|
|
35
37
|
```
|
|
36
|
-
Expedite
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
Expedite.define do
|
|
39
|
+
action :info do
|
|
40
|
+
puts " Process.pid = #{Process.pid}"
|
|
41
|
+
puts " Process.ppid = #{Process.ppid}"
|
|
42
|
+
puts " $parent_var = #{$parent_var}"
|
|
43
|
+
puts "$development_var = #{$development_var}"
|
|
44
|
+
end
|
|
41
45
|
end
|
|
42
46
|
```
|
|
43
47
|
|
|
44
|
-
After
|
|
45
|
-
example, the `main.rb` calls the `
|
|
46
|
-
|
|
48
|
+
After defining your agents and actions, you can then use it. In the simple
|
|
49
|
+
example, the `main.rb` calls the `info` command on the `development/abc`
|
|
50
|
+
agent.
|
|
47
51
|
|
|
52
|
+
The `invoke` method will execute the action, and return the result. There
|
|
53
|
+
is also an `exec` method that will replace the current executable with
|
|
54
|
+
the action; in that case, the return result is the exit code.
|
|
48
55
|
```
|
|
49
56
|
require 'expedite'
|
|
50
57
|
|
|
51
|
-
Expedite.
|
|
58
|
+
Expedite.agent("development/abc").invoke("info")
|
|
52
59
|
```
|
|
53
60
|
|
|
54
61
|
When you run `main.rb`, the following output is produced. Note that `$sleep_parent`
|
|
55
|
-
comes from teh `parent`
|
|
56
|
-
|
|
62
|
+
comes from teh `parent` agent, and `$sleep_child` comes from the `development/abc`
|
|
63
|
+
agent.
|
|
57
64
|
|
|
58
65
|
```
|
|
59
66
|
# bundle exec ./main.rb
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
$
|
|
63
|
-
|
|
67
|
+
Process.pid = 3855
|
|
68
|
+
Process.ppid = 3854
|
|
69
|
+
$parent_var = 1
|
|
70
|
+
$development_var = development/abc
|
|
64
71
|
```
|
|
65
72
|
|
|
66
73
|
Calling `main.rb` automatically started the expedite server in the background.
|
|
67
74
|
In the above example, it does the following:
|
|
68
75
|
|
|
69
|
-
1. Launch the `
|
|
70
|
-
2. Fork from the `
|
|
71
|
-
3. Fork from the `development/abc`
|
|
76
|
+
1. Launch the `parent` agent.
|
|
77
|
+
2. Fork from the `parent` agent to create the `development/abc` agent.
|
|
78
|
+
3. Fork from the `development/abc` agent to run the `info` command, and then quit.
|
|
72
79
|
|
|
73
|
-
To explicitly stop the server and all the
|
|
80
|
+
To explicitly stop the server and all the agents, you use:
|
|
74
81
|
|
|
75
82
|
```
|
|
76
83
|
$ bundle exec expedite stop
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
module Expedite
|
|
2
|
-
module
|
|
2
|
+
module Action
|
|
3
3
|
class Boot
|
|
4
|
-
def call
|
|
5
|
-
|
|
4
|
+
def call(*args)
|
|
5
|
+
agent = args[0]
|
|
6
6
|
|
|
7
|
-
require "expedite/
|
|
7
|
+
require "expedite/server/agent"
|
|
8
8
|
|
|
9
|
-
Expedite::
|
|
10
|
-
|
|
9
|
+
Expedite::Server::Agent.new(
|
|
10
|
+
agent: agent,
|
|
11
11
|
manager: UNIXSocket.for_fd(@child_socket.fileno),
|
|
12
12
|
env: Expedite::Env.new(
|
|
13
13
|
root: ENV['EXPEDITE_ROOT'],
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
require 'expedite/
|
|
2
|
-
require 'expedite/
|
|
1
|
+
require 'expedite/action/block'
|
|
2
|
+
require 'expedite/action/boot'
|
|
3
3
|
|
|
4
4
|
module Expedite
|
|
5
|
-
class
|
|
5
|
+
class Actions
|
|
6
6
|
def self.current
|
|
7
|
-
@current ||=
|
|
7
|
+
@current ||= Actions.new
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def self.lookup(name)
|
|
@@ -12,16 +12,16 @@ module Expedite
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
##
|
|
15
|
-
# Registers
|
|
15
|
+
# Registers an action. If multiple actions are registered with the
|
|
16
16
|
# same name, the last one takes precedence.
|
|
17
17
|
#
|
|
18
|
-
# [name] Name of the
|
|
18
|
+
# [name] Name of the action. Expedite internal actions are prefixed
|
|
19
19
|
# with "expedite/"
|
|
20
|
-
# [klass_or_nil] Class of the
|
|
21
|
-
# Expedite::
|
|
22
|
-
# [named_options]
|
|
20
|
+
# [klass_or_nil] Class of the action. If omitted, will default to
|
|
21
|
+
# Expedite::Action::Block.
|
|
22
|
+
# [named_options] Action options. Passed to the initializer.
|
|
23
23
|
def self.register(name, klass_or_nil = nil, **named_options, &block)
|
|
24
|
-
self.current.register(name, klass_or_nil, **named_options, &block)
|
|
24
|
+
self.current.register(name.to_s, klass_or_nil, **named_options, &block)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
##
|
|
@@ -36,13 +36,13 @@ module Expedite
|
|
|
36
36
|
|
|
37
37
|
def lookup(name)
|
|
38
38
|
ret = @registrations[name]
|
|
39
|
-
raise NotImplementedError, "
|
|
39
|
+
raise NotImplementedError, "Action #{name.inspect} not found" if ret.nil?
|
|
40
40
|
ret
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def register(name, klass_or_nil = nil, **named_options, &block)
|
|
44
44
|
cmd = if klass_or_nil.nil?
|
|
45
|
-
|
|
45
|
+
Action::Block.new(**named_options, &block)
|
|
46
46
|
else
|
|
47
47
|
klass_or_nil.new(**named_options)
|
|
48
48
|
end
|
|
@@ -54,7 +54,7 @@ module Expedite
|
|
|
54
54
|
@registrations = {}
|
|
55
55
|
|
|
56
56
|
# Default registrations
|
|
57
|
-
register("expedite/boot", Expedite::
|
|
57
|
+
register("expedite/boot", Expedite::Action::Boot)
|
|
58
58
|
|
|
59
59
|
nil
|
|
60
60
|
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
|
|
2
|
+
module Expedite
|
|
3
|
+
# Definition of a Agent
|
|
4
|
+
class Agent
|
|
5
|
+
##
|
|
6
|
+
# Name of the parent agent. This allows you to create agents from
|
|
7
|
+
# an existing agent.
|
|
8
|
+
# Defaults to nil.
|
|
9
|
+
attr_accessor :parent
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
# If set to true, agent will be restarted automatically if it is killed.
|
|
13
|
+
# Defaults to false.
|
|
14
|
+
attr_accessor :keep_alive
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
# [parent] Name of parent agent.
|
|
18
|
+
# [keep_alive] Specifies if the agent should be automatically restarted if it is terminated. Defaults to false.
|
|
19
|
+
# [after_fork] Block is executed when agent is first preloaded.
|
|
20
|
+
def initialize(parent: nil, keep_alive: false, &after_fork)
|
|
21
|
+
@parent = parent
|
|
22
|
+
@keep_alive = keep_alive
|
|
23
|
+
@after_fork_proc = after_fork
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# Called when agent if first preloaded. This version calls the after_fork
|
|
28
|
+
# block provided in the initializer.
|
|
29
|
+
def after_fork(agent)
|
|
30
|
+
@after_fork_proc&.call(agent)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class Agents
|
|
35
|
+
Registration = Struct.new(:matcher, :agent) do
|
|
36
|
+
def match?(name)
|
|
37
|
+
File.fnmatch?(matcher, name.to_s)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.current
|
|
42
|
+
@current ||= Agents.new
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# Retrieves the specified agent
|
|
47
|
+
def self.lookup(agent)
|
|
48
|
+
self.current.lookup(agent.to_s)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
##
|
|
52
|
+
# Registers a agent. Agents are matched in the
|
|
53
|
+
# order they are registered.
|
|
54
|
+
#
|
|
55
|
+
# [matcher] Wildcard to match a name against.
|
|
56
|
+
# [named_options] Agent options.
|
|
57
|
+
# [after_fork] Optional block that is called when
|
|
58
|
+
# agent is preloaded.
|
|
59
|
+
#
|
|
60
|
+
# = Example
|
|
61
|
+
# Expedite::Agents.register('base' do |name|
|
|
62
|
+
# puts "Base #{name} started"
|
|
63
|
+
# end
|
|
64
|
+
# Expedite::Agents.register('development/abc', parent: 'base') do |name|
|
|
65
|
+
# puts "Agent #{name} started"
|
|
66
|
+
# end
|
|
67
|
+
def self.register(matcher, **named_options, &after_fork)
|
|
68
|
+
self.current.register(matcher.to_s, **named_options, &after_fork)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
##
|
|
72
|
+
# Resets registrations to default
|
|
73
|
+
def self.reset
|
|
74
|
+
self.current.reset
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def initialize
|
|
78
|
+
@registrations = []
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def lookup(agent)
|
|
82
|
+
ret = @registrations.find do |r|
|
|
83
|
+
r.match?(agent)
|
|
84
|
+
end
|
|
85
|
+
raise NotImplementedError, "Agent #{agent.inspect} not found" if ret.nil?
|
|
86
|
+
ret.agent
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def register(matcher, **named_options, &after_fork)
|
|
90
|
+
@registrations << Registration.new(
|
|
91
|
+
matcher,
|
|
92
|
+
Agent.new(**named_options, &after_fork)
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def reset
|
|
97
|
+
@registrations = []
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
data/lib/expedite/cli/server.rb
CHANGED
|
@@ -2,9 +2,9 @@ module Expedite
|
|
|
2
2
|
module Cli
|
|
3
3
|
class Server
|
|
4
4
|
def run(args)
|
|
5
|
-
require 'expedite/server'
|
|
5
|
+
require 'expedite/server/controller'
|
|
6
6
|
|
|
7
|
-
server = Expedite::Server.new(foreground: true)
|
|
7
|
+
server = Expedite::Server::Controller.new(foreground: true)
|
|
8
8
|
server.boot
|
|
9
9
|
end
|
|
10
10
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Based on https://github.com/rails/spring/blob/master/lib/spring/client/status.rb
|
|
2
|
+
require 'expedite'
|
|
3
|
+
require 'expedite/cli/server'
|
|
4
|
+
require 'expedite/cli/stop'
|
|
5
|
+
|
|
6
|
+
module Expedite
|
|
7
|
+
module Cli
|
|
8
|
+
class Status
|
|
9
|
+
def run(args)
|
|
10
|
+
require 'expedite/server/controller'
|
|
11
|
+
|
|
12
|
+
ctrl = Expedite::Server::Controller.new(foreground: true)
|
|
13
|
+
if ctrl.running?
|
|
14
|
+
puts "Expedite is running (pid=#{ctrl.pid})"
|
|
15
|
+
puts
|
|
16
|
+
print_process ctrl.pid
|
|
17
|
+
Expedite.agent("__server__").invoke("application_pids").each do |pid|
|
|
18
|
+
print_process pid
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
puts "Expedite is not running"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def summary
|
|
26
|
+
'Expedite server status'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def print_process(pid)
|
|
30
|
+
puts `ps -p #{pid} -o pid= -o command=`
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/expedite/cli/stop.rb
CHANGED
data/lib/expedite/cli.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'expedite/cli/server'
|
|
2
|
+
require 'expedite/cli/status'
|
|
2
3
|
require 'expedite/cli/stop'
|
|
3
4
|
|
|
4
5
|
module Expedite
|
|
@@ -26,6 +27,7 @@ module Expedite
|
|
|
26
27
|
COMMANDS = {
|
|
27
28
|
'help' => Cli::Help,
|
|
28
29
|
'server' => Cli::Server,
|
|
30
|
+
'status' => Cli::Status,
|
|
29
31
|
'stop' => Cli::Stop,
|
|
30
32
|
}
|
|
31
33
|
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
|
|
2
|
+
# Based on https://github.com/rails/spring/blob/master/lib/spring/client/run.rb
|
|
3
|
+
|
|
4
|
+
require 'bundler'
|
|
5
|
+
require 'rbconfig'
|
|
6
|
+
require 'socket'
|
|
7
|
+
|
|
8
|
+
require 'expedite/client/invoke'
|
|
9
|
+
require 'expedite/env'
|
|
10
|
+
require 'expedite/errors'
|
|
11
|
+
require 'expedite/protocol'
|
|
12
|
+
|
|
13
|
+
module Expedite
|
|
14
|
+
module Client
|
|
15
|
+
class Exec < Invoke
|
|
16
|
+
FORWARDED_SIGNALS = %w(INT QUIT USR1 USR2 INFO WINCH) & Signal.list.keys
|
|
17
|
+
|
|
18
|
+
def initialize(env: nil, agent: nil)
|
|
19
|
+
super
|
|
20
|
+
|
|
21
|
+
@signal_queue = []
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def call(*args)
|
|
25
|
+
@args = args
|
|
26
|
+
begin
|
|
27
|
+
connect
|
|
28
|
+
rescue Errno::ENOENT, Errno::ECONNRESET, Errno::ECONNREFUSED
|
|
29
|
+
cold_run
|
|
30
|
+
else
|
|
31
|
+
warm_run
|
|
32
|
+
end
|
|
33
|
+
ensure
|
|
34
|
+
server.close if server
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def warm_run
|
|
38
|
+
run
|
|
39
|
+
rescue CommandNotFound
|
|
40
|
+
raise
|
|
41
|
+
require "expedite/command"
|
|
42
|
+
|
|
43
|
+
if Expedite.command(args.first)
|
|
44
|
+
# Command installed since Expedite started
|
|
45
|
+
stop_server
|
|
46
|
+
cold_run
|
|
47
|
+
else
|
|
48
|
+
raise
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def cold_run
|
|
53
|
+
boot_server
|
|
54
|
+
connect
|
|
55
|
+
run
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def run
|
|
59
|
+
status = perform(*@args)
|
|
60
|
+
|
|
61
|
+
exit status.to_i
|
|
62
|
+
rescue Errno::ECONNRESET
|
|
63
|
+
exit 1
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def verify_server_version
|
|
67
|
+
server_version = server.gets.chomp
|
|
68
|
+
if server_version != env.version
|
|
69
|
+
$stderr.puts "There is a version mismatch between the Expedite client " \
|
|
70
|
+
"(#{env.version}) and the server (#{server_version})."
|
|
71
|
+
|
|
72
|
+
if server_booted?
|
|
73
|
+
$stderr.puts "We already tried to reboot the server, but the mismatch is still present."
|
|
74
|
+
exit 1
|
|
75
|
+
else
|
|
76
|
+
$stderr.puts "Restarting to resolve."
|
|
77
|
+
stop_server
|
|
78
|
+
cold_run
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def queue_signals
|
|
84
|
+
FORWARDED_SIGNALS.each do |sig|
|
|
85
|
+
trap(sig) { @signal_queue << sig }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def suspend_resume_on_tstp_cont(pid)
|
|
90
|
+
trap("TSTP") {
|
|
91
|
+
log "suspended"
|
|
92
|
+
Process.kill("STOP", pid.to_i)
|
|
93
|
+
Process.kill("STOP", Process.pid)
|
|
94
|
+
}
|
|
95
|
+
trap("CONT") {
|
|
96
|
+
log "resumed"
|
|
97
|
+
Process.kill("CONT", pid.to_i)
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def forward_signals(application)
|
|
102
|
+
@signal_queue.each { |sig| kill sig, application }
|
|
103
|
+
|
|
104
|
+
FORWARDED_SIGNALS.each do |sig|
|
|
105
|
+
trap(sig) { forward_signal sig, application }
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def forward_signal(sig, application)
|
|
110
|
+
if kill(sig, application) != 0
|
|
111
|
+
# If the application process is gone, then don't block the
|
|
112
|
+
# signal on this process.
|
|
113
|
+
trap(sig, 'DEFAULT')
|
|
114
|
+
Process.kill(sig, Process.pid)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def kill(sig, application)
|
|
119
|
+
application.puts(sig)
|
|
120
|
+
application.gets.to_i
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
|
|
3
|
+
require 'expedite/env'
|
|
4
|
+
require 'expedite/errors'
|
|
5
|
+
require 'expedite/protocol'
|
|
6
|
+
|
|
7
|
+
module Expedite
|
|
8
|
+
module Client
|
|
9
|
+
class Invoke
|
|
10
|
+
CONNECT_TIMEOUT = 1
|
|
11
|
+
BOOT_TIMEOUT = 20
|
|
12
|
+
|
|
13
|
+
attr_reader :args, :env, :agent
|
|
14
|
+
attr_reader :server
|
|
15
|
+
|
|
16
|
+
def initialize(env: nil, agent: nil)
|
|
17
|
+
@env = env || Env.new
|
|
18
|
+
@agent = agent
|
|
19
|
+
|
|
20
|
+
@server_booted = false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def call(*args)
|
|
24
|
+
begin
|
|
25
|
+
connect
|
|
26
|
+
rescue Errno::ENOENT, Errno::ECONNRESET, Errno::ECONNREFUSED
|
|
27
|
+
boot_server
|
|
28
|
+
connect
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
perform(*args)
|
|
32
|
+
ensure
|
|
33
|
+
server.close if server
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def perform(*args)
|
|
37
|
+
verify_server_version
|
|
38
|
+
|
|
39
|
+
agent, client = UNIXSocket.pair
|
|
40
|
+
connect_to_agent(client, args)
|
|
41
|
+
run_command(client, agent, args)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def verify_server_version
|
|
45
|
+
server_version = server.gets.chomp
|
|
46
|
+
raise ArgumentError, "Server mismatch. Expected #{env.version}, got #{server_version}." if server_version != env.version
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def connect_to_agent(client, args)
|
|
50
|
+
server.send_io client
|
|
51
|
+
|
|
52
|
+
server.send_object("args" => args, "agent" => agent)
|
|
53
|
+
|
|
54
|
+
if IO.select([server], [], [], CONNECT_TIMEOUT)
|
|
55
|
+
server.gets or raise CommandNotFound
|
|
56
|
+
else
|
|
57
|
+
raise "Error connecting to Expedite server"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def run_command(client, agent, args)
|
|
62
|
+
log "sending command"
|
|
63
|
+
|
|
64
|
+
agent.send_io STDOUT
|
|
65
|
+
agent.send_io STDERR
|
|
66
|
+
agent.send_io STDIN
|
|
67
|
+
|
|
68
|
+
agent.send_object("args" => args, "env" => ENV.to_hash)
|
|
69
|
+
|
|
70
|
+
pid = server.gets
|
|
71
|
+
pid = pid.chomp if pid
|
|
72
|
+
|
|
73
|
+
# We must not close the client socket until we are sure that the application has
|
|
74
|
+
# received the FD. Otherwise the FD can end up getting closed while it's in the server
|
|
75
|
+
# socket buffer on OS X. This doesn't happen on Linux.
|
|
76
|
+
client.close
|
|
77
|
+
|
|
78
|
+
if pid && !pid.empty?
|
|
79
|
+
log "got pid: #{pid}"
|
|
80
|
+
|
|
81
|
+
## suspend_resume_on_tstp_cont(pid)
|
|
82
|
+
|
|
83
|
+
## forward_signals(application)
|
|
84
|
+
result = agent.recv_object
|
|
85
|
+
if result.key?("exception")
|
|
86
|
+
e = result["exception"]
|
|
87
|
+
log "got exception #{e}"
|
|
88
|
+
raise e
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
ret = result["return"]
|
|
92
|
+
log "got return value #{ret}"
|
|
93
|
+
return ret
|
|
94
|
+
else
|
|
95
|
+
log "got no pid"
|
|
96
|
+
raise UnknownError, "got no pid"
|
|
97
|
+
end
|
|
98
|
+
ensure
|
|
99
|
+
agent.close
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def boot_server
|
|
103
|
+
env.socket_path.unlink if env.socket_path.exist?
|
|
104
|
+
|
|
105
|
+
pid = Process.spawn(gem_env, env.server_command, out: File::NULL)
|
|
106
|
+
timeout = Time.now + BOOT_TIMEOUT
|
|
107
|
+
|
|
108
|
+
@server_booted = true
|
|
109
|
+
|
|
110
|
+
until env.socket_path.exist?
|
|
111
|
+
_, status = Process.waitpid2(pid, Process::WNOHANG)
|
|
112
|
+
|
|
113
|
+
if status
|
|
114
|
+
# Server did not start
|
|
115
|
+
raise ArgumentError, "Server exited: #{status.exitstatus}"
|
|
116
|
+
elsif Time.now > timeout
|
|
117
|
+
$stderr.puts "Starting Expedite server with `#{env.server_command}` " \
|
|
118
|
+
"timed out after #{BOOT_TIMEOUT} seconds"
|
|
119
|
+
exit 1
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
sleep 0.1
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def server_booted?
|
|
127
|
+
@server_booted
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def stop_server
|
|
131
|
+
server.close
|
|
132
|
+
@server = nil
|
|
133
|
+
env.stop
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def log(message)
|
|
137
|
+
env.log "[client] #{message}"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def connect
|
|
141
|
+
@server = UNIXSocket.open(env.socket_path)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def gem_env
|
|
145
|
+
bundle = Bundler.bundle_path.to_s
|
|
146
|
+
paths = Gem.path + ENV["GEM_PATH"].to_s.split(File::PATH_SEPARATOR)
|
|
147
|
+
|
|
148
|
+
{
|
|
149
|
+
"GEM_PATH" => [bundle, *paths].uniq.join(File::PATH_SEPARATOR),
|
|
150
|
+
"GEM_HOME" => bundle
|
|
151
|
+
}
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
data/lib/expedite/env.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require 'digest'
|
|
2
2
|
require 'pathname'
|
|
3
3
|
require 'expedite/version'
|
|
4
|
+
require 'expedite/server/agent_manager'
|
|
4
5
|
|
|
5
6
|
module Expedite
|
|
6
7
|
class Env
|
|
@@ -18,7 +19,7 @@ module Expedite
|
|
|
18
19
|
|
|
19
20
|
env = self
|
|
20
21
|
@applications = Hash.new do |h, k|
|
|
21
|
-
h[k] =
|
|
22
|
+
h[k] = Server::AgentManager.new(k, env)
|
|
22
23
|
end
|
|
23
24
|
end
|
|
24
25
|
|