expedite 0.2.3 → 0.2.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8babab9c1e4ea0a7e6b07fe21738b029200d6f32cf1630552bce7fef3a7f6092
4
- data.tar.gz: c24fd8f9e9cd7720c32d37329c67cccb089987b8a24df30bfedb45cdb20f48e8
3
+ metadata.gz: ece07855912fe5042042713eb78fecedc03cd06f5b495c0f17a1e7dab14ee99e
4
+ data.tar.gz: c94ea277e1a83968b187648d5917ed2e42181c81f3a3a04c4c6acafc1c52fbf5
5
5
  SHA512:
6
- metadata.gz: 8beb17a4d477869134b0775b8437e15f51dbb9e51138b49355ea5fade24e7a771b07fdfec98f92c5bb28bf0cc876fa673befd3bd38848ca8c74fcda143996bd9
7
- data.tar.gz: 3d810666ae634dfc032c99ee2b73d55f17790b552c85ab6dd4ba1e486c19374ac6e1d36fb0ff73badb1d5a807a5a1f1520ca7060dcda9e72ee2d306efed6cec1
6
+ metadata.gz: eb9688825664432ab5e7f5ef60ee855715eabcf6e25ee9242c6eca816c99bb7431eb36ad12b1124e11401dd961b4d68868077c41c9dd8f6b5bb20ffc854a1e09
7
+ data.tar.gz: baf6f7c68c3f98eccf8651947b72dd7e3150580d886015038e7ccd68e0c63798db82e8b6977c1f974a1c027112a233dc0fc227b002cb763151c3916fbda58a5f
data/README.md CHANGED
@@ -16,7 +16,9 @@ This is the "parent" agent:
16
16
  ```
17
17
  Expedite.define do
18
18
  agent :parent do
19
- $parent_var = 1
19
+ before(:serve) do |name|
20
+ $parent_var = name
21
+ end
20
22
  end
21
23
  end
22
24
  ```
@@ -26,8 +28,12 @@ matchers.
26
28
 
27
29
  ```
28
30
  Expedite.define do
29
- agent "development/*", parent: :parent do |name|
30
- $development_var = name
31
+ agent "development/*" do
32
+ self.parent = :parent
33
+
34
+ before(:serve) do |name|
35
+ $child_var = name
36
+ end
31
37
  end
32
38
  end
33
39
  ```
@@ -37,10 +43,12 @@ The following defines an `info` action.
37
43
  ```
38
44
  Expedite.define do
39
45
  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}"
46
+ {
47
+ "Process.pid" => Process.pid,
48
+ "Process.ppid" => Process.ppid,
49
+ "$parent_var" => $parent_var,
50
+ "$child_var" => $child_var,
51
+ }
44
52
  end
45
53
  end
46
54
  ```
@@ -55,7 +63,7 @@ the action; in that case, the return result is the exit code.
55
63
  ```
56
64
  require 'expedite'
57
65
 
58
- Expedite.agent("development/abc").invoke("info")
66
+ puts Expedite.agent("development/abc").invoke("info")
59
67
  ```
60
68
 
61
69
  When you run `main.rb`, the following output is produced. Note that `$sleep_parent`
@@ -3,7 +3,7 @@ module Expedite
3
3
  module Action
4
4
  class Block
5
5
  attr_reader :runs_in
6
-
6
+
7
7
  def initialize(runs_in: :application, &block)
8
8
  @runs_in = runs_in
9
9
  @block = block
@@ -13,6 +13,7 @@ module Expedite
13
13
  @block.call(*args)
14
14
  end
15
15
 
16
+ # Ignoring all streams
16
17
  def setup(_)
17
18
  end
18
19
  end
@@ -2,12 +2,12 @@ module Expedite
2
2
  module Action
3
3
  class Boot
4
4
  def call(*args)
5
- agent = args[0]
5
+ name = args[0]
6
6
 
7
7
  require "expedite/server/agent"
