bran 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 36a615f86cbc15a2b7c138816df60678ca2a0786
4
- data.tar.gz: 48570f692b614243db475256f19265f74993be70
3
+ metadata.gz: 28390755dc30a2dc1027f082f7f5340d88222bb8
4
+ data.tar.gz: 32514e834d6f931cab276f2480d45be6588e3982
5
5
  SHA512:
6
- metadata.gz: c14a66422d3310bc2619afb7c59e883c6c9ec6022b7e9139b7800b2a5aa3ca1114cab48ce76d4bce553ffbcab647de60c5604dfbc8f4f6159649ea3a18c31e6e
7
- data.tar.gz: 2ba3d09c4a6786b300e3c4c96030ed692fdc60a880c539a396efc42ba081eb66c190529e4c9bc217d12fd415399b1bacc1088b9fe5b0261f6f616fcb17ab21e8
6
+ metadata.gz: 52c90e1f8c60ccc4607da7af594e30366732c1a97778b320749f09f5feced266b4169bb5e1320621fc7a8a72eef7aee3f1edc6ddac2bd85b392e3e33024e7aa6
7
+ data.tar.gz: 86e450e1c22aacd177e02e4331f71100de0761912361304d63eb9bd579327a05a4874202c3caccc86d085384959d7512c59b30ad52bb783f9d6c41e62852e1e1
data/README.md CHANGED
@@ -18,6 +18,35 @@ Bran::Ext.check_assumptions = true
18
18
 
19
19
  The description of each Bran integration will also be labeled "Tested with" for known good combinations of dependent gems.
20
20
 
21
+ ### IO, TCPServer
22
+
23
+ Use Bran with any pure-Ruby library or application that uses plain IO (such as TCPSocket and TCPServer). All blocking `read` operations on IO, `IO.select`, and `TCPServer#accept` will be patched to yield their fiber instead of blocking.
24
+
25
+ Without any other Bran integrations, this requires manually running your own concurrent fibers of execution, scoped with a thread-local fiber manager. If no fiber manager is present, the methods will function as normal (without Bran). It's easier to use with another application-wide integration (like the Rainbows integration) that manages concurrent fibers for you.
26
+
27
+ To activate the Bran integrations, simply load the corresponding files:
28
+
29
+ ```ruby
30
+ require "bran/ext/io"
31
+ require "bran/ext/tcp_server"
32
+ ```
33
+
34
+ ### Ethon (Typhoeus)
35
+
36
+ Use Bran with any library or application that uses [Ethon][ethon] to perform HTTP requests (for example, [Typhoeus][typhoeus]). Both `Ethon::Easy#perform` and `Ethon::Multi#perform` will be patched to yield their fiber instead of blocking while waiting for the HTTP response.
37
+
38
+ Without any other Bran integrations, this requires manually running your own concurrent fibers of execution, scoped with a thread-local fiber manager. If no fiber manager is present, the methods will function as normal (without Bran). It's easier to use with another application-wide integration (like the Rainbows integration) that manages concurrent fibers for you.
39
+
40
+ To activate the Bran integration, simply load the corresponding file:
41
+
42
+ ```ruby
43
+ require "bran/ext/ethon"
44
+ ```
45
+
46
+ Tested with:
47
+
48
+ - `ethon 0.8.1`
49
+
21
50
  ### Rainbows
22
51
 
23
52
  Use Bran with [Rainbows][rainbows], a [Unicorn][unicorn]-based and [Unicorn][unicorn]-compatible webserver from the creators of [Unicorn][unicorn].
@@ -36,8 +65,11 @@ end
36
65
  ```
37
66
 
38
67
  Tested with:
68
+
39
69
  - `rainbows 5.0.0` (`unicorn 5.0.0`, `kgio 2.10.0`)
40
70
  - `rainbows 5.0.0` (`unicorn 5.0.1`, `kgio 2.10.0`)
41
71
 
72
+ [ethon]: https://github.com/typhoeus/ethon
73
+ [typhoeus]: https://github.com/typhoeus/typhoeus
42
74
  [rainbows]: http://rainbows.bogomips.org/
43
75
  [unicorn]: http://unicorn.bogomips.org/
@@ -0,0 +1,11 @@
1
+
2
+ require_relative "../../bran"
3
+ require_relative "../../bran/ext"
4
+
5
+ ::Bran::Ext[:ethon] = true
6
+
7
+ require "ethon"
8
+
9
+ require_relative "ethon/curl"
10
+ require_relative "ethon/multi"
11
+ require_relative "ethon/easy"
@@ -0,0 +1,21 @@
1
+
2
+ Ethon::Curl.ffi_lib ["libcurl", "libcurl.so.4"]
3
+
4
+ Ethon::Curl.attach_function :multi_socket_action, :curl_multi_socket_action,
5
+ [:pointer, :int, :int, :pointer], :multi_code, blocking: true
6
+
7
+ module Ethon
8
+ module Curl
9
+ POLL_NONE = 0
10
+ POLL_IN = 1
11
+ POLL_OUT = 2
12
+ POLL_INOUT = 3
13
+ POLL_REMOVE = 4
14
+
15
+ CSELECT_IN = 0x01
16
+ CSELECT_OUT = 0x02
17
+ CSELECT_ERR = 0x04
18
+
19
+ SOCKET_TIMEOUT = -1
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+
2
+ Module.new do
3
+ Ethon::Easy.prepend self
4
+
5
+ def perform
6
+ fm = Thread.current.thread_variable_get(:fiber_manager)
7
+ return super unless fm
8
+
9
+ multi = Ethon::Multi.new
10
+ multi.add self
11
+ multi.perform
12
+
13
+ return_code
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+
2
+ require_relative "multi/bran"
3
+
4
+ Module.new do
5
+ Ethon::Multi.prepend self
6
+
7
+ def initialize(*)
8
+ super
9
+
10
+ # Create a bran performer that can steal work from this multi.
11
+ # Fall back to normal path (no bran) if not under fiber management.
12
+ fm = Thread.current.thread_variable_get(:fiber_manager)
13
+ @bran = Ethon::Multi::Bran.new(fm, self) if fm
14
+ end
15
+
16
+ def perform
17
+ # Use the bran performer instead of the normal path to perform this multi.
18
+ # Fall back to normal path (no bran) if not under fiber management,
19
+ # or if it fails because we are not under the same fiber manager as before.
20
+ (@bran && @bran.perform) || super
21
+
22
+ nil
23
+ end
24
+ end
@@ -0,0 +1,142 @@
1
+
2
+ ::Bran::Ext.assume do
3
+ check ::Ethon::Multi < ::Ethon::Multi::Operations
4
+ check ::Ethon::Multi::Operations.instance_method(:handle).arity == 0
5
+ check ::Ethon::Multi::Operations.instance_method(:check).arity == 0
6
+
7
+ check ::Ethon::Multi < ::Ethon::Multi::Stack
8
+ check ::Ethon::Multi::Stack.instance_method(:easy_handles).arity == 0
9
+
10
+ check ::Ethon::Multi < ::Ethon::Multi::Options
11
+ check ::Ethon::Multi::Options.instance_method(:socketfunction=).arity == 1
12
+ check ::Ethon::Multi::Options.instance_method(:timerfunction=).arity == 1
13
+
14
+ check ::Ethon::Errors::MultiTimeout.instance_method(:initialize).arity == 1
15
+
16
+ check ::Ethon::Curl.method(:multi_socket_action)
17
+ check ::Ethon::Curl::POLL_IN
18
+ check ::Ethon::Curl::POLL_OUT
19
+ check ::Ethon::Curl::POLL_INOUT
20
+ check ::Ethon::Curl::POLL_REMOVE
21
+ check ::Ethon::Curl::SOCKET_TIMEOUT
22
+ check ::Ethon::Curl::CSELECT_IN
23
+ check ::Ethon::Curl::CSELECT_OUT
24
+ end
25
+
26
+ module Ethon
27
+
28
+ class Multi
29
+ class Bran
30
+ def initialize(fm, multi)
31
+ @fm = fm
32
+ @multi = multi
33
+
34
+ @multi.socketfunction = method(:_socketfunction_callback).to_proc
35
+ @multi.timerfunction = method(:_timerfunction_callback).to_proc
36
+ end
37
+
38
+ def perform
39
+ fm = Thread.current.thread_variable_get(:fiber_manager)
40
+ return unless fm == @fm # abort if not run within the same context.
41
+
42
+ @curl_readables = {}
43
+ @curl_writables = {}
44
+ @fds_remaining_ptr = ::FFI::MemoryPointer.new(:int)
45
+
46
+ raise @callback_error if @callback_error
47
+
48
+ @perform_fiber = ::Fiber.current
49
+ ::Fiber.yield
50
+ @perform_fiber = nil
51
+
52
+ raise @callback_error if @callback_error
53
+
54
+ true
55
+ end
56
+
57
+ private
58
+
59
+ def _socketfunction_callback(easy_handle, fd, action, *)
60
+ case action
61
+ when ::Ethon::Curl::POLL_IN then poll_in(fd)
62
+ when ::Ethon::Curl::POLL_OUT then poll_out(fd)
63
+ when ::Ethon::Curl::POLL_INOUT then poll_in(fd); poll_out(fd)
64
+ when ::Ethon::Curl::POLL_REMOVE then poll_remove(fd)
65
+ end
66
+
67
+ 0
68
+ rescue Exception => e
69
+ @callback_error = e
70
+ @fm.resume_soon(@perform_fiber) if @perform_fiber
71
+
72
+ 0
73
+ end
74
+
75
+ def _timerfunction_callback(_, timeout_ms, *)
76
+ timer_cancel
77
+
78
+ return if timeout_ms.is_a?(::Bignum) # corresponds to -1
79
+
80
+ @timeout_timer = @fm.loop.timer_oneshot timeout_ms / 1000.0 do
81
+ @timeout_timer = nil
82
+
83
+ socket_action!(::Ethon::Curl::SOCKET_TIMEOUT, 0) if @perform_fiber
84
+ end
85
+
86
+ 0
87
+ rescue Exception => e
88
+ @callback_error = e
89
+ @fm.resume_soon(@perform_fiber) if @perform_fiber
90
+
91
+ 0
92
+ end
93
+
94
+ def poll_in(fd)
95
+ return if @curl_readables[fd]
96
+ @curl_readables[fd] = true
97
+
98
+ @fm.loop.push_readable fd,
99
+ Proc.new { socket_action!(fd, ::Ethon::Curl::CSELECT_IN) }
100
+ end
101
+
102
+ def poll_out(fd)
103
+ return if @curl_writables[fd]
104
+ @curl_writables[fd] = true
105
+
106
+ @fm.loop.push_writable fd,
107
+ Proc.new { socket_action!(fd, ::Ethon::Curl::CSELECT_OUT) }
108
+ end
109
+
110
+ def poll_remove(fd)
111
+ @fm.loop.pop_readable(fd) if @curl_readables.delete(fd)
112
+ @fm.loop.pop_writable(fd) if @curl_writables.delete(fd)
113
+
114
+ maybe_finish!
115
+ end
116
+
117
+ def timer_cancel
118
+ @fm.loop.timer_cancel @timeout_timer if @timeout_timer
119
+ end
120
+
121
+ def socket_action!(fd, flags)
122
+ code = ::Ethon::Curl.multi_socket_action(@multi.handle, fd, flags,
123
+ @fds_remaining_ptr)
124
+ raise ::Ethon::Errors::MultiTimeout.new(code) unless code == :ok
125
+
126
+ if @fds_remaining_ptr.read_int == 0
127
+ @multi.__send__(:check)
128
+ maybe_finish!
129
+ end
130
+ end
131
+
132
+ def maybe_finish!
133
+ return unless @curl_readables.empty? && @curl_writables.empty? \
134
+ && @multi.easy_handles.empty?
135
+ timer_cancel
136
+
137
+ @perform_fiber.resume
138
+ end
139
+
140
+ end
141
+ end
142
+ end
@@ -39,5 +39,9 @@ module Bran
39
39
  ::Fiber.yield
40
40
  end
41
41
 
42
+ def resume_soon(fiber)
43
+ @loop.timer_oneshot_wake(0, fiber)
44
+ end
45
+
42
46
  end
43
47
  end
@@ -30,6 +30,7 @@ module Bran
30
30
 
31
31
  @poll_read_callback = FFI.uv_poll_cb(&method(:_poll_read_callback))
32
32
  @poll_write_callback = FFI.uv_poll_cb(&method(:_poll_write_callback))
33
+ @poll_rw_callback = FFI.uv_poll_cb(&method(:_poll_rw_callback))
33
34
 
34
35
  # TODO: add more Ruby-compatible signal handlers by default?
35
36
  signal_start(:INT) { @abort_signal = :INT; stop! }
@@ -103,54 +104,64 @@ module Bran
103
104
  # Push the given handler for the given fd, adding if necessary.
104
105
  # If persistent is false, the handler will be popped after one trigger.
105
106
  def push_readable(fd, handler, persistent = true)
106
- ptr = ptr()
107
- fd = Integer(fd)
107
+ ptr = ptr()
108
+ fd = Integer(fd)
109
+ poll = nil
108
110
 
109
- # TODO: worry about readable vs writable mixing/overlap
110
111
  if (readables = @on_readables[fd])
111
112
  readables << [handler, persistent]
112
- return
113
+ elsif (poll = @running_writes[fd])
114
+ @running_reads[fd] = poll
115
+ @fds_by_read_addr[poll.address] = fd
116
+ @on_readables[fd] = [[handler, persistent]]
117
+
118
+ Util.error_check "starting the poll readable + writable entry",
119
+ FFI.uv_poll_start(poll, FFI::UV_READABLE | FFI::UV_WRITABLE, @poll_rw_callback)
120
+ else
121
+ poll = @available_polls.pop || FFI.uv_poll_alloc
122
+ @running_reads[fd] = poll
123
+ @fds_by_read_addr[poll.address] = fd
124
+ @on_readables[fd] = [[handler, persistent]]
125
+
126
+ Util.error_check "creating the poll readable entry",
127
+ FFI.uv_poll_init(ptr, poll, fd)
128
+
129
+ Util.error_check "starting the poll readable entry",
130
+ FFI.uv_poll_start(poll, FFI::UV_READABLE, @poll_read_callback)
113
131
  end
114
132
 
115
- poll = @available_polls.pop || FFI.uv_poll_alloc
116
- @running_reads[fd] = poll
117
- @fds_by_read_addr[poll.address] = fd
118
- @on_readables[fd] = [[handler, persistent]]
119
-
120
- # TODO: investigate if need not init existing available_polls
121
- Util.error_check "creating the poll readable entry",
122
- FFI.uv_poll_init(ptr, poll, fd)
123
-
124
- Util.error_check "starting the poll readable entry",
125
- FFI.uv_poll_start(poll, FFI::UV_READABLE, @poll_read_callback)
126
-
127
133
  fd
128
134
  end
129
135
 
130
136
  # Push the given handler for the given fd, adding if necessary.
131
137
  # If persistent is false, the handler will be popped after one trigger.
132
138
  def push_writable(fd, handler, persistent = true)
133
- ptr = ptr()
134
- fd = Integer(fd)
139
+ ptr = ptr()
140
+ fd = Integer(fd)
141
+ poll = nil
135
142
 
136
- # TODO: worry about writable vs writable mixing/overlap
137
143
  if (writables = @on_writables[fd])
138
144
  writables << [handler, persistent]
139
- return
145
+ elsif (poll = @running_reads[fd])
146
+ @running_writes[fd] = poll
147
+ @fds_by_write_addr[poll.address] = fd
148
+ @on_writables[fd] = [[handler, persistent]]
149
+
150
+ Util.error_check "starting the poll readable + writable entry",
151
+ FFI.uv_poll_start(poll, FFI::UV_READABLE | FFI::UV_WRITABLE, @poll_rw_callback)
152
+ else
153
+ poll = @available_polls.pop || FFI.uv_poll_alloc
154
+ @running_writes[fd] = poll
155
+ @fds_by_write_addr[poll.address] = fd
156
+ @on_writables[fd] = [[handler, persistent]]
157
+
158
+ Util.error_check "creating the poll writeable entry",
159
+ FFI.uv_poll_init(ptr, poll, fd)
160
+
161
+ Util.error_check "starting the poll writeable entry",
162
+ FFI.uv_poll_start(poll, FFI::UV_WRITABLE, @poll_write_callback)
140
163
  end
141
164
 
142
- poll = @available_polls.pop || FFI.uv_poll_alloc
143
- @running_writes[fd] = poll
144
- @fds_by_write_addr[poll.address] = fd
145
- @on_writables[fd] = [[handler, persistent]]
146
-
147
- # TODO: investigate if need not init existing available_polls
148
- Util.error_check "creating the poll writable entry",
149
- FFI.uv_poll_init(ptr, poll, fd)
150
-
151
- Util.error_check "starting the poll writable entry",
152
- FFI.uv_poll_start(poll, FFI::UV_WRITABLE, @poll_write_callback)
153
-
154
165
  fd
155
166
  end
156
167
 
@@ -160,7 +171,7 @@ module Bran
160
171
 
161
172
  readables = @on_readables[fd]
162
173
  return unless readables
163
-
174
+
164
175
  readables.pop
165
176
  return unless readables.empty?
166
177
 
@@ -171,6 +182,13 @@ module Bran
171
182
  Util.error_check "stopping the poll readable entry",
172
183
  FFI.uv_poll_stop(poll)
173
184
 
185
+ if poll == @running_writes[fd]
186
+ Util.error_check "restarting the poll writable entry",
187
+ FFI.uv_poll_start(poll, FFI::UV_WRITABLE, @poll_write_callback)
188
+
189
+ return
190
+ end
191
+
174
192
  @available_polls << poll
175
193
 
176
194
  nil
@@ -193,6 +211,13 @@ module Bran
193
211
  Util.error_check "stopping the poll writable entry",
194
212
  FFI.uv_poll_stop(poll)
195
213
 
214
+ if poll == @running_reads[fd]
215
+ Util.error_check "restarting the poll readable entry",
216
+ FFI.uv_poll_start(poll, FFI::UV_READABLE, @poll_read_callback)
217
+
218
+ return
219
+ end
220
+
196
221
  @available_polls << poll
197
222
 
198
223
  nil
@@ -314,7 +339,9 @@ module Bran
314
339
  # Callback method called directly from FFI when an event is readable.
315
340
  def _poll_read_callback(poll, rc, events)
316
341
  rescue_abort do
317
- fd = @fds_by_read_addr.fetch(poll.address)
342
+ fd = @fds_by_read_addr[poll.address]
343
+ return unless fd
344
+
318
345
  readables = @on_readables.fetch(fd)
319
346
 
320
347
  handler, persistent = readables.last
@@ -327,7 +354,9 @@ module Bran
327
354
  # Callback method called directly from FFI when an event is writable.
328
355
  def _poll_write_callback(poll, rc, events)
329
356
  rescue_abort do
330
- fd = @fds_by_write_addr.fetch(poll.address)
357
+ fd = @fds_by_write_addr[poll.address]
358
+ return unless fd
359
+
331
360
  writables = @on_writables.fetch(fd)
332
361
 
333
362
  handler, persistent = writables.last
@@ -337,6 +366,12 @@ module Bran
337
366
  end
338
367
  end
339
368
 
369
+ # Callback method called directly from FFI when an event is readable or writable.
370
+ def _poll_rw_callback(poll, rc, events)
371
+ _poll_read_callback(poll, rc, events) if events & FFI::UV_READABLE != 0
372
+ _poll_write_callback(poll, rc, events) if events & FFI::UV_WRITABLE != 0
373
+ end
374
+
340
375
  # Invoke the given handler, possibly converting the given rc to an error.
341
376
  def invoke_handler(handler, rc)
342
377
  case handler
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bran
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe McIlvain
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-03 00:00:00.000000000 Z
11
+ date: 2016-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -125,6 +125,11 @@ files:
125
125
  - ext/libuv/Rakefile
126
126
  - lib/bran.rb
127
127
  - lib/bran/ext.rb
128
+ - lib/bran/ext/ethon.rb
129
+ - lib/bran/ext/ethon/curl.rb
130
+ - lib/bran/ext/ethon/easy.rb
131
+ - lib/bran/ext/ethon/multi.rb
132
+ - lib/bran/ext/ethon/multi/bran.rb
128
133
  - lib/bran/ext/io.rb
129
134
  - lib/bran/ext/rainbows.rb
130
135
  - lib/bran/ext/rainbows/bran.rb