arachni-rpc-em 0.1.2 → 0.1.3dev1
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/CHANGELOG.md +9 -3
- data/README.md +9 -6
- data/Rakefile +4 -4
- data/examples/client.EM.run.rb +8 -45
- data/examples/client.rb +17 -36
- data/examples/server.rb +16 -12
- data/lib/arachni/rpc/em/client.rb +37 -50
- data/lib/arachni/rpc/em/em.rb +1 -1
- data/lib/arachni/rpc/em/protocol.rb +33 -4
- data/lib/arachni/rpc/em/server.rb +41 -49
- data/lib/arachni/rpc/em/ssl.rb +8 -11
- data/lib/arachni/rpc/em/version.rb +1 -1
- data/spec/arachni/rpc/em/client_spec.rb +42 -29
- data/spec/pems/cacert.pem +35 -37
- data/spec/pems/client/cert.pem +35 -37
- data/spec/pems/client/key.pem +49 -49
- data/spec/pems/server/cert.pem +35 -37
- data/spec/pems/server/key.pem +49 -49
- data/spec/servers/with_fallback.rb +13 -0
- data/spec/servers/with_ssl_primitives.rb +4 -7
- data/spec/spec_helper.rb +1 -0
- metadata +13 -11
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
# ChangeLog
|
2
2
|
|
3
|
-
## Version 0.1.
|
3
|
+
## Version 0.1.3 (_Under development_)
|
4
4
|
|
5
|
-
-
|
6
|
-
-
|
5
|
+
- Stopped client callbacks from being deferred.
|
6
|
+
- Server now supports a fallback serializer to allow clients to use a secondary serializer if they so choose.
|
7
|
+
- Client request-retry strategy tweaked to be more resilient.
|
8
|
+
|
9
|
+
## Version 0.1.2
|
10
|
+
|
11
|
+
- Code cleanup.
|
12
|
+
- Client retries on Errno::ECONNREFUSED.
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
<table>
|
3
3
|
<tr>
|
4
4
|
<th>Version</th>
|
5
|
-
<td>0.1.
|
5
|
+
<td>0.1.3dev</td>
|
6
6
|
</tr>
|
7
7
|
<tr>
|
8
8
|
<th>Github page</th>
|
@@ -38,11 +38,14 @@ Arachni-RPC EM is an implementation of the <a href="http://github.com/Arachni/ar
|
|
38
38
|
|
39
39
|
It's capable of:
|
40
40
|
|
41
|
-
-
|
42
|
-
-
|
43
|
-
-
|
44
|
-
-
|
45
|
-
-
|
41
|
+
- Performing and handling a few thousand requests per second (depending on call size, network conditions and the like).
|
42
|
+
- Configurable retry-on-fail for requests.
|
43
|
+
- TLS encryption (with peer verification).
|
44
|
+
- Asynchronous and synchronous requests.
|
45
|
+
- Handling server-side asynchronous calls that require a block (or any method that passes its result to a block instead of returning it).
|
46
|
+
- Token-based authentication.
|
47
|
+
- Primary and secondary (fallback) serializers -- Server will expect the Client to use the primary serializer,
|
48
|
+
if the Request cannot be parsed using the primary one, it will revert to using the fallback to parse the Request and serialize the Response.
|
46
49
|
|
47
50
|
## Usage
|
48
51
|
|
data/Rakefile
CHANGED
@@ -19,7 +19,7 @@ rescue LoadError => e
|
|
19
19
|
puts ' gem install rspec'
|
20
20
|
end
|
21
21
|
|
22
|
-
task default: [ :spec ]
|
22
|
+
task default: [ :build, :spec ]
|
23
23
|
|
24
24
|
desc "Generate docs"
|
25
25
|
task :docs do
|
@@ -48,12 +48,12 @@ end
|
|
48
48
|
|
49
49
|
desc "Build and install the arachni gem."
|
50
50
|
task :install => [ :build ] do
|
51
|
-
|
52
51
|
sh "gem install arachni-rpc-em-#{Arachni::RPC::EM::VERSION}.gem"
|
53
52
|
end
|
54
53
|
|
55
|
-
desc "Push a new version to
|
54
|
+
desc "Push a new version to Rubygems"
|
56
55
|
task :publish => [ :build ] do
|
57
|
-
|
56
|
+
sh "git tag -a v#{Arachni::RPC::EM::VERSION} -m 'Version #{Arachni::RPC::EM::VERSION}'"
|
58
57
|
sh "gem push arachni-rpc-em-#{Arachni::RPC::EM::VERSION}.gem"
|
59
58
|
end
|
59
|
+
task :release => [ :publish ]
|
data/examples/client.EM.run.rb
CHANGED
@@ -19,19 +19,17 @@ require File.join( File.expand_path( File.dirname( __FILE__ ) ), '../lib/arachni
|
|
19
19
|
|
20
20
|
# connect to the server
|
21
21
|
client = Arachni::RPC::EM::Client.new(
|
22
|
-
:
|
23
|
-
:
|
22
|
+
host: 'localhost',
|
23
|
+
port: 7332,
|
24
24
|
|
25
25
|
# optional authentication token, if it doesn't match the one
|
26
26
|
# set on the server-side you'll be getting exceptions.
|
27
|
-
:
|
28
|
-
|
29
|
-
# :keep_alive => false,
|
27
|
+
token: 'superdupersecret',
|
30
28
|
|
31
29
|
# optional serializer (defaults to YAML)
|
32
30
|
# see the 'serializer' method at:
|
33
31
|
# http://eventmachine.rubyforge.org/EventMachine/Protocols/ObjectProtocol.html#M000369
|
34
|
-
:
|
32
|
+
serializer: Marshal
|
35
33
|
)
|
36
34
|
|
37
35
|
bench = Arachni::RPC::RemoteObjectMapper.new( client, 'bench' )
|
@@ -65,49 +63,14 @@ require File.join( File.expand_path( File.dirname( __FILE__ ) ), '../lib/arachni
|
|
65
63
|
end
|
66
64
|
|
67
65
|
# async calls are the same
|
68
|
-
bench.foo( 'This is an async call... business as usual. :)' )
|
69
|
-
|res|
|
66
|
+
bench.foo( 'This is an async call... business as usual. :)' ) do |res|
|
70
67
|
p res
|
71
68
|
# => "This is an async call... business as usual. :)"
|
72
|
-
|
69
|
+
end
|
73
70
|
|
74
|
-
bench.async_foo( 'This is an async call to an async remote method.' )
|
75
|
-
|res|
|
71
|
+
bench.async_foo( 'This is an async call to an async remote method.' ) do |res|
|
76
72
|
p res
|
77
73
|
# => "This is an async call to an async remote method."
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
#
|
82
|
-
# The system uses 2 methods to make calls appear sync:
|
83
|
-
# - Threads (when the code is *not* run directly inside the Reactor's thread, see example.rb)
|
84
|
-
# - Fibers (when the code *is* run inside the Reactor's thread, like right here)
|
85
|
-
#
|
86
|
-
# For performance reasons callbacks are ::EM.defer'ed.
|
87
|
-
#
|
88
|
-
# This means that they're already in their own thread so you don't need
|
89
|
-
# ::Arachni::RPC::EM::EM::Synchrony.run to perform sync calls from inside callbacks.
|
90
|
-
#
|
91
|
-
bench.async_foo( 'Coo-coo' ) {
|
92
|
-
|res|
|
93
|
-
|
94
|
-
p res
|
95
|
-
# => "Coo-coo"
|
96
|
-
|
97
|
-
p bench.async_foo( 'Coo-coo 2' )
|
98
|
-
# => "Coo-coo 2"
|
99
|
-
|
100
|
-
bench.async_foo( 'Coo-coo 3' ) {
|
101
|
-
|res|
|
102
|
-
|
103
|
-
p res
|
104
|
-
# => "Coo-coo 3"
|
105
|
-
|
106
|
-
p bench.foo( 'Coo-coo 4' )
|
107
|
-
# => "Coo-coo 4"
|
108
|
-
}
|
109
|
-
|
110
|
-
}
|
111
|
-
|
74
|
+
end
|
112
75
|
|
113
76
|
end
|
data/examples/client.rb
CHANGED
@@ -14,45 +14,30 @@ require File.join( cwd, '../lib/arachni/rpc/', 'em' )
|
|
14
14
|
|
15
15
|
# connect to the server
|
16
16
|
client = Arachni::RPC::EM::Client.new(
|
17
|
-
:
|
18
|
-
:
|
17
|
+
host: 'localhost',
|
18
|
+
port: 7332,
|
19
19
|
|
20
20
|
# optional authentication token, if it doesn't match the one
|
21
21
|
# set on the server-side you'll be getting exceptions.
|
22
|
-
:
|
22
|
+
token: 'superdupersecret',
|
23
23
|
|
24
24
|
# optional serializer (defaults to YAML)
|
25
25
|
# see the 'serializer' method at:
|
26
26
|
# http://eventmachine.rubyforge.org/EventMachine/Protocols/ObjectProtocol.html#M000369
|
27
|
-
:serializer => Marshal,
|
28
|
-
|
29
|
-
#
|
30
|
-
# Connection keep alive is set to true by default, this means that
|
31
|
-
# a single connection will be maintained and all calls will pass
|
32
|
-
# through it.
|
33
|
-
# This bypasses a bug in EventMachine and allows you to perform thousands
|
34
|
-
# of calls without issue.
|
35
|
-
#
|
36
|
-
# However, you are responsible for closing the connection when you're done.
|
37
|
-
#
|
38
|
-
# If keep alive is set to false then each call will go through its own connection
|
39
|
-
# and the responsibility for closing that connection falls on Arachni-RPC.
|
40
|
-
#
|
41
|
-
# Unfortunately, if you try to make a greater number of calls than your system's
|
42
|
-
# maximum open file descriptors limit EventMachine will freak-out.
|
43
27
|
#
|
44
|
-
|
28
|
+
# Will trigger the Server's fallback serializer.
|
29
|
+
serializer: YAML,
|
45
30
|
|
46
31
|
#
|
47
32
|
# In order to enable peer verification one must first provide
|
48
33
|
# the following:
|
49
34
|
#
|
50
35
|
# SSL CA certificate
|
51
|
-
# :
|
36
|
+
# ssl_ca: cwd + '/../spec/pems/cacert.pem',
|
52
37
|
# SSL private key
|
53
|
-
# :
|
38
|
+
# ssl_pkey: cwd + '/../spec/pems/client/key.pem',
|
54
39
|
# SSL certificate
|
55
|
-
# :
|
40
|
+
# ssl_cert: cwd + '/../spec/pems/client/cert.pem'
|
56
41
|
)
|
57
42
|
|
58
43
|
# Make things easy on the eyes using the mapper, it allows you to do this:
|
@@ -69,9 +54,7 @@ bench = Arachni::RPC::RemoteObjectMapper.new( client, 'bench' )
|
|
69
54
|
# In order to perform an asynchronous call you will need to provide a block,
|
70
55
|
# even if it is an empty one.
|
71
56
|
#
|
72
|
-
bench.foo( 'This is an async call to "bench.foo".' )
|
73
|
-
|res|
|
74
|
-
|
57
|
+
bench.foo( 'This is an async call to "bench.foo".' ) do |res|
|
75
58
|
p res
|
76
59
|
# => "This is an async call to \"bench.foo\"."
|
77
60
|
|
@@ -98,7 +81,7 @@ bench.foo( 'This is an async call to "bench.foo".' ) {
|
|
98
81
|
# was there an authentication token mismatch?
|
99
82
|
# p res.rpc_invalid_token_error?
|
100
83
|
# => false
|
101
|
-
|
84
|
+
end
|
102
85
|
|
103
86
|
|
104
87
|
|
@@ -108,11 +91,10 @@ bench.foo( 'This is an async call to "bench.foo".' ) {
|
|
108
91
|
# You'll need to kind-of specify the async methods on the server-side,
|
109
92
|
# check the server example file for more info.
|
110
93
|
#
|
111
|
-
bench.async_foo( 'This is an async call to "bench.async_foo".' )
|
112
|
-
|res|
|
94
|
+
bench.async_foo( 'This is an async call to "bench.async_foo".' ) do |res|
|
113
95
|
p res
|
114
96
|
# => "This is an async call to \"bench.async_foo\"."
|
115
|
-
|
97
|
+
end
|
116
98
|
|
117
99
|
p bench.async_foo( 'This is a sync call to "bench.async_foo".' )
|
118
100
|
# => "This is a sync call to \"bench.async_foo\"."
|
@@ -141,7 +123,7 @@ p bench.foo( 'This is a sync call to "bench.foo".' )
|
|
141
123
|
blah = Arachni::RPC::RemoteObjectMapper.new( client, 'blah' )
|
142
124
|
begin
|
143
125
|
p blah.something
|
144
|
-
rescue
|
126
|
+
rescue => e
|
145
127
|
p e # => #<Arachni::RPC::EM::Exceptions::InvalidObject: Trying to access non-existent object 'blah'.>
|
146
128
|
end
|
147
129
|
|
@@ -150,7 +132,7 @@ end
|
|
150
132
|
#
|
151
133
|
begin
|
152
134
|
p bench.fdoo
|
153
|
-
rescue
|
135
|
+
rescue => e
|
154
136
|
p e # => #<Arachni::RPC::EM::Exceptions::InvalidMethod: Trying to access non-public method 'fdoo'.>
|
155
137
|
end
|
156
138
|
|
@@ -161,8 +143,7 @@ end
|
|
161
143
|
# It will *NOT* be thrown!
|
162
144
|
# It will be *RETURNED*!
|
163
145
|
#
|
164
|
-
blah.something
|
165
|
-
|res|
|
146
|
+
blah.something do |res|
|
166
147
|
p res # => #<Arachni::RPC::EM::Exceptions::InvalidObject: Trying to access non-existent object 'blah'.>
|
167
148
|
|
168
149
|
# RPC Exception helper methods have been added to all Ruby objects (except BasicObject)
|
@@ -170,12 +151,12 @@ blah.something {
|
|
170
151
|
|
171
152
|
# p res.rpc_exception? # => true
|
172
153
|
# p res.rpc_invalid_object_error? # => true
|
173
|
-
|
154
|
+
end
|
174
155
|
|
175
156
|
#
|
176
157
|
# We don't know when async calls will return so we wait forever.
|
177
158
|
#
|
178
159
|
# Call ::EM.stop to break-out.
|
179
160
|
#
|
180
|
-
Arachni::RPC::EM.block
|
161
|
+
Arachni::RPC::EM.block
|
181
162
|
|
data/examples/server.rb
CHANGED
@@ -13,7 +13,7 @@ require File.join( cwd, '../lib/arachni/rpc/', 'em' )
|
|
13
13
|
|
14
14
|
class Parent
|
15
15
|
def foo( arg )
|
16
|
-
|
16
|
+
arg
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -38,22 +38,27 @@ class Bench < Parent
|
|
38
38
|
end
|
39
39
|
|
40
40
|
server = Arachni::RPC::EM::Server.new(
|
41
|
-
:
|
42
|
-
:
|
41
|
+
host: 'localhost',
|
42
|
+
port: 7332,
|
43
43
|
|
44
44
|
# optional authentication token, if it doesn't match the one
|
45
45
|
# set on the client-side the client won't be able to do anything
|
46
46
|
# and keep getting exceptions.
|
47
|
-
:
|
47
|
+
token: 'superdupersecret',
|
48
48
|
|
49
49
|
# optional serializer (defaults to YAML)
|
50
50
|
# see the 'serializer' method at:
|
51
51
|
# http://eventmachine.rubyforge.org/EventMachine/Protocols/ObjectProtocol.html#M000369
|
52
|
-
|
52
|
+
#
|
53
|
+
# Use Marshal for performance as the primary serializer.
|
54
|
+
serializer: Marshal,
|
55
|
+
|
56
|
+
# Fallback to YAML for interoperability -- used for requests that can't be parsed by Marshal.load.
|
57
|
+
fallback_serializer: YAML,
|
53
58
|
|
54
|
-
# :
|
55
|
-
# :
|
56
|
-
# :
|
59
|
+
# ssl_ca: cwd + '/../spec/pems/cacert.pem',
|
60
|
+
# ssl_pkey: cwd + '/../spec/pems/server/key.pem',
|
61
|
+
# ssl_cert: cwd + '/../spec/pems/server/cert.pem'
|
57
62
|
)
|
58
63
|
|
59
64
|
#
|
@@ -64,15 +69,14 @@ server = Arachni::RPC::EM::Server.new(
|
|
64
69
|
# you can just decide dynamically based on a plethora of data which Ruby provides
|
65
70
|
# by its 'Method' class.
|
66
71
|
#
|
67
|
-
server.add_async_check
|
68
|
-
|method|
|
72
|
+
server.add_async_check do |method|
|
69
73
|
#
|
70
74
|
# Must return 'true' for async and 'false' for sync.
|
71
75
|
#
|
72
76
|
# Very simple check here...
|
73
77
|
#
|
74
|
-
|
75
|
-
|
78
|
+
method.name.to_s.start_with? 'async_'
|
79
|
+
end
|
76
80
|
|
77
81
|
server.add_handler( 'bench', Bench.new )
|
78
82
|
|
@@ -14,7 +14,8 @@ module EM
|
|
14
14
|
# Simple EventMachine-based RPC client.
|
15
15
|
#
|
16
16
|
# It's capable of:
|
17
|
-
# - performing and handling a few thousands requests per second (depending on
|
17
|
+
# - performing and handling a few thousands requests per second (depending on
|
18
|
+
# call size, network conditions and the like)
|
18
19
|
# - TLS encryption
|
19
20
|
# - asynchronous and synchronous requests
|
20
21
|
# - handling remote asynchronous calls that require a block
|
@@ -39,10 +40,12 @@ class Client
|
|
39
40
|
DEFAULT_TRIES = 9
|
40
41
|
|
41
42
|
def initialize( opts )
|
42
|
-
@opts = opts
|
43
|
+
@opts = opts.dup
|
44
|
+
|
43
45
|
@max_retries = @opts[:max_retries] || DEFAULT_TRIES
|
46
|
+
|
44
47
|
@opts[:tries] ||= 0
|
45
|
-
@tries
|
48
|
+
@tries ||= @opts[:tries]
|
46
49
|
|
47
50
|
@status = :idle
|
48
51
|
|
@@ -58,14 +61,15 @@ class Client
|
|
58
61
|
def unbind( reason )
|
59
62
|
end_ssl
|
60
63
|
|
61
|
-
if @request && @request.callback &&
|
62
|
-
if reason == Errno::ECONNREFUSED
|
64
|
+
if @request && @request.callback && !done?
|
65
|
+
if retry? #&& reason == Errno::ECONNREFUSED
|
63
66
|
retry_request
|
64
67
|
else
|
65
68
|
e = Arachni::RPC::Exceptions::ConnectionError.new( "Connection closed [#{reason}]" )
|
66
69
|
@request.callback.call( e )
|
67
70
|
end
|
68
71
|
end
|
72
|
+
|
69
73
|
@status = :closed
|
70
74
|
end
|
71
75
|
|
@@ -77,42 +81,37 @@ class Client
|
|
77
81
|
@status
|
78
82
|
end
|
79
83
|
|
84
|
+
def done?
|
85
|
+
!!@done
|
86
|
+
end
|
87
|
+
|
80
88
|
#
|
81
89
|
# Used to handle responses.
|
82
90
|
#
|
83
91
|
# @param [Arachni::RPC::EM::Response] res
|
84
92
|
#
|
85
93
|
def receive_response( res )
|
86
|
-
@status = :done
|
87
|
-
|
88
94
|
if exception?( res )
|
89
95
|
res.obj = Arachni::RPC::Exceptions.from_response( res )
|
90
96
|
end
|
91
97
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
close_connection
|
98
|
-
end
|
99
|
-
|
100
|
-
if @request.defer?
|
101
|
-
# the callback might block a bit so tell EM to put it in a thread
|
102
|
-
::EM.defer { callback.call( res.obj ) }
|
103
|
-
else
|
104
|
-
callback.call( res.obj )
|
105
|
-
end
|
106
|
-
end
|
98
|
+
@request.callback.call( res.obj ) if @request.callback
|
99
|
+
ensure
|
100
|
+
@done = true
|
101
|
+
@status = :done
|
102
|
+
close_connection
|
107
103
|
end
|
108
104
|
|
109
105
|
def retry_request
|
110
106
|
opts = @opts.dup
|
111
107
|
opts[:tries] += 1
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
::EM.
|
108
|
+
|
109
|
+
@tries += 1
|
110
|
+
::EM.next_tick {
|
111
|
+
::EM::Timer.new( 0.2 ) {
|
112
|
+
::EM.connect( opts[:host], opts[:port], self.class, opts ).
|
113
|
+
send_request( @request )
|
114
|
+
}
|
116
115
|
}
|
117
116
|
end
|
118
117
|
|
@@ -131,9 +130,9 @@ class Client
|
|
131
130
|
# @param [Arachni::RPC::EM::Request] req
|
132
131
|
#
|
133
132
|
def send_request( req )
|
134
|
-
@status = :pending
|
135
133
|
@request = req
|
136
134
|
super( req )
|
135
|
+
@status = :pending
|
137
136
|
end
|
138
137
|
end
|
139
138
|
|
@@ -162,6 +161,9 @@ class Client
|
|
162
161
|
# # http://eventmachine.rubyforge.org/EventMachine/Protocols/ObjectProtocol.html#M000369
|
163
162
|
# :serializer => Marshal,
|
164
163
|
#
|
164
|
+
# # serializer to use if the first choice fails
|
165
|
+
# :fallback_serializer => YAML,
|
166
|
+
#
|
165
167
|
# :max_retries => 0,
|
166
168
|
#
|
167
169
|
# #
|
@@ -179,18 +181,12 @@ class Client
|
|
179
181
|
# @param [Hash] opts
|
180
182
|
#
|
181
183
|
def initialize( opts )
|
182
|
-
|
183
|
-
|
184
|
-
@token = @opts[:token]
|
184
|
+
@opts = opts.merge( role: :client )
|
185
|
+
@token = @opts[:token]
|
185
186
|
|
186
|
-
|
187
|
+
@host, @port = @opts[:host], @opts[:port].to_i
|
187
188
|
|
188
|
-
|
189
|
-
rescue EventMachine::ConnectionError => e
|
190
|
-
exc = ConnectionError.new( e.to_s + " for '#{@k}'." )
|
191
|
-
exc.set_backtrace( e.backtrace )
|
192
|
-
raise exc
|
193
|
-
end
|
189
|
+
Arachni::RPC::EM.ensure_em_running
|
194
190
|
end
|
195
191
|
|
196
192
|
#
|
@@ -201,10 +197,9 @@ class Client
|
|
201
197
|
# To perform an async call you need to provide a block which will be passed
|
202
198
|
# the return value once the method has finished executing.
|
203
199
|
#
|
204
|
-
# server.call( 'handler.method', arg1, arg2 )
|
205
|
-
# |res|
|
200
|
+
# server.call( 'handler.method', arg1, arg2 ) do |res|
|
206
201
|
# do_stuff( res )
|
207
|
-
#
|
202
|
+
# end
|
208
203
|
#
|
209
204
|
#
|
210
205
|
# To perform a sync (blocking) call do not pass a block, the value will be
|
@@ -224,11 +219,7 @@ class Client
|
|
224
219
|
token: @token
|
225
220
|
)
|
226
221
|
|
227
|
-
|
228
|
-
call_async( req )
|
229
|
-
else
|
230
|
-
call_sync( req )
|
231
|
-
end
|
222
|
+
block_given? ? call_async( req ) : call_sync( req )
|
232
223
|
end
|
233
224
|
|
234
225
|
private
|
@@ -238,7 +229,7 @@ class Client
|
|
238
229
|
end
|
239
230
|
|
240
231
|
def call_async( req, &block )
|
241
|
-
::EM.
|
232
|
+
::EM.next_tick {
|
242
233
|
req.callback = block if block_given?
|
243
234
|
connect.send_request( req )
|
244
235
|
}
|
@@ -257,10 +248,6 @@ class Client
|
|
257
248
|
end
|
258
249
|
sleep
|
259
250
|
else
|
260
|
-
# Fibers do not work across threads so don't defer the callback
|
261
|
-
# once the Handler gets to it
|
262
|
-
req.do_not_defer
|
263
|
-
|
264
251
|
f = Fiber.current
|
265
252
|
call_async( req ) { |obj| f.resume( obj ) }
|
266
253
|
|