expedite 0.1.1 → 0.2.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 +4 -4
- data/README.md +7 -0
- data/lib/expedite/actions.rb +4 -4
- data/lib/expedite/cli/rails.rb +13 -0
- data/lib/expedite/cli.rb +15 -8
- data/lib/expedite/client/base.rb +165 -0
- data/lib/expedite/client/exec.rb +17 -11
- data/lib/expedite/client/invoke.rb +5 -146
- data/lib/expedite/env.rb +2 -4
- data/lib/expedite/helper/rails.rb +8 -8
- data/lib/expedite/protocol.rb +2 -1
- data/lib/expedite/server/agent.rb +77 -39
- data/lib/expedite/server/agent_manager.rb +11 -5
- data/lib/expedite/server/agent_pool.rb +39 -0
- data/lib/expedite/server/application_manager.rb +27 -0
- data/lib/expedite/server/controller.rb +17 -11
- data/lib/expedite/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '0218f397835f80f748b0c255e8097aad9b8e8063122d15415a19f88e35546335'
|
|
4
|
+
data.tar.gz: 643e16d53333346a199595b2cb9fcab21116f53c606a505a40675d6ca074baaf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c27ae0ac8775724ae0da4983e2f4ec04754e1cacd29bd632043c36f2c7634dd3914b92fc2e9d15f0b263418fdf0813e4d34aef7d4191fa0b14c368fed4993fb9
|
|
7
|
+
data.tar.gz: '09d8bdf813c976cffe6bfb3c2a6a31f14f1e0e1e6577a06a8e67e2be66271dc09a1180723bda57e1fa91a891f152f0316c5d893f795357d33ba090139782b6aa'
|
data/README.md
CHANGED
|
@@ -89,6 +89,13 @@ You can also start the server in the foreground.
|
|
|
89
89
|
$ bundle exec expedite server
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
+
If `rails` is in the `Gemfile`, then you can also start the rails commands through expedite.
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
$ bundle exec expedite rails console
|
|
96
|
+
$ echo "puts ActiveRecord::Base.connection" | bundle exec expedite rails runner -
|
|
97
|
+
```
|
|
98
|
+
|
|
92
99
|
## Acknowledgements
|
|
93
100
|
|
|
94
101
|
Expedite's server core is modified from [Spring](https://github.com/rails/spring)
|
data/lib/expedite/actions.rb
CHANGED
|
@@ -35,9 +35,9 @@ module Expedite
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def lookup(name)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
ret = @registrations[name.to_s]
|
|
39
|
+
raise NotImplementedError, "Action #{name.inspect} not found" if ret.nil?
|
|
40
|
+
ret
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def register(name, klass_or_nil = nil, **named_options, &block)
|
|
@@ -47,7 +47,7 @@ module Expedite
|
|
|
47
47
|
klass_or_nil.new(**named_options)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
@registrations[name] = cmd
|
|
50
|
+
@registrations[name.to_s] = cmd
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def reset
|
data/lib/expedite/cli.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'expedite/cli/rails'
|
|
1
2
|
require 'expedite/cli/server'
|
|
2
3
|
require 'expedite/cli/status'
|
|
3
4
|
require 'expedite/cli/stop'
|
|
@@ -13,7 +14,7 @@ module Expedite
|
|
|
13
14
|
puts
|
|
14
15
|
puts "Commands:"
|
|
15
16
|
|
|
16
|
-
cmds = Expedite::Cli
|
|
17
|
+
cmds = Expedite::Cli.commands
|
|
17
18
|
cmds.keys.sort!.each do |cmd|
|
|
18
19
|
c = cmds[cmd].new
|
|
19
20
|
puts " #{cmd}: #{c.summary}"
|
|
@@ -27,12 +28,18 @@ module Expedite
|
|
|
27
28
|
|
|
28
29
|
module_function
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
def commands
|
|
32
|
+
return @commands unless @commands.nil?
|
|
33
|
+
|
|
34
|
+
cmds = {
|
|
35
|
+
'help' => Cli::Help,
|
|
36
|
+
'server' => Cli::Server,
|
|
37
|
+
'status' => Cli::Status,
|
|
38
|
+
'stop' => Cli::Stop,
|
|
39
|
+
}
|
|
40
|
+
cmds['rails'] = Cli::Rails if Gem.loaded_specs["rails"]
|
|
41
|
+
@commands = cmds
|
|
42
|
+
end
|
|
36
43
|
|
|
37
44
|
def run(args)
|
|
38
45
|
command(args.first).run(args[1..])
|
|
@@ -42,7 +49,7 @@ module Expedite
|
|
|
42
49
|
end
|
|
43
50
|
|
|
44
51
|
def command(cmd)
|
|
45
|
-
klass =
|
|
52
|
+
klass = commands[cmd]
|
|
46
53
|
raise UnknownCommandError, "Unknown command '#{cmd}'" if klass.nil?
|
|
47
54
|
klass.new
|
|
48
55
|
end
|
|
@@ -0,0 +1,165 @@
|
|
|
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 Base
|
|
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}, env)
|
|
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({
|
|
69
|
+
"args" => args,
|
|
70
|
+
"env" => ENV.to_hash,
|
|
71
|
+
"method" => run_command_method,
|
|
72
|
+
}, env)
|
|
73
|
+
|
|
74
|
+
pid = server.gets
|
|
75
|
+
pid = pid.chomp if pid
|
|
76
|
+
|
|
77
|
+
# We must not close the client socket until we are sure that the application has
|
|
78
|
+
# received the FD. Otherwise the FD can end up getting closed while it's in the server
|
|
79
|
+
# socket buffer on OS X. This doesn't happen on Linux.
|
|
80
|
+
client.close
|
|
81
|
+
|
|
82
|
+
if pid && !pid.empty?
|
|
83
|
+
log "got pid: #{pid}"
|
|
84
|
+
|
|
85
|
+
## suspend_resume_on_tstp_cont(pid)
|
|
86
|
+
|
|
87
|
+
## forward_signals(application)
|
|
88
|
+
result = agent.recv_object
|
|
89
|
+
if result.key?("exception")
|
|
90
|
+
e = result["exception"]
|
|
91
|
+
log "got exception #{e}"
|
|
92
|
+
raise e
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
ret = result["return"]
|
|
96
|
+
log "got return value #{ret}"
|
|
97
|
+
return ret
|
|
98
|
+
else
|
|
99
|
+
log "got no pid"
|
|
100
|
+
raise UnknownError, "got no pid"
|
|
101
|
+
end
|
|
102
|
+
ensure
|
|
103
|
+
agent.close
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def boot_server
|
|
107
|
+
env.socket_path.unlink if env.socket_path.exist?
|
|
108
|
+
|
|
109
|
+
pid = Process.spawn(gem_env, env.server_command, out: File::NULL)
|
|
110
|
+
timeout = Time.now + BOOT_TIMEOUT
|
|
111
|
+
|
|
112
|
+
@server_booted = true
|
|
113
|
+
|
|
114
|
+
until env.socket_path.exist?
|
|
115
|
+
_, status = Process.waitpid2(pid, Process::WNOHANG)
|
|
116
|
+
|
|
117
|
+
if status
|
|
118
|
+
# Server did not start
|
|
119
|
+
raise ArgumentError, "Server exited: #{status.exitstatus}"
|
|
120
|
+
elsif Time.now > timeout
|
|
121
|
+
$stderr.puts "Starting Expedite server with `#{env.server_command}` " \
|
|
122
|
+
"timed out after #{BOOT_TIMEOUT} seconds"
|
|
123
|
+
exit 1
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
sleep 0.1
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def server_booted?
|
|
131
|
+
@server_booted
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def stop_server
|
|
135
|
+
server.close
|
|
136
|
+
@server = nil
|
|
137
|
+
env.stop
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def log(message)
|
|
141
|
+
env.log "[client] #{message}"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def connect
|
|
145
|
+
@server = UNIXSocket.open(env.socket_path)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def gem_env
|
|
149
|
+
bundle = Bundler.bundle_path.to_s
|
|
150
|
+
paths = Gem.path + ENV["GEM_PATH"].to_s.split(File::PATH_SEPARATOR)
|
|
151
|
+
|
|
152
|
+
{
|
|
153
|
+
"GEM_PATH" => [bundle, *paths].uniq.join(File::PATH_SEPARATOR),
|
|
154
|
+
"GEM_HOME" => bundle
|
|
155
|
+
}
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
protected
|
|
160
|
+
def run_command_method
|
|
161
|
+
raise NotImplementedError, "run_command_method must be overriden"
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
data/lib/expedite/client/exec.rb
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
|
|
2
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'
|
|
3
|
+
require 'expedite/client/base'
|
|
12
4
|
|
|
13
5
|
module Expedite
|
|
14
6
|
module Client
|
|
15
|
-
class Exec <
|
|
7
|
+
class Exec < Base
|
|
16
8
|
FORWARDED_SIGNALS = %w(INT QUIT USR1 USR2 INFO WINCH) & Signal.list.keys
|
|
17
9
|
|
|
18
10
|
def initialize(env: nil, agent: nil)
|
|
@@ -58,7 +50,16 @@ module Expedite
|
|
|
58
50
|
def run
|
|
59
51
|
status = perform(*@args)
|
|
60
52
|
|
|
61
|
-
|
|
53
|
+
code = if status.respond_to?(:to_i)
|
|
54
|
+
status.to_i
|
|
55
|
+
elsif status == true
|
|
56
|
+
0
|
|
57
|
+
elsif status == false
|
|
58
|
+
1
|
|
59
|
+
else
|
|
60
|
+
126
|
|
61
|
+
end
|
|
62
|
+
exit code
|
|
62
63
|
rescue Errno::ECONNRESET
|
|
63
64
|
exit 1
|
|
64
65
|
end
|
|
@@ -119,6 +120,11 @@ module Expedite
|
|
|
119
120
|
application.puts(sig)
|
|
120
121
|
application.gets.to_i
|
|
121
122
|
end
|
|
123
|
+
|
|
124
|
+
protected
|
|
125
|
+
def run_command_method
|
|
126
|
+
"exec"
|
|
127
|
+
end
|
|
122
128
|
end
|
|
123
129
|
end
|
|
124
130
|
end
|
|
@@ -1,154 +1,13 @@
|
|
|
1
1
|
require 'socket'
|
|
2
2
|
|
|
3
|
-
require 'expedite/
|
|
4
|
-
require 'expedite/errors'
|
|
5
|
-
require 'expedite/protocol'
|
|
3
|
+
require 'expedite/client/base'
|
|
6
4
|
|
|
7
5
|
module Expedite
|
|
8
6
|
module Client
|
|
9
|
-
class Invoke
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
}
|
|
7
|
+
class Invoke < Base
|
|
8
|
+
protected
|
|
9
|
+
def run_command_method
|
|
10
|
+
"invoke"
|
|
152
11
|
end
|
|
153
12
|
end
|
|
154
13
|
end
|
data/lib/expedite/env.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require 'digest'
|
|
2
2
|
require 'pathname'
|
|
3
3
|
require 'expedite/version'
|
|
4
|
-
require 'expedite/server/
|
|
4
|
+
require 'expedite/server/application_manager'
|
|
5
5
|
|
|
6
6
|
module Expedite
|
|
7
7
|
class Env
|
|
@@ -18,9 +18,7 @@ module Expedite
|
|
|
18
18
|
@application_id = Digest::SHA1.hexdigest(@root)
|
|
19
19
|
|
|
20
20
|
env = self
|
|
21
|
-
@applications =
|
|
22
|
-
h[k] = Server::AgentManager.new(k, env)
|
|
23
|
-
end
|
|
21
|
+
@applications = Server::ApplicationManager.new(env)
|
|
24
22
|
end
|
|
25
23
|
|
|
26
24
|
def version
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
|
|
2
1
|
Expedite.define do
|
|
2
|
+
# Agent that rails environment loaded
|
|
3
3
|
agent :rails_environment do
|
|
4
4
|
app_root = Dir.pwd
|
|
5
|
+
require "#{app_root}/config/environment.rb"
|
|
6
|
+
end
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
Rails.application.eager_load!
|
|
8
|
+
# Actions that runs rails commands
|
|
9
|
+
action :rails_commands do |args|
|
|
10
|
+
ARGV.replace(args)
|
|
11
|
+
require "rails/commands"
|
|
12
|
+
true
|
|
13
13
|
end
|
|
14
14
|
end
|
data/lib/expedite/protocol.rb
CHANGED
|
@@ -23,6 +23,13 @@ module Expedite
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
module Server
|
|
26
|
+
# This code runs in the process that has the actual code pre-loaded, and is
|
|
27
|
+
# used to serve requests.
|
|
28
|
+
# * An "invoke" request is handled in the agent itself.
|
|
29
|
+
# * A "fork" request causes the agent to fork, and the forked process handles
|
|
30
|
+
# the request.
|
|
31
|
+
# Each agent processes a single request at a time, unless it is an
|
|
32
|
+
# Expedite::Action::Boot request which is used to make derived agents.
|
|
26
33
|
class Agent
|
|
27
34
|
include Signals
|
|
28
35
|
|
|
@@ -125,7 +132,7 @@ module Expedite
|
|
|
125
132
|
end
|
|
126
133
|
|
|
127
134
|
def serve(client)
|
|
128
|
-
|
|
135
|
+
puts "got client"
|
|
129
136
|
manager.puts
|
|
130
137
|
|
|
131
138
|
_stdout, stderr, _stdin = streams = 3.times.map { client.recv_io }
|
|
@@ -133,48 +140,25 @@ module Expedite
|
|
|
133
140
|
|
|
134
141
|
preload unless preloaded?
|
|
135
142
|
|
|
136
|
-
args, env = client.recv_object.values_at("args", "env")
|
|
143
|
+
args, env, method = client.recv_object.values_at("args", "env", "method")
|
|
144
|
+
log "serve #{args} using #{method}"
|
|
137
145
|
|
|
138
146
|
exec_name = args.shift
|
|
139
147
|
action = Expedite::Actions.lookup(exec_name)
|
|
140
148
|
action.setup(client)
|
|
141
149
|
|
|
142
|
-
connect_database
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
# Load in the current env vars, except those which *were* changed when Spring started
|
|
150
|
-
env.each { |k, v| ENV[k] = v }
|
|
151
|
-
|
|
152
|
-
# requiring is faster, so if config.cache_classes was true in
|
|
153
|
-
# the environment's config file, then we can respect that from
|
|
154
|
-
# here on as we no longer need constant reloading.
|
|
155
|
-
if @original_cache_classes
|
|
156
|
-
ActiveSupport::Dependencies.mechanism = :require
|
|
157
|
-
Rails.application.config.cache_classes = true
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
connect_database
|
|
161
|
-
srand
|
|
162
|
-
|
|
163
|
-
invoke_after_fork_callbacks
|
|
164
|
-
shush_backtraces
|
|
165
|
-
|
|
166
|
-
begin
|
|
167
|
-
ret = action.call(*args)
|
|
168
|
-
rescue => e
|
|
169
|
-
client.send_object("exception" => e)
|
|
170
|
-
else
|
|
171
|
-
client.send_object("return" => ret )
|
|
172
|
-
end
|
|
150
|
+
connect_database # why are we connecting prior? is this for invoke?
|
|
151
|
+
pid = case method
|
|
152
|
+
when "invoke"
|
|
153
|
+
# TODO: Invoke in a worker process instead of the preloader
|
|
154
|
+
serve_invoke(client, action, args, env)
|
|
155
|
+
else
|
|
156
|
+
serve_fork(client, action, args, env)
|
|
173
157
|
end
|
|
174
158
|
|
|
175
159
|
disconnect_database
|
|
176
160
|
|
|
177
|
-
log "forked #{pid}"
|
|
161
|
+
log "forked #{pid}" # pid is current process
|
|
178
162
|
manager.puts pid
|
|
179
163
|
|
|
180
164
|
# Boot makes a new application, so we don't wait for it
|
|
@@ -201,6 +185,51 @@ module Expedite
|
|
|
201
185
|
reset_streams
|
|
202
186
|
end
|
|
203
187
|
|
|
188
|
+
# Returns pid of the current process
|
|
189
|
+
def serve_invoke(client, action, args, env)
|
|
190
|
+
begin
|
|
191
|
+
ret = action.call(*args)
|
|
192
|
+
rescue Exception => e
|
|
193
|
+
client.send_object({"exception" => e}, self.env)
|
|
194
|
+
else
|
|
195
|
+
client.send_object({"return" => ret}, self.env)
|
|
196
|
+
end
|
|
197
|
+
Process.pid
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def serve_fork(client, action, args, env)
|
|
201
|
+
fork do
|
|
202
|
+
Process.setsid
|
|
203
|
+
IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") }
|
|
204
|
+
trap("TERM", "DEFAULT")
|
|
205
|
+
|
|
206
|
+
# Load in the current env vars, except those which *were* changed when Spring started
|
|
207
|
+
env.each { |k, v| ENV[k] = v }
|
|
208
|
+
|
|
209
|
+
# requiring is faster, so if config.cache_classes was true in
|
|
210
|
+
# the environment's config file, then we can respect that from
|
|
211
|
+
# here on as we no longer need constant reloading.
|
|
212
|
+
if @original_cache_classes
|
|
213
|
+
ActiveSupport::Dependencies.mechanism = :require
|
|
214
|
+
Rails.application.config.cache_classes = true
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
connect_database
|
|
218
|
+
srand
|
|
219
|
+
|
|
220
|
+
invoke_after_fork_callbacks
|
|
221
|
+
shush_backtraces
|
|
222
|
+
|
|
223
|
+
begin
|
|
224
|
+
ret = action.call(*args)
|
|
225
|
+
rescue => e
|
|
226
|
+
client.send_object({"exception" => e}, self.env)
|
|
227
|
+
else
|
|
228
|
+
client.send_object({"return" => ret}, self.env)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
204
233
|
def terminate
|
|
205
234
|
if exiting?
|
|
206
235
|
# Ensure that we do not ignore subsequent termination attempts
|
|
@@ -283,19 +312,28 @@ module Expedite
|
|
|
283
312
|
end
|
|
284
313
|
|
|
285
314
|
def wait(pid, streams, client)
|
|
286
|
-
|
|
315
|
+
if pid != Process.pid
|
|
316
|
+
@mutex.synchronize { @waiting << pid }
|
|
317
|
+
end
|
|
287
318
|
|
|
288
319
|
# Wait in a separate thread so we can run multiple actions at once
|
|
289
320
|
Expedite.failsafe_thread {
|
|
290
321
|
begin
|
|
291
|
-
|
|
292
|
-
|
|
322
|
+
exitstatue = if pid == Process.pid
|
|
323
|
+
log "#{pid} is current process"
|
|
324
|
+
0
|
|
325
|
+
else
|
|
326
|
+
_, status = Process.wait2 pid
|
|
327
|
+
log "#{pid} exited with #{status.exitstatus}"
|
|
328
|
+
end
|
|
293
329
|
|
|
294
330
|
streams.each(&:close)
|
|
295
|
-
client.puts(
|
|
331
|
+
client.puts(exitstatus)
|
|
296
332
|
client.close
|
|
297
333
|
ensure
|
|
298
|
-
|
|
334
|
+
if pid != Process.pid
|
|
335
|
+
@mutex.synchronize { @waiting.delete pid }
|
|
336
|
+
end
|
|
299
337
|
exit_if_finished
|
|
300
338
|
end
|
|
301
339
|
}
|
|
@@ -7,6 +7,7 @@ require 'expedite/agents'
|
|
|
7
7
|
|
|
8
8
|
module Expedite
|
|
9
9
|
module Server
|
|
10
|
+
# Locates agent processes and spawns them if necessary
|
|
10
11
|
class AgentManager
|
|
11
12
|
attr_reader :pid, :child, :name, :env, :status, :agent
|
|
12
13
|
|
|
@@ -81,7 +82,7 @@ module Expedite
|
|
|
81
82
|
end
|
|
82
83
|
rescue Exception => e
|
|
83
84
|
# NotImplementedError is an Exception, not StandardError
|
|
84
|
-
client.send_object("exception" => e)
|
|
85
|
+
client.send_object({"exception" => e}, env)
|
|
85
86
|
return Process.pid
|
|
86
87
|
rescue Errno::ECONNRESET, Errno::EPIPE => e
|
|
87
88
|
log "#{e} while reading from child; returning no pid"
|
|
@@ -120,6 +121,7 @@ module Expedite
|
|
|
120
121
|
end
|
|
121
122
|
end
|
|
122
123
|
|
|
124
|
+
# Creates a child that is forked from a parent
|
|
123
125
|
def fork_child(preload = false)
|
|
124
126
|
@child, child_socket = UNIXSocket.pair
|
|
125
127
|
|
|
@@ -129,21 +131,25 @@ module Expedite
|
|
|
129
131
|
wr.send_io STDERR
|
|
130
132
|
wr.send_io STDIN
|
|
131
133
|
|
|
132
|
-
wr.send_object(
|
|
134
|
+
wr.send_object({
|
|
133
135
|
'args' => ['expedite/boot', name],
|
|
134
|
-
'env' => {}
|
|
135
|
-
|
|
136
|
+
'env' => {},
|
|
137
|
+
'method' => "fork",
|
|
138
|
+
}, env)
|
|
136
139
|
|
|
137
140
|
wr.send_io child_socket
|
|
138
141
|
wr.send_io env.log_file
|
|
139
142
|
wr.close
|
|
140
143
|
|
|
141
|
-
@pid = env.applications
|
|
144
|
+
@pid = env.applications.with(parent) do |target|
|
|
145
|
+
target.run(rd)
|
|
146
|
+
end
|
|
142
147
|
|
|
143
148
|
start_wait_thread(pid, child) if child.gets
|
|
144
149
|
child_socket.close
|
|
145
150
|
end
|
|
146
151
|
|
|
152
|
+
# Creates a child that is started from scratch
|
|
147
153
|
def spawn_child(preload = false)
|
|
148
154
|
@child, child_socket = UNIXSocket.pair
|
|
149
155
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
require 'expedite/server/agent_manager'
|
|
3
|
+
|
|
4
|
+
module Expedite
|
|
5
|
+
module Server
|
|
6
|
+
class AgentPool
|
|
7
|
+
def initialize(name, env)
|
|
8
|
+
@name = name
|
|
9
|
+
@env = env
|
|
10
|
+
@checked_in = []
|
|
11
|
+
@checked_out = Set.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Get a free agent from the pool
|
|
15
|
+
def checkout
|
|
16
|
+
agent = @checked_in.pop
|
|
17
|
+
|
|
18
|
+
agent = build_agent if agent.nil?
|
|
19
|
+
@checked_out.add(agent)
|
|
20
|
+
|
|
21
|
+
agent
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def checkin(agent)
|
|
25
|
+
@checked_out.delete(agent)
|
|
26
|
+
@checked_in.push(agent)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def build_agent
|
|
30
|
+
Server::AgentManager.new(@name, @env)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Returns all agents, both checked in and checked out
|
|
34
|
+
def all
|
|
35
|
+
@checked_in + @checked_out.to_a
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'expedite/server/agent_pool'
|
|
2
|
+
|
|
3
|
+
module Expedite
|
|
4
|
+
module Server
|
|
5
|
+
class ApplicationManager
|
|
6
|
+
attr_reader :pools
|
|
7
|
+
|
|
8
|
+
def initialize(env)
|
|
9
|
+
@env = env
|
|
10
|
+
@pools = Hash.new do |h, k|
|
|
11
|
+
h[k] = AgentPool.new(k, @env)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def with(name)
|
|
16
|
+
pool = @pools[name]
|
|
17
|
+
target = pool.checkout
|
|
18
|
+
begin
|
|
19
|
+
ret = yield target
|
|
20
|
+
ensure
|
|
21
|
+
pool.checkin(target)
|
|
22
|
+
end
|
|
23
|
+
ret
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -101,12 +101,12 @@ module Expedite
|
|
|
101
101
|
app_client = client.recv_io
|
|
102
102
|
command = client.recv_object
|
|
103
103
|
|
|
104
|
-
args, agent = command.values_at(
|
|
104
|
+
args, agent = command.values_at("args", "agent")
|
|
105
105
|
cmd = args.first
|
|
106
106
|
|
|
107
|
-
if agent ==
|
|
107
|
+
if agent == "__server__"
|
|
108
108
|
case cmd
|
|
109
|
-
when
|
|
109
|
+
when "application_pids"
|
|
110
110
|
# Corresponds to Client::Invoke#run_command
|
|
111
111
|
client.puts
|
|
112
112
|
|
|
@@ -118,10 +118,10 @@ module Expedite
|
|
|
118
118
|
client.puts Process.pid
|
|
119
119
|
|
|
120
120
|
application_pids = []
|
|
121
|
-
env.applications.each do |k,
|
|
122
|
-
application_pids
|
|
121
|
+
env.applications.pools.each do |k, pool|
|
|
122
|
+
application_pids.concat(pool.all.map(&:pid))
|
|
123
123
|
end
|
|
124
|
-
unix_socket.send_object("return" => application_pids)
|
|
124
|
+
unix_socket.send_object({"return" => application_pids}, env)
|
|
125
125
|
|
|
126
126
|
unix_socket.close
|
|
127
127
|
client.close
|
|
@@ -134,9 +134,9 @@ module Expedite
|
|
|
134
134
|
client.puts
|
|
135
135
|
|
|
136
136
|
begin
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
env.applications.with(agent) do |target|
|
|
138
|
+
client.puts target.run(app_client)
|
|
139
|
+
end
|
|
140
140
|
rescue AgentNotFoundError => e
|
|
141
141
|
unix_socket = UNIXSocket.for_fd(app_client.fileno)
|
|
142
142
|
_stdout = unix_socket.recv_io
|
|
@@ -150,7 +150,7 @@ module Expedite
|
|
|
150
150
|
# boot only
|
|
151
151
|
#@child_socket = client.recv_io
|
|
152
152
|
#@log_file = client.recv_io
|
|
153
|
-
unix_socket.send_object("exception" => e)
|
|
153
|
+
unix_socket.send_object({"exception" => e}, env)
|
|
154
154
|
|
|
155
155
|
unix_socket.close
|
|
156
156
|
client.close
|
|
@@ -195,7 +195,13 @@ module Expedite
|
|
|
195
195
|
end
|
|
196
196
|
end
|
|
197
197
|
|
|
198
|
-
|
|
198
|
+
thrs = []
|
|
199
|
+
env.applications.pools.each do |k, pool|
|
|
200
|
+
pool.all.each do |a|
|
|
201
|
+
thrs << Expedite.failsafe_thread { a.stop }
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
thrs.map(&:join)
|
|
199
205
|
end
|
|
200
206
|
|
|
201
207
|
def write_pidfile
|
data/lib/expedite/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: expedite
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bing-Chang Lai
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-12-
|
|
11
|
+
date: 2022-12-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Manages Ruby processes that can be used to spawn child processes faster.
|
|
14
14
|
email: johnny.lai@me.com
|
|
@@ -25,9 +25,11 @@ files:
|
|
|
25
25
|
- lib/expedite/actions.rb
|
|
26
26
|
- lib/expedite/agents.rb
|
|
27
27
|
- lib/expedite/cli.rb
|
|
28
|
+
- lib/expedite/cli/rails.rb
|
|
28
29
|
- lib/expedite/cli/server.rb
|
|
29
30
|
- lib/expedite/cli/status.rb
|
|
30
31
|
- lib/expedite/cli/stop.rb
|
|
32
|
+
- lib/expedite/client/base.rb
|
|
31
33
|
- lib/expedite/client/exec.rb
|
|
32
34
|
- lib/expedite/client/invoke.rb
|
|
33
35
|
- lib/expedite/env.rb
|
|
@@ -38,6 +40,8 @@ files:
|
|
|
38
40
|
- lib/expedite/server/agent.rb
|
|
39
41
|
- lib/expedite/server/agent_boot.rb
|
|
40
42
|
- lib/expedite/server/agent_manager.rb
|
|
43
|
+
- lib/expedite/server/agent_pool.rb
|
|
44
|
+
- lib/expedite/server/application_manager.rb
|
|
41
45
|
- lib/expedite/server/controller.rb
|
|
42
46
|
- lib/expedite/signals.rb
|
|
43
47
|
- lib/expedite/syntax.rb
|