8
-
8
+
9
9
  Expedite::Server::Agent.new(
10
- agent: agent,
10
+ name: name,
11
11
  manager: UNIXSocket.for_fd(@child_socket.fileno),
12
12
  env: Expedite::Env.new(
13
13
  root: ENV['EXPEDITE_ROOT'],
@@ -16,9 +16,10 @@ module Expedite
16
16
  ).boot
17
17
  end
18
18
 
19
- def setup(client)
20
- @child_socket = client.recv_io
21
- @log_file = client.recv_io
19
+ # STDOUT, STDERR, STDIN, log, child_socket
20
+ def setup(streams)
21
+ @log_file = streams[3]
22
+ @child_socket = streams[4]
22
23
  end
23
24
 
24
25
  def runs_in
@@ -1,7 +1,10 @@
1
+ require 'expedite/hooks'
1
2
 
2
3
  module Expedite
3
4
  # Definition of a Agent
4
5
  class Agent
6
+ include Hooks
7
+
5
8
  ##
6
9
  # Name of the parent agent. This allows you to create agents from
7
10
  # an existing agent.
@@ -16,18 +19,20 @@ module Expedite
16
19
  ##
17
20
  # [parent] Name of parent agent.
18
21
  # [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)
22
+ def initialize(parent: nil, keep_alive: false)
21
23
  @parent = parent
22
24
  @keep_alive = keep_alive
23
- @after_fork_proc = after_fork
24
25
  end
25
26
 
26
27
  ##
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)
28
+ # Register a before event
29
+ # @params event [String] Allowed values: :run
30
+ def before(event, &block)
31
+ register_hook(:"before_#{event}", block)
32
+ end
33
+
34
+ def after(event, &block)
35
+ register_hook(:"after_#{event}", block)
31
36
  end
32
37
  end
33
38
 
@@ -54,8 +59,6 @@ module Expedite
54
59
  #
55
60
  # [matcher] Wildcard to match a name against.
56
61
  # [named_options] Agent options.
57
- # [after_fork] Optional block that is called when
58
- # agent is preloaded.
59
62
  #
60
63
  # = Example
61
64
  # Expedite::Agents.register('base' do |name|
@@ -64,8 +67,8 @@ module Expedite
64
67
  # Expedite::Agents.register('development/abc', parent: 'base') do |name|
65
68
  # puts "Agent #{name} started"
66
69
  # end
67
- def self.register(matcher, **named_options, &after_fork)
68
- self.current.register(matcher.to_s, **named_options, &after_fork)
70
+ def self.register(matcher, **named_options)
71
+ self.current.register(matcher.to_s, **named_options)
69
72
  end
70
73
 
71
74
  ##
@@ -86,11 +89,10 @@ module Expedite
86
89
  ret.agent
87
90
  end
88
91
 
89
- def register(matcher, **named_options, &after_fork)
90
- @registrations << Registration.new(
91
- matcher,
92
- Agent.new(**named_options, &after_fork)
93
- )
92
+ def register(matcher, **named_options)
93
+ agent = Agent.new(**named_options)
94
+ @registrations << Registration.new(matcher, agent)
95
+ agent
94
96
  end
95
97
 
96
98
  def reset
@@ -4,23 +4,23 @@ require 'expedite/client/invoke'
4
4
  module Expedite
5
5
  module Client
6
6
  class AgentProxy
7
- attr_accessor :env, :agent
7
+ attr_accessor :name, :env
8
8
 
9
9
  ##
10
10
  #
11
+ # @param name [String] Name of the agent
11
12
  # @param env [Expedite::Env] Environment
12
- # @param agent [String] Name of the agent
13
- def initialize(env:, agent:)
13
+ def initialize(name, env:)
14
+ self.name = name
14
15
  self.env = env
15
- self.agent = agent
16
16
  end
17
17
 
18
18
  def exec(*args)
19
- Client::Exec.new(env: env, agent: agent).call(*args)
19
+ Client::Exec.new(env: env, agent: name).call(*args)
20
20
  end
21
21
 
22
22
  def invoke(*args)
23
- Client::Invoke.new(env: env, agent: agent).call(*args)
23
+ Client::Invoke.new(env: env, agent: name).call(*args)
24
24
  end
25
25
  end
26
26
  end
@@ -59,11 +59,11 @@ module Expedite
59
59
  end
60
60
 
61
61
  def run_command(client, agent, args)
62
+ @null_socket ||= File.open(File::NULL, "a")
62
63
  log "sending command"
63
64
 
64
- agent.send_io STDOUT
65
- agent.send_io STDERR
66
- agent.send_io STDIN
65
+ # No child socket
66
+ agent.send_setup(@null_socket, env)
67
67
 
68
68
  agent.send_object({
69
69
  "args" => args,
@@ -85,7 +85,7 @@ module Expedite
85
85
  ## suspend_resume_on_tstp_cont(pid)
86
86
 
87
87
  ## forward_signals(application)
88
- result = agent.recv_object
88
+ result = agent.recv_object(env)
89
89
  if result.key?("exception")
90
90
  e = result["exception"]
91
91
  log "got exception #{e}"
@@ -0,0 +1,46 @@
1
+ module Expedite
2
+ module Hooks
3
+ ##
4
+ # Register a new hook with the given block to name
5
+ # @param name [String] Name of the hook
6
+ def register_hook(name, block)
7
+ return clear_hooks(name) if block.nil?
8
+
9
+ block = Array(block)
10
+ all_hooks[name].concat(block)
11
+ end
12
+
13
+ ##
14
+ # Clears all hooks for the specified name
15
+ # @param name [String] Name of the hook
16
+ def clear_hooks(name)
17
+ all_hooks[name] = []
18
+ end
19
+
20
+ ##
21
+ # Returns all hooks for the specified name
22
+ # @param name [String] Name of the hook
23
+ def hooks(name)
24
+ all_hooks[name]
25
+ end
26
+
27
+ ##
28
+ # Returne all hooks as a hash
29
+ def all_hooks
30
+ @all_hooks ||= Hash.new { |h, k| h[k] = [] }
31
+ end
32
+
33
+ ##
34
+ # Runs all Procs registered for the specified name
35
+ # @param name [String] Name of the hook
36
+ # @param @args Arguments passed to the Procs
37
+ def run_hook(name, *args)
38
+ hks = hooks(name)
39
+ return if hks.empty?
40
+
41
+ hks.each do |hook|
42
+ args.any? ? hook.call(*args) : hook.call
43
+ end
44
+ end
45
+ end
46
+ end
@@ -8,6 +8,7 @@ module Expedite
8
8
 
9
9
  self.puts data.bytesize.to_i
10
10
  self.write data
11
+ self.write "$"
11
12
  end
12
13
 
13
14
  ##
@@ -29,11 +30,38 @@ module Expedite
29
30
  self.send_object({"return" => obj}, env)
30
31
  end
31
32
 
32
- def recv_object
33
+ def recv_object(env)
33
34
  len = self.gets.to_i
34
35
  data = self.read(len)
36
+ e = self.read(1)
37
+ env.log "recv_object len=#{len} data=... e=#{e}"
38
+ raise "Unexpected end #{e}" if e != "$"
35
39
  Marshal.load(data)
36
40
  end
41
+
42
+
43
+ ##
44
+ # Send sockets for setup.
45
+ # Linux seems to require all fds to be sent first
46
+ #
47
+ def send_setup(child_socket, env)
48
+ self.send_io STDOUT
49
+ self.send_io STDERR
50
+ self.send_io STDIN
51
+ self.send_io env.log_file
52
+ self.send_io child_socket
53
+ end
54
+
55
+ ##
56
+ # Receive setup sockets.
57
+ # Returns the child_socket
58
+ #
59
+ def recv_setup(env)
60
+ streams = 5.times.map { self.recv_io }
61
+ [STDOUT, STDERR, STDIN].zip(streams[0..2]).each { |a, b| a.reopen(b) }
62
+ env.log_file = streams[3]
63
+ return streams
64
+ end
37
65
  end
38
66
  end
39
67
 
@@ -4,11 +4,11 @@ require 'pty'
4
4
  require 'set'
5
5
  require 'socket'
6
6
  require 'expedite/actions'
7
+ require 'expedite/agents'
7
8
  require 'expedite/env'
8
9
  require 'expedite/failsafe_thread'
9
10
  require 'expedite/protocol'
10
11
  require 'expedite/signals'
11
- require 'expedite/agents'
12
12
 
13
13
  module Expedite
14
14
  def self.agent
@@ -33,11 +33,12 @@ module Expedite
33
33
  class Agent
34
34
  include Signals
35
35
 
36
- attr_reader :agent
36
+ attr_reader :name
37
37
  attr_reader :manager, :env
38
38
 
39
- def initialize(agent:, manager:, env:)
40
- @agent = agent
39
+ # @params name [String] Name of agent
40
+ def initialize(name:, manager:, env:)
41
+ @name = name
41
42
  @manager = manager
42
43
  @env = env
43
44
  @mutex = Mutex.new
@@ -56,8 +57,8 @@ module Expedite
56
57
  Signal.trap("TERM") { terminate }
57
58
 
58
59
  env.load_helper
59
- eager_preload if false #if ENV.delete("SPRING_PRELOAD") == "1"
60
- run
60
+
61
+ serve
61
62
  end
62
63
 
63
64
  def state(val)
@@ -75,8 +76,12 @@ module Expedite
75
76
  env.app_name
76
77
  end
77
78
 
79
+ def agent
80
+ @agent ||= Expedite::Agents.lookup(name)
81
+ end
82
+
78
83
  def log(message)
79
- env.log "[application:#{agent}] #{message}"
84
+ env.log "[application:#{name}] #{message}"
80
85
  end
81
86
 
82
87
  def preloaded?
@@ -112,10 +117,10 @@ module Expedite
112
117
  with_pty { preload }
113
118
  end
114
119
 
115
- def run
116
- $0 = "expedite agent | #{app_name} | #{agent}"
120
+ def serve
121
+ $0 = "expedite agent | #{app_name} | #{name}"
117
122
 
118
- Expedite::Agents.lookup(agent).after_fork(agent)
123
+ agent.run_hook(:before_serve, name)
119
124
 
120
125
  state :running
121
126
  manager.puts
@@ -124,36 +129,37 @@ module Expedite
124
129
  IO.select [manager, @interrupt.first]
125
130
 
126
131
  if terminating? || preload_failed?
132
+ agent.run_hook(:after_serve, name)
127
133
  exit
128
134
  else
129
- serve manager.recv_io(UNIXSocket)
135
+ serve_request(manager.recv_io(UNIXSocket))
130
136
  end
131
137
  end
132
138
  end
133
139
 
134
- def serve(client)
140
+ def serve_request(client)
135
141
  puts "got client"
136
142
  manager.puts
137
143
 
138
- _stdout, stderr, _stdin = streams = 3.times.map { client.recv_io }
139
- [STDOUT, STDERR, STDIN].zip(streams).each { |a, b| a.reopen(b) }
144
+ streams = client.recv_setup(env)
140
145
 
141
146
  preload unless preloaded?
142
147
 
143
- args, env, method = client.recv_object.values_at("args", "env", "method")
144
- log "serve #{args} using #{method}"
148
+ cargs, cenv, cmethod = client.recv_object(env).values_at("args", "env", "method")
149
+ log "serve #{cargs} using #{cmethod}"
145
150
 
146
- exec_name = args.shift
151
+ exec_name = cargs.shift
147
152
  action = Expedite::Actions.lookup(exec_name)
148
- action.setup(client)
153
+ action.setup(streams)
154
+ # TODO: before(:request)
149
155
 
150
156
  connect_database # why are we connecting prior? is this for invoke?
151
- pid = case method
157
+ pid = case cmethod
152
158
  when "invoke"
153
159
  # TODO: Invoke in a worker process instead of the preloader
154
- serve_invoke(client, action, args, env)
160
+ serve_invoke(client, action, cargs, cenv)
155
161
  else
156
- serve_fork(client, action, args, env)
162
+ serve_fork(client, action, cargs, cenv)
157
163
  end
158
164
 
159
165
  disconnect_database
@@ -186,25 +192,25 @@ module Expedite
186
192
  end
187
193
 
188
194
  # Returns pid of the current process
189
- def serve_invoke(client, action, args, env)
195
+ def serve_invoke(client, action, cargs, cenv)
190
196
  begin
191
- ret = action.call(*args)
197
+ ret = action.call(*cargs)
192
198
  rescue Exception => e
193
- client.send_object({"exception" => e}, self.env)
199
+ client.send_exception(e, self.env)
194
200
  else
195
- client.send_object({"return" => ret}, self.env)
201
+ client.send_return(ret, self.env)
196
202
  end
197
203
  Process.pid
198
204
  end
199
205
 
200
- def serve_fork(client, action, args, env)
206
+ def serve_fork(client, action, cargs, cenv)
201
207
  fork do
202
208
  Process.setsid
203
209
  IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") }
204
210
  trap("TERM", "DEFAULT")
205
211
 
206
212
  # Load in the current env vars, except those which *were* changed when Spring started
207
- env.each { |k, v| ENV[k] = v }
213
+ cenv.each { |k, v| ENV[k] = v }
208
214
 
209
215
  # requiring is faster, so if config.cache_classes was true in
210
216
  # the environment's config file, then we can respect that from
@@ -221,7 +227,7 @@ module Expedite
221
227
  shush_backtraces
222
228
 
223
229
  begin
224
- ret = action.call(*args)
230
+ ret = action.call(*cargs)
225
231
  rescue => e
226
232
  client.send_exception(e, self.env)
227
233
  else
@@ -1,7 +1,7 @@
1
1
  require "expedite/server/agent"
2
2
 
3
3
  app = Expedite::Server::Agent.new(
4
- agent: ENV['EXPEDITE_VARIANT'],
4
+ name: ENV['EXPEDITE_VARIANT'],
5
5
  manager: UNIXSocket.for_fd(3),
6
6
  env: Expedite::Env.new(
7
7
  root: ENV['EXPEDITE_ROOT'],
@@ -82,7 +82,7 @@ module Expedite
82
82
  end
83
83
  rescue Exception => e
84
84
  # NotImplementedError is an Exception, not StandardError
85
- client.send_object({"exception" => e}, env)
85
+ client.send_exception(e, env)
86
86
  return Process.pid
87
87
  rescue Errno::ECONNRESET, Errno::EPIPE => e
88
88
  log "#{e} while reading from child; returning no pid"
@@ -95,9 +95,10 @@ module Expedite
95
95
  log "stopping"
96
96
  @state = :stopping
97
97
 
98
- if pid
99
- Process.kill('TERM', pid)
100
- Process.wait(pid)
98
+ _pid = self.pid
99
+ if _pid
100
+ Process.kill('TERM', _pid)
101
+ Process.wait(_pid)
101
102
  end
102
103
  rescue Errno::ESRCH, Errno::ECHILD
103
104
  # Don't care
@@ -123,30 +124,31 @@ module Expedite
123
124
 
124
125
  # Creates a child that is forked from a parent
125
126
  def fork_child(preload = false)
126
- @child, child_socket = UNIXSocket.pair
127
+ child_socket = nil
128
+ wr = nil
129
+ begin
130
+ @child, child_socket = UNIXSocket.pair(:STREAM)
127
131
 
128
- # Compose command
129
- wr, rd = UNIXSocket.pair
130
- wr.send_io STDOUT
131
- wr.send_io STDERR
132
- wr.send_io STDIN
132
+ # Compose command
133
+ wr, rd = UNIXSocket.pair(:STREAM)
133
134
 
134
- wr.send_object({
135
- 'args' => ['expedite/boot', name],
136
- 'env' => {},
137
- 'method' => "fork",
138
- }, env)
135
+ wr.send_setup(child_socket, env)
139
136
 
140
- wr.send_io child_socket
141
- wr.send_io env.log_file
142
- wr.close
137
+ wr.send_object({
138
+ 'args' => ['expedite/boot', name],
139
+ 'env' => {},
140
+ 'method' => "fork",
141
+ }, env)
143
142
 
144
- @pid = env.applications.with(parent) do |target|
145
- target.run(rd)
146
- end
143
+ @pid = env.applications.with(parent) do |target|
144
+ target.run(rd)
145
+ end
147
146
 
148
- start_wait_thread(pid, child) if child.gets
149
- child_socket.close
147
+ start_wait_thread(pid, child) if child.gets
148
+ ensure
149
+ wr&.close
150
+ child_socket&.close
151
+ end
150
152
  end
151
153
 
152
154
  # Creates a child that is started from scratch
@@ -102,7 +102,7 @@ module Expedite
102
102
 
103
103
  # Corresponds to Client::Invoke#connect_to_agent
104
104
  app_client = client.recv_io
105
- command = client.recv_object
105
+ command = client.recv_object(env)
106
106
 
107
107
  args, agent = command.values_at("args", "agent")
108
108
  cmd = args.first
@@ -114,9 +114,7 @@ module Expedite
114
114
  client.puts
115
115
 
116
116
  unix_socket = UNIXSocket.for_fd(app_client.fileno)
117
- _stdout = unix_socket.recv_io
118
- _stderr = unix_socket.recv_io
119
- _stdin = unix_socket.recv_io
117
+ _ = unix_socket.recv_setup(env)
120
118
 
121
119
  client.puts Process.pid
122
120
 
@@ -124,7 +122,7 @@ module Expedite
124
122
  env.applications.pools.each do |k, pool|
125
123
  application_pids.concat(pool.all.map(&:pid))
126
124
  end
127
- unix_socket.send_object({"return" => application_pids}, env)
125
+ unix_socket.send_return(application_pids, env)
128
126
 
129
127
  unix_socket.close
130
128
  client.close
@@ -142,11 +140,9 @@ module Expedite
142
140
  end
143
141
  rescue AgentNotFoundError => e
144
142
  unix_socket = UNIXSocket.for_fd(app_client.fileno)
145
- _stdout = unix_socket.recv_io
146
- _stderr = unix_socket.recv_io
147
- _stdin = unix_socket.recv_io
143
+ _ = unix_socket.recv_setup(env)
148
144
 
149
- args, env = unix_socket.recv_object.values_at("args", "env")
145
+ args, env = unix_socket.recv_object(env).values_at("args", "env")
150
146
 
151
147
  client.puts Process.pid
152
148
 
@@ -13,7 +13,9 @@ module Expedite
13
13
  end
14
14
 
15
15
  def agent(name, parent:nil, &block)
16
- Expedite::Agents.register(name, parent: parent, &block)
16
+ agent = Expedite::Agents.register(name, parent: parent)
17
+ agent.instance_eval(&block) if !block.nil?
18
+ agent
17
19
  end
18
20
 
19
21
  def self.run(&block)
@@ -23,4 +25,4 @@ module Expedite
23
25
  end
24
26
 
25
27
  extend Syntax
26
- end
28
+ end
@@ -1,3 +1,3 @@
1
1
  module Expedite
2
- VERSION = '0.2.3'
2
+ VERSION = '0.2.4'
3
3
  end
data/lib/expedite.rb CHANGED
@@ -9,6 +9,6 @@ module Expedite
9
9
  # @param env [Expedite::Env] Defaults to an environment pointing to the
10
10
  # current directory
11
11
  def self.agent(name, env: Env.new)
12
- Client::AgentProxy.new(env: env, agent: name)
12
+ Client::AgentProxy.new(name, env: env)
13
13
  end
14
14
  end
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.2.3
4
+ version: 0.2.4
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-30 00:00:00.000000000 Z
11
+ date: 2023-02-08 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
@@ -37,6 +37,7 @@ files:
37
37
  - lib/expedite/errors.rb
38
38
  - lib/expedite/failsafe_thread.rb
39
39
  - lib/expedite/helper/rails.rb
40
+ - lib/expedite/hooks.rb
40
41
  - lib/expedite/protocol.rb
41
42
  - lib/expedite/server/agent.rb
42
43
  - lib/expedite/server/agent_boot.rb