bran 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 36a615f86cbc15a2b7c138816df60678ca2a0786
4
+ data.tar.gz: 48570f692b614243db475256f19265f74993be70
5
+ SHA512:
6
+ metadata.gz: c14a66422d3310bc2619afb7c59e883c6c9ec6022b7e9139b7800b2a5aa3ca1114cab48ce76d4bce553ffbcab647de60c5604dfbc8f4f6159649ea3a18c31e6e
7
+ data.tar.gz: 2ba3d09c4a6786b300e3c4c96030ed692fdc60a880c539a396efc42ba081eb66c190529e4c9bc217d12fd415399b1bacc1088b9fe5b0261f6f616fcb17ab21e8
@@ -0,0 +1,43 @@
1
+ # Bran
2
+
3
+ ## Integrations
4
+
5
+ Bran is most useful when integrated with every part of your application that performs I/O. Because I/O is commonly abstracted away from our applications by libraries, Bran includes integrations and patches for various third-party libraries so that they can use Bran to yield control to other Fibers while waiting for I/O.
6
+
7
+ It's important to realize that some of these integrations may depend on "implementation details" of the libraries that they patch. Some libraries do not expose the public interfaces we need to integrate Bran with their I/O path, and some libraries do not even make it _clear_ which interfaces are public API and which interfaces are implementation details.
8
+
9
+ Bran integrations try to use only public interfaces wherever possible, and will otherwise try to make explicit assumptions about the interfaces they expect. Some of these assumptions can be checked at runtime when loading the integrations (attempt to "fail eagerly" in the case of poor integration). To enable this behavior, do the following before loading any Bran integration:
10
+
11
+ ```ruby
12
+ require "bran/ext"
13
+
14
+ Bran::Ext.check_assumptions = true
15
+
16
+ # Load specific Bran integrations here.
17
+ ```
18
+
19
+ The description of each Bran integration will also be labeled "Tested with" for known good combinations of dependent gems.
20
+
21
+ ### Rainbows
22
+
23
+ Use Bran with [Rainbows][rainbows], a [Unicorn][unicorn]-based and [Unicorn][unicorn]-compatible webserver from the creators of [Unicorn][unicorn].
24
+
25
+ Each worker process will allow multiple concurrent connections, accepted by the Bran adapter into a fixed-size fiber pool, with the fibers being managed by Bran. Without any other Bran integrations, you can expect this configuration to only process one request at a time, with performance on par with that of Unicorn. Using other Bran I/O integrations while handling requests will give the fibers a chance to work concurrently, increasing performance for I/O-bound loads.
26
+
27
+ To activate the Bran integration, try adding the following example configuration to your Unicorn/Rainbows config file (often called `unicorn.rb`):
28
+
29
+ ```ruby
30
+ require "bran/ext/rainbows"
31
+
32
+ Rainbows! do
33
+ use :Bran # use the Bran adapter as the concurrency manager
34
+ worker_connections 100 # accept 100 connections per worker (100 fibers)
35
+ end
36
+ ```
37
+
38
+ Tested with:
39
+ - `rainbows 5.0.0` (`unicorn 5.0.0`, `kgio 2.10.0`)
40
+ - `rainbows 5.0.0` (`unicorn 5.0.1`, `kgio 2.10.0`)
41
+
42
+ [rainbows]: http://rainbows.bogomips.org/
43
+ [unicorn]: http://unicorn.bogomips.org/
@@ -0,0 +1,93 @@
1
+
2
+ require "rake/clean"
3
+ require "ffi"
4
+
5
+ FILES = {}
6
+
7
+ task :default => [:build, :compact]
8
+
9
+ def self.file_task(filename, opts, &block)
10
+ name, dep = opts.is_a?(Hash) ? opts.to_a.first : [opts, nil]
11
+
12
+ FILES[name] = filename
13
+ CLEAN.include filename
14
+ task name => filename
15
+
16
+ if dep
17
+ file filename => FILES[dep], &block
18
+ else
19
+ file filename, &block
20
+ end
21
+ end
22
+
23
+ def cmd(string)
24
+ fail "Command failed: #{string}" unless system(string)
25
+ end
26
+
27
+ file_task "libuv.tar.gz", :download_tarball do
28
+ version = "1.8.0"
29
+ release = "https://github.com/libuv/libuv/archive/v#{version}.tar.gz"
30
+ cmd "wget -O #{FILES[:download_tarball]} #{release}"
31
+ end
32
+
33
+ file_task "libuv", :download => :download_tarball do
34
+ cmd "tar -zxf #{FILES[:download_tarball]}"
35
+ cmd "mv libuv-* #{FILES[:download]}"
36
+ end
37
+
38
+ file_task "autogen.touch", :autogen => :download do
39
+ cmd "/usr/bin/env sh -c 'cd #{FILES[:download]} && ./autogen.sh'"
40
+ cmd "touch #{FILES[:autogen]}"
41
+ end
42
+
43
+ file_task "configure.touch", :configure => :autogen do
44
+ cmd "/usr/bin/env sh -c 'cd #{FILES[:download]} && ./configure'"
45
+ cmd "touch #{FILES[:configure]}"
46
+ end
47
+
48
+ # TODO: try to get patch accepted upstream?
49
+ file_task "patch.touch", :patch => :configure do
50
+ uv_header = "#{FILES[:download]}/include/uv.h"
51
+ uv_source = "#{FILES[:download]}/src/uv-common.c"
52
+
53
+ ##
54
+ # Patch 1 - runtime detection for the sizeof each struct type.
55
+
56
+ types = []
57
+ not_types = []
58
+ File.read(uv_header).each_line do |line|
59
+ # Record the type from each struct typedef.
60
+ match = /typedef struct uv_\w+_s (uv_\w+?)_t;/.match line
61
+ types << match[1] if match
62
+
63
+ # Don't record the type if it already has a sizeof function.
64
+ match = /UV_EXTERN size_t (uv_\w+?)_sizeof\(void\);/.match line
65
+ not_types << match[1] if match
66
+ end
67
+ types -= not_types
68
+
69
+ # Declare the sizeof function for each recorded type.
70
+ File.open uv_header, "a" do |file|
71
+ types.each do |type|
72
+ file.puts("UV_EXTERN size_t #{type}_sizeof(void);")
73
+ end
74
+ end
75
+
76
+ # Implement the sizeof function for each recorded type.
77
+ File.open uv_source, "a" do |file|
78
+ types.each do |type|
79
+ file.puts("size_t #{type}_sizeof(void) { return sizeof(#{type}_t); }")
80
+ end
81
+ end
82
+ end
83
+
84
+ file_task "libuv.#{::FFI::Platform::LIBSUFFIX}", :build => :patch do
85
+ cmd "/usr/bin/env sh -c 'cd #{FILES[:download]} && make'"
86
+ cmd "cp #{FILES[:download]}/.libs/#{FILES[:build]} ."
87
+ end
88
+
89
+ task :compact => FILES[:build] do
90
+ FILES.each do |key, filename|
91
+ cmd "rm -rf #{filename}" unless key == :build
92
+ end
93
+ end
@@ -0,0 +1,4 @@
1
+
2
+ require_relative "bran/libuv"
3
+
4
+ require_relative "bran/fiber_manager"
@@ -0,0 +1,35 @@
1
+
2
+ module Bran
3
+ class Ext
4
+
5
+ class << self
6
+ attr_accessor :check_assumptions
7
+
8
+ def assume(&block)
9
+ return unless @check_assumptions
10
+
11
+ instance_eval &block
12
+ end
13
+
14
+ def check(cond)
15
+ return if cond
16
+
17
+ match = caller.first.match(/\A(.+?):(\d+):in/)
18
+ line = File.read(match[1]).each_line.to_a[Integer(match[2]) - 1].strip
19
+
20
+ fail "Bran extension compatibility check failed: #{line}"
21
+ end
22
+
23
+ REGISTRY = {}
24
+
25
+ def []=(ext_name, value)
26
+ REGISTRY[ext_name] = value
27
+ end
28
+
29
+ def [](ext_name)
30
+ REGISTRY[ext_name]
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,107 @@
1
+
2
+ require_relative "../../bran"
3
+ require_relative "../../bran/ext"
4
+
5
+ ::Bran::Ext[:io] = true
6
+
7
+ # TODO: split to io-read.rb
8
+ Module.new do
9
+ IO.prepend self
10
+
11
+ def getbyte(*)
12
+ fm = Thread.current.thread_variable_get(:fiber_manager)
13
+ fm.wait_for_readable!(to_i) if fm
14
+ super
15
+ end
16
+
17
+ def getc(*)
18
+ fm = Thread.current.thread_variable_get(:fiber_manager)
19
+ fm.wait_for_readable!(to_i) if fm
20
+ super
21
+ end
22
+
23
+ def gets(*)
24
+ fm = Thread.current.thread_variable_get(:fiber_manager)
25
+ fm.wait_for_readable!(to_i) if fm
26
+ super
27
+ end
28
+
29
+ def read(*)
30
+ fm = Thread.current.thread_variable_get(:fiber_manager)
31
+ fm.wait_for_readable!(to_i) if fm
32
+ super
33
+ end
34
+
35
+ def readbyte(*)
36
+ fm = Thread.current.thread_variable_get(:fiber_manager)
37
+ fm.wait_for_readable!(to_i) if fm
38
+ super
39
+ end
40
+
41
+ def readchar(*)
42
+ fm = Thread.current.thread_variable_get(:fiber_manager)
43
+ fm.wait_for_readable!(to_i) if fm
44
+ super
45
+ end
46
+
47
+ def readlines(*)
48
+ fm = Thread.current.thread_variable_get(:fiber_manager)
49
+ fm.wait_for_readable!(to_i) if fm
50
+ super
51
+ end
52
+
53
+ def readpartial(*)
54
+ fm = Thread.current.thread_variable_get(:fiber_manager)
55
+ fm.wait_for_readable!(to_i) if fm
56
+ super
57
+ end
58
+
59
+ def sysread(*)
60
+ fm = Thread.current.thread_variable_get(:fiber_manager)
61
+ fm.wait_for_readable!(to_i) if fm
62
+ super
63
+ end
64
+ end
65
+
66
+ # TODO: split to io-select.rb
67
+ Module.new do
68
+ IO.singleton_class.prepend self
69
+
70
+ def select(r_ary = nil, w_ary = nil, e_ary = nil, timeout = nil)
71
+ fm = Thread.current.thread_variable_get(:fiber_manager)
72
+ return super unless fm
73
+
74
+ raise NotImplementedError if e_ary && e_ary.any? # TODO: support e_ary?
75
+
76
+ # TODO: move inner implementation to inside FiberManager?
77
+ fiber = Fiber.current
78
+ timer = nil
79
+ finish = Proc.new do |item|
80
+ begin
81
+ r_ary.each { |io| fm.loop.pop_readable(Integer(io)) } if r_ary
82
+ w_ary.each { |io| fm.loop.pop_writable(Integer(io)) } if w_ary
83
+ fm.loop.timer_cancel(timer) if timer
84
+ ensure
85
+ fiber.resume(item)
86
+ end
87
+ end
88
+
89
+ r_ary.each do |io|
90
+ fm.loop.push_readable(Integer(io), Proc.new { |*|
91
+ finish.call([[io], [], []])
92
+ })
93
+ end if r_ary
94
+
95
+ w_ary.each do |io|
96
+ fm.loop.push_writable(Integer(io), Proc.new { |*|
97
+ finish.call([[], [io], []])
98
+ })
99
+ end if w_ary
100
+
101
+ if timeout
102
+ timer = fm.loop.timer_oneshot(timeout) { timer = nil; finish.call(nil) }
103
+ end
104
+
105
+ Fiber.yield
106
+ end
107
+ end
@@ -0,0 +1,9 @@
1
+
2
+ require_relative "../../bran"
3
+ require_relative "../../bran/ext"
4
+
5
+ ::Bran::Ext[:rainbows] = true
6
+
7
+ require "rainbows"
8
+
9
+ require_relative "rainbows/bran"
@@ -0,0 +1,64 @@
1
+
2
+ ::Bran::Ext.assume do
3
+ ::Rainbows::HttpServer.instance_method(:worker_connections).tap do |m|
4
+ check m.owner == ::Rainbows::HttpServer
5
+ check m.arity == 0
6
+ end
7
+
8
+ ::Rainbows::HttpServer.instance_method(:init_worker_process).tap do |m|
9
+ check m.owner == ::Unicorn::HttpServer
10
+ check m.arity == 1
11
+ end
12
+
13
+ ::Rainbows::HttpServer.instance_method(:reopen_worker_logs).tap do |m|
14
+ check m.owner == ::Unicorn::HttpServer
15
+ check m.arity == 1
16
+ end
17
+
18
+ ::Unicorn::HttpServer.instance_method(:process_client).tap do |m|
19
+ check m.owner == ::Unicorn::HttpServer
20
+ check m.arity == 1
21
+ end
22
+
23
+ check ::Unicorn::Worker.instance_method(:nr).arity == 0
24
+ check ::Kgio::TCPServer.instance_method(:to_i).arity == 0
25
+ end
26
+
27
+ module Rainbows
28
+ module Bran
29
+ include ::Rainbows::Base
30
+
31
+ def worker_loop(worker)
32
+ readers = init_worker_process(worker)
33
+
34
+ # We have to skip the implementation from Rainbows::Base,
35
+ # and use the implementation from Unicorn::HttpServer directly.
36
+ process_client = ::Unicorn::HttpServer.instance_method(:process_client)
37
+ process_client = process_client.bind(self)
38
+
39
+ manager = ::Bran::FiberManager.new
40
+ stopping = false
41
+
42
+ manager.run! do
43
+ manager.loop.signal_start(:INT) { exit!(0) }
44
+ manager.loop.signal_start(:TERM) { exit!(0) }
45
+ manager.loop.signal_start(:USR1) { reopen_worker_logs(worker.nr) } # TODO: test
46
+ manager.loop.signal_start(:QUIT) { stopping = true } # TODO: test softness
47
+
48
+ readers.each do |reader|
49
+ next unless reader.is_a?(::Kgio::TCPServer) # TODO: other readers?
50
+
51
+ worker_connections.times.map do |i|
52
+ ::Fiber.new do
53
+ until stopping # TODO: rescue and report errors here
54
+ manager.wait_for_readable!(reader)
55
+ process_client.call(reader.kgio_accept)
56
+ end
57
+ end.resume
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,17 @@
1
+
2
+ require_relative "../../bran"
3
+ require_relative "../../bran/ext"
4
+
5
+ ::Bran::Ext[:tcp_server] = true
6
+
7
+ require "socket"
8
+
9
+ Module.new do
10
+ TCPServer.prepend self
11
+
12
+ def accept(*)
13
+ fm = Thread.current.thread_variable_get(:fiber_manager)
14
+ fm.wait_for_readable!(to_i) if fm
15
+ super
16
+ end
17
+ end
@@ -0,0 +1,43 @@
1
+
2
+ require "fiber"
3
+
4
+ module Bran
5
+ class FiberManager
6
+ attr_reader :loop # TODO: hide loop when rest of interface is stable
7
+
8
+ def initialize
9
+ @loop = LibUV::Reactor.new
10
+ end
11
+
12
+ def run!
13
+ Thread.current.thread_variable_set(:fiber_manager, self)
14
+
15
+ yield if block_given?
16
+
17
+ @loop.run!
18
+ ensure
19
+ Thread.current.thread_variable_set(:fiber_manager, nil)
20
+ end
21
+
22
+ def stop!
23
+ Thread.current.thread_variable_set(:fiber_manager, nil)
24
+ @loop.stop!
25
+ end
26
+
27
+ def wait_for_readable!(fd)
28
+ @loop.push_readable(Integer(fd), ::Fiber.current, false)
29
+ ::Fiber.yield
30
+ end
31
+
32
+ def wait_for_writable!(fd)
33
+ @loop.push_writable(Integer(fd), ::Fiber.current, true)
34
+ ::Fiber.yield
35
+ end
36
+
37
+ def wait_for_seconds!(seconds)
38
+ @loop.timer_oneshot_wake(seconds, ::Fiber.current)
39
+ ::Fiber.yield
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,5 @@
1
+
2
+ require_relative "libuv/ffi"
3
+ require_relative "libuv/util"
4
+
5
+ require_relative "libuv/reactor"
@@ -0,0 +1,103 @@
1
+
2
+ require "ffi"
3
+
4
+ module Bran
5
+ module LibUV
6
+ module FFI
7
+ extend ::FFI::Library
8
+
9
+ libfile = "libuv.#{::FFI::Platform::LIBSUFFIX}"
10
+
11
+ ffi_lib \
12
+ File.expand_path("../../../ext/libuv/#{libfile}", File.dirname(__FILE__))
13
+
14
+ opts = {
15
+ blocking: true # only necessary on MRI to deal with the GIL.
16
+ }
17
+
18
+ UV_READABLE = 1
19
+ UV_WRITABLE = 2
20
+
21
+ # Struct sizes/allocators
22
+ %w(uv_loop uv_poll uv_signal uv_timer).each do |type|
23
+ eval <<-RUBY
24
+ typedef :pointer, :#{type}_ptr
25
+ attach_function :#{type}_sizeof, [], :size_t, **opts
26
+ #{type.upcase}_SIZEOF = #{type}_sizeof
27
+
28
+ def self.#{type}_alloc
29
+ ptr = ::FFI::MemoryPointer.new(#{type.upcase}_SIZEOF)
30
+ ptr.autorelease = false
31
+ ptr
32
+ end
33
+ RUBY
34
+ end
35
+
36
+ typedef :int, :uv_os_sock_t # not true on Windows, but we don't care
37
+
38
+ typedef enum([
39
+ :default,
40
+ :once,
41
+ :nowait
42
+ ]), :uv_run_mode
43
+
44
+ ##
45
+ # Callback factory methods.
46
+ #
47
+ # WARNING: If your Ruby code doesn't retain a reference to the
48
+ # FFI::Function object after passing it to a C function call,
49
+ # it may be garbage collected while C still holds the pointer,
50
+ # potentially resulting in a segmentation fault.
51
+
52
+ typedef :pointer, :uv_poll_cb_ptr
53
+ def self.uv_poll_cb(&block)
54
+ # (handle, status, events)
55
+ params = [:pointer, :int, :int]
56
+ ::FFI::Function.new :void, params, blocking: true do |*args|
57
+ block.call(*args)
58
+ end
59
+ end
60
+
61
+ typedef :pointer, :uv_signal_cb_ptr
62
+ def self.uv_signal_cb(&block)
63
+ # (handle, signo)
64
+ params = [:pointer, :int]
65
+ ::FFI::Function.new :void, params, blocking: true do |*args|
66
+ block.call(*args)
67
+ end
68
+ end
69
+
70
+ typedef :pointer, :uv_timer_cb_ptr
71
+ def self.uv_timer_cb(&block)
72
+ # (handle)
73
+ params = [:pointer]
74
+ ::FFI::Function.new :void, params, blocking: true do |*args|
75
+ block.call(*args)
76
+ end
77
+ end
78
+
79
+ attach_function :uv_strerror, [:int], :string, **opts
80
+ attach_function :uv_err_name, [:int], :string, **opts
81
+
82
+ attach_function :uv_loop_init, [:uv_loop_ptr], :int, **opts
83
+ attach_function :uv_loop_close, [:uv_loop_ptr], :int, **opts
84
+ attach_function :uv_loop_alive, [:uv_loop_ptr], :int, **opts
85
+
86
+ attach_function :uv_run, [:uv_loop_ptr, :uv_run_mode], :int, **opts
87
+ attach_function :uv_stop, [:uv_loop_ptr], :void, **opts
88
+
89
+ attach_function :uv_poll_init, [:uv_loop_ptr, :uv_poll_ptr, :int], :int, **opts
90
+ attach_function :uv_poll_init_socket, [:uv_loop_ptr, :uv_poll_ptr, :uv_os_sock_t], :int, **opts
91
+ attach_function :uv_poll_start, [:uv_poll_ptr, :int, :uv_poll_cb_ptr], :int, **opts
92
+ attach_function :uv_poll_stop, [:uv_poll_ptr], :int, **opts
93
+
94
+ attach_function :uv_signal_init, [:uv_loop_ptr, :uv_signal_ptr], :int, **opts
95
+ attach_function :uv_signal_start, [:uv_signal_ptr, :uv_signal_cb_ptr, :int], :int, **opts
96
+ attach_function :uv_signal_stop, [:uv_signal_ptr], :int, **opts
97
+
98
+ attach_function :uv_timer_init, [:uv_loop_ptr, :uv_timer_ptr], :int, **opts
99
+ attach_function :uv_timer_start, [:uv_timer_ptr, :uv_timer_cb_ptr, :uint64, :uint64], :int, **opts
100
+ attach_function :uv_timer_stop, [:uv_timer_ptr], :int, **opts
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,361 @@
1
+
2
+ module Bran
3
+ module LibUV
4
+ class Reactor
5
+
6
+ # Raised when an operation is performed on an already-destroyed Loop.
7
+ class DestroyedError < RuntimeError; end
8
+
9
+ def initialize
10
+ @ptr = FFI.uv_loop_alloc
11
+ Util.error_check "creating the loop",
12
+ FFI.uv_loop_init(@ptr)
13
+
14
+ @finalizer = self.class.create_finalizer_for(@ptr)
15
+ ObjectSpace.define_finalizer(self, @finalizer)
16
+
17
+ @available_polls = []
18
+ @running_reads = {}
19
+ @running_writes = {}
20
+ @fds_by_read_addr = {}
21
+ @fds_by_write_addr = {}
22
+ @on_readables = {}
23
+ @on_writables = {}
24
+
25
+ @available_signals = []
26
+ @running_signals = {}
27
+
28
+ @available_timers = []
29
+ @running_timers = {}
30
+
31
+ @poll_read_callback = FFI.uv_poll_cb(&method(:_poll_read_callback))
32
+ @poll_write_callback = FFI.uv_poll_cb(&method(:_poll_write_callback))
33
+
34
+ # TODO: add more Ruby-compatible signal handlers by default?
35
+ signal_start(:INT) { @abort_signal = :INT; stop! }
36
+ end
37
+
38
+ # Free the native resources associated with this object. This will
39
+ # be done automatically on garbage collection if not called explicitly.
40
+ def destroy
41
+ if @finalizer
42
+ @finalizer.call
43
+ ObjectSpace.undefine_finalizer(self)
44
+ end
45
+ @ptr = @finalizer = nil
46
+
47
+ @available_polls = \
48
+ @running_reads = @running_writes = \
49
+ @fds_by_read_addr = @fds_by_write_addr = \
50
+ @on_readables = @on_writables = \
51
+ @available_signals = @running_signals = \
52
+ @available_timers = @running_timers = \
53
+ @poll_read_callback = @poll_write_callback = nil
54
+
55
+ self
56
+ end
57
+
58
+ # @api private
59
+ def ptr
60
+ raise DestroyedError unless @ptr
61
+ @ptr
62
+ end
63
+
64
+ # @api private
65
+ def self.create_finalizer_for(ptr)
66
+ Proc.new do
67
+ FFI.uv_loop_close(ptr)
68
+ # TODO: prevent running finalizer when loop hasn't been stopped?
69
+ ptr.free
70
+ end
71
+ end
72
+
73
+ # Capture exceptions raised from callbacks, stopping the loop,
74
+ # capturing the exception to be re-raised outside the loop in #run!.
75
+ # @api private
76
+ def rescue_abort
77
+ yield
78
+ rescue Exception => ex
79
+ @abort_exception = ex
80
+ stop!
81
+ end
82
+
83
+ # Run the libuv event loop in default (blocking) mode,
84
+ # running until stopped or until all handles are removed.
85
+ def run!
86
+ @abort_exception = nil
87
+ @abort_signal = nil
88
+
89
+ rc = FFI.uv_run(ptr, :default)
90
+
91
+ # If an exception or signal caused the stop, re-raise it here.
92
+ raise @abort_exception if @abort_exception
93
+ Process.kill(@abort_signal, Process.pid) if @abort_signal
94
+
95
+ Util.error_check "running the loop in blocking mode", rc
96
+ end
97
+
98
+ # Return true if there are active handles or request in the loop.
99
+ def stop!
100
+ FFI.uv_stop(ptr)
101
+ end
102
+
103
+ # Push the given handler for the given fd, adding if necessary.
104
+ # If persistent is false, the handler will be popped after one trigger.
105
+ def push_readable(fd, handler, persistent = true)
106
+ ptr = ptr()
107
+ fd = Integer(fd)
108
+
109
+ # TODO: worry about readable vs writable mixing/overlap
110
+ if (readables = @on_readables[fd])
111
+ readables << [handler, persistent]
112
+ return
113
+ end
114
+
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
+ fd
128
+ end
129
+
130
+ # Push the given handler for the given fd, adding if necessary.
131
+ # If persistent is false, the handler will be popped after one trigger.
132
+ def push_writable(fd, handler, persistent = true)
133
+ ptr = ptr()
134
+ fd = Integer(fd)
135
+
136
+ # TODO: worry about writable vs writable mixing/overlap
137
+ if (writables = @on_writables[fd])
138
+ writables << [handler, persistent]
139
+ return
140
+ end
141
+
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
+ fd
155
+ end
156
+
157
+ # Remove the next readable handler for the given fd.
158
+ def pop_readable(fd)
159
+ fd = Integer(fd)
160
+
161
+ readables = @on_readables[fd]
162
+ return unless readables
163
+
164
+ readables.pop
165
+ return unless readables.empty?
166
+
167
+ @on_readables.delete(fd)
168
+ poll = @running_reads.delete(fd)
169
+ @fds_by_read_addr.delete(poll.address)
170
+
171
+ Util.error_check "stopping the poll readable entry",
172
+ FFI.uv_poll_stop(poll)
173
+
174
+ @available_polls << poll
175
+
176
+ nil
177
+ end
178
+
179
+ # Remove the next writable handler for the given fd.
180
+ def pop_writable(fd)
181
+ fd = Integer(fd)
182
+
183
+ writables = @on_writables[fd]
184
+ return unless writables
185
+
186
+ writables.pop
187
+ return unless writables.empty?
188
+
189
+ @on_writables.delete(fd)
190
+ poll = @running_writes.delete(fd)
191
+ @fds_by_write_addr.delete(poll.address)
192
+
193
+ Util.error_check "stopping the poll writable entry",
194
+ FFI.uv_poll_stop(poll)
195
+
196
+ @available_polls << poll
197
+
198
+ nil
199
+ end
200
+
201
+ # Start handling the given signal, running the given block when it occurs.
202
+ def signal_start(signo, &block)
203
+ ptr = ptr()
204
+ signo = Signal.list.fetch(signo.to_s) unless signo.is_a?(Integer)
205
+
206
+ signal_stop(signo) if @running_signals.has_key?(signo)
207
+
208
+ callback = FFI.uv_signal_cb do |_, _|
209
+ rescue_abort do
210
+ block.call self, signo
211
+ end
212
+ end
213
+
214
+ signal = @available_signals.pop || FFI.uv_signal_alloc
215
+ @running_signals[signo] = [signal, callback]
216
+
217
+ # TODO: investigate if need not init existing available_signals
218
+ Util.error_check "creating the signal item",
219
+ FFI.uv_signal_init(ptr, signal)
220
+
221
+ Util.error_check "starting the signal item",
222
+ FFI.uv_signal_start(signal, callback, signo)
223
+ end
224
+
225
+ # Stop handling the given signal.
226
+ def signal_stop(signo)
227
+ signo = Signal.list.fetch(signo.to_s) unless signo.is_a?(Integer)
228
+
229
+ signal, callback = @running_signals.delete(signo)
230
+
231
+ return unless signal
232
+
233
+ Util.error_check "stopping the signal item",
234
+ FFI.uv_signal_stop(signal)
235
+
236
+ @available_signals << signal
237
+
238
+ nil
239
+ end
240
+
241
+ # Start a timer to run the given block after the given timeout.
242
+ # If a repeat_interval is given, after the first run, the block will be
243
+ # run repeatedly at that interval. If a repeat_interval is not given,
244
+ # or given as nil or 0, timer_cancel is called automatically at first run.
245
+ # Both timeout and repeat_interval should be given in seconds.
246
+ def timer_start(timeout, repeat_interval = nil, &block)
247
+ ptr = ptr()
248
+
249
+ timeout = (timeout * 1000).ceil
250
+
251
+ repeat = false
252
+ if repeat_interval and repeat_interval > 0
253
+ repeat_interval = (repeat_interval * 1000).ceil
254
+ repeat = true
255
+ else
256
+ repeat_interval = 0
257
+ end
258
+
259
+ raise ArgumentError, "callback block required" unless block
260
+
261
+ timer = @available_timers.pop || FFI.uv_timer_alloc
262
+ id = timer.address
263
+
264
+ callback = FFI.uv_timer_cb do |_|
265
+ rescue_abort do
266
+ block.call self, id
267
+
268
+ timer_cancel(id) unless repeat
269
+ end
270
+ end
271
+
272
+ @running_timers[id] = [timer, callback]
273
+
274
+ # TODO: investigate if need not init existing available_timers
275
+ Util.error_check "creating the timer item",
276
+ FFI.uv_timer_init(ptr, timer)
277
+
278
+ Util.error_check "starting the timer item",
279
+ FFI.uv_timer_start(timer, callback, timeout, repeat_interval)
280
+
281
+ id
282
+ end
283
+
284
+ # Stop handling the given timer.
285
+ def timer_cancel(id)
286
+ id = Integer(id)
287
+
288
+ timer, callback = @running_timers.delete(id)
289
+
290
+ return unless timer
291
+
292
+ Util.error_check "stopping the timer item",
293
+ FFI.uv_timer_stop(timer)
294
+
295
+ @available_timers << timer
296
+
297
+ nil
298
+ end
299
+
300
+ # Start a timer to run the given block after the given timeout.
301
+ # The timer will be run just once, starting now.
302
+ def timer_oneshot(time, &block)
303
+ timer_start(time, &block)
304
+ end
305
+
306
+ # Start a timer to wake the given fiber after the given timeout.
307
+ # The timer will be run just once, starting now.
308
+ def timer_oneshot_wake(time, fiber)
309
+ timer_start(time) { fiber.resume } # TODO: optimize this case
310
+ end
311
+
312
+ private
313
+
314
+ # Callback method called directly from FFI when an event is readable.
315
+ def _poll_read_callback(poll, rc, events)
316
+ rescue_abort do
317
+ fd = @fds_by_read_addr.fetch(poll.address)
318
+ readables = @on_readables.fetch(fd)
319
+
320
+ handler, persistent = readables.last
321
+ pop_readable(fd) unless persistent
322
+
323
+ invoke_handler(handler, rc)
324
+ end
325
+ end
326
+
327
+ # Callback method called directly from FFI when an event is writable.
328
+ def _poll_write_callback(poll, rc, events)
329
+ rescue_abort do
330
+ fd = @fds_by_write_addr.fetch(poll.address)
331
+ writables = @on_writables.fetch(fd)
332
+
333
+ handler, persistent = writables.last
334
+ pop_writable(fd) unless persistent
335
+
336
+ invoke_handler(handler, rc)
337
+ end
338
+ end
339
+
340
+ # Invoke the given handler, possibly converting the given rc to an error.
341
+ def invoke_handler(handler, rc)
342
+ case handler
343
+ when ::Fiber
344
+ if rc == 0
345
+ handler.resume nil
346
+ else
347
+ handler.resume Util.error_create("running the libuv loop", rc)
348
+ end
349
+ when ::Proc
350
+ if rc == 0
351
+ handler.call nil
352
+ else
353
+ error = Util.error_create("running the libuv loop", rc)
354
+ handler.call error
355
+ end
356
+ end
357
+ end
358
+
359
+ end
360
+ end
361
+ end
@@ -0,0 +1,19 @@
1
+
2
+ module Bran
3
+ module LibUV
4
+ module Util
5
+
6
+ def self.error_check(action_description, rc)
7
+ raise error_create(action_description, rc) if rc < 0
8
+ end
9
+
10
+ def self.error_create(action_description, rc)
11
+ # TODO: use appropriate SystemCallError exception class based on errno.
12
+ name = FFI.uv_err_name(rc)
13
+ desc = FFI.uv_strerror(rc)
14
+ RuntimeError.new("LibUV error - while #{action_description} - #{name} - #{desc}")
15
+ end
16
+
17
+ end
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bran
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Joe McIlvain
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-02-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.9'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.9.8
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.9'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.9.8
33
+ - !ruby/object:Gem::Dependency
34
+ name: bundler
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.6'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.6'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '10.3'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '10.3'
61
+ - !ruby/object:Gem::Dependency
62
+ name: pry
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.9'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.9'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: rspec-its
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '1.0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '1.0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: fivemat
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '1.3'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '1.3'
117
+ description: A source of Fiber.
118
+ email: joe.eli.mac@gmail.com
119
+ executables: []
120
+ extensions:
121
+ - ext/libuv/Rakefile
122
+ extra_rdoc_files: []
123
+ files:
124
+ - README.md
125
+ - ext/libuv/Rakefile
126
+ - lib/bran.rb
127
+ - lib/bran/ext.rb
128
+ - lib/bran/ext/io.rb
129
+ - lib/bran/ext/rainbows.rb
130
+ - lib/bran/ext/rainbows/bran.rb
131
+ - lib/bran/ext/tcp_server.rb
132
+ - lib/bran/fiber_manager.rb
133
+ - lib/bran/libuv.rb
134
+ - lib/bran/libuv/ffi.rb
135
+ - lib/bran/libuv/reactor.rb
136
+ - lib/bran/libuv/util.rb
137
+ homepage: https://github.com/jemc/ruby-bran
138
+ licenses:
139
+ - All rights reserved.
140
+ metadata: {}
141
+ post_install_message:
142
+ rdoc_options: []
143
+ require_paths:
144
+ - lib
145
+ required_ruby_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ requirements: []
156
+ rubyforge_project:
157
+ rubygems_version: 2.4.5
158
+ signing_key:
159
+ specification_version: 4
160
+ summary: bran
161
+ test_files: []
162
+ has_rdoc: