rev 0.1.0

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.
@@ -0,0 +1,101 @@
1
+ #--
2
+ # Copyright (C)2007 Tony Arcieri
3
+ # You can redistribute this under the terms of the Ruby license
4
+ # See file LICENSE for details
5
+ #++
6
+
7
+ require File.dirname(__FILE__) + '/../rev'
8
+
9
+ module Rev
10
+ class Loop
11
+ attr_reader :watchers
12
+
13
+ # Create a new Rev::Loop
14
+ #
15
+ # Options:
16
+ #
17
+ # :skip_environment (boolean)
18
+ # Ignore the $LIBEV_FLAGS environment variable
19
+ #
20
+ # :fork_check (boolean)
21
+ # Enable autodetection of forks
22
+ #
23
+ # :backend
24
+ # Choose the default backend, one (or many in an array) of:
25
+ # :select (most platforms)
26
+ # :poll (most platforms except Windows)
27
+ # :epoll (Linux)
28
+ # :kqueue (BSD/Mac OS X)
29
+ # :port (Solaris 10)
30
+ #
31
+ def initialize(options = {})
32
+ @watchers = []
33
+ @active_watchers = 0
34
+
35
+ flags = 0
36
+
37
+ options.each do |option, value|
38
+ case option
39
+ when :skip_environment
40
+ flags |= EVFLAG_NOEV if value
41
+ when :fork_check
42
+ flags |= EVFLAG_FORKCHECK if value
43
+ when :backend
44
+ value = [value] unless value.is_a? Array
45
+ value.each do |backend|
46
+ case backend
47
+ when :select then flags |= EVBACKEND_SELECT
48
+ when :poll then flags |= EVBACKEND_POLL
49
+ when :epoll then flags |= EVBACKEND_EPOLL
50
+ when :kqueue then flags |= EVBACKEND_KQUEUE
51
+ when :port then flags |= EVBACKEND_PORT
52
+ else raise ArgumentError, "no such backend: #{backend}"
53
+ end
54
+ end
55
+ else raise ArgumentError, "no such option: #{option}"
56
+ end
57
+ end
58
+
59
+ @loop = ev_loop_new(flags)
60
+ end
61
+
62
+ # Attach a watcher to the loop
63
+ def attach(watcher)
64
+ watcher.attach self
65
+ end
66
+
67
+ # Run the event loop and dispatch events back to Ruby. If there
68
+ # are no watchers associated with the event loop it will return
69
+ # immediately. Otherwise, run will continue blocking and making
70
+ # event callbacks to watchers until all watchers associated with
71
+ # the loop have been disabled or detached. The loop may be
72
+ # explicitly stopped by calling the stop method on the loop object.
73
+ def run
74
+ raise RuntimeError, "no watchers for this loop" if @watchers.empty?
75
+
76
+ @running = true
77
+ while @running and not @active_watchers.zero?
78
+ run_once
79
+ end
80
+ end
81
+
82
+ # Stop the event loop if it's running
83
+ def stop
84
+ raise RuntimeError, "loop not running" unless @running
85
+ @running = false
86
+ end
87
+
88
+ #######
89
+ private
90
+ #######
91
+
92
+ EVFLAG_NOENV = 0x1000000 # do NOT consult environment
93
+ EVFLAG_FORKCHECK = 0x2000000 # check for a fork in each iteration
94
+
95
+ EVBACKEND_SELECT = 0x00000001 # supported about anywhere
96
+ EVBACKEND_POLL = 0x00000002 # !win
97
+ EVBACKEND_EPOLL = 0x00000004 # linux
98
+ EVBACKEND_KQUEUE = 0x00000008 # bsd
99
+ EVBACKEND_PORT = 0x00000020 # solaris 10
100
+ end
101
+ end
@@ -0,0 +1,53 @@
1
+ #--
2
+ # Copyright (C)2007 Tony Arcieri
3
+ # You can redistribute this under the terms of the Ruby license
4
+ # See file LICENSE for details
5
+ #++
6
+
7
+ require File.dirname(__FILE__) + '/../rev'
8
+
9
+ module Rev
10
+ class Server < Listener
11
+ def initialize(listen_socket, klass = Socket, *args, &block)
12
+ # Ensure the provided class responds to attach
13
+ unless (klass.instance_method(:attach) rescue nil)
14
+ raise ArgumentError, "provided class must respond to 'attach'"
15
+ end
16
+
17
+ # Verify the arity of the provided arguments
18
+ arity = klass.instance_method(:initialize).arity
19
+ expected = arity >= 0 ? arity : -(arity + 1)
20
+
21
+ if (arity >= 0 and args.size + 1 != expected) or (arity < 0 and args.size + 1 < expected)
22
+ raise ArgumentError, "wrong number of arguments for #{klass}#initialize (#{args.size+1} for #{expected})"
23
+ end
24
+
25
+ @klass, @args, @block = klass, args, block
26
+ super(listen_socket)
27
+ end
28
+
29
+ #########
30
+ protected
31
+ #########
32
+
33
+ def on_connection(socket)
34
+ connection = @klass.new(socket, *@args).attach(evloop)
35
+ @block.(connection) if @block
36
+ connection.on_connect
37
+ end
38
+ end
39
+
40
+ class TCPServer < Server
41
+ def initialize(host, port, klass = TCPSocket, *args, &block)
42
+ listen_socket = ::TCPServer.new(host, port)
43
+ listen_socket.instance_eval { listen(1024) } # Change listen backlog to 1024
44
+ super(listen_socket, klass, *args, &block)
45
+ end
46
+ end
47
+
48
+ class UNIXServer < Server
49
+ def initialize(path, klass = UNIXSocket, *args, &block)
50
+ super(::UNIXServer.new(*args), klass, *args, &block)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,186 @@
1
+ #--
2
+ # Copyright (C)2007 Tony Arcieri
3
+ # You can redistribute this under the terms of the Ruby license
4
+ # See file LICENSE for details
5
+ #++
6
+
7
+ require 'socket'
8
+ require 'resolv'
9
+ require File.dirname(__FILE__) + '/../rev'
10
+
11
+ module Rev
12
+ class Socket < BufferedIO
13
+ def self.connect(socket, *args)
14
+ new(socket, *args).instance_eval {
15
+ @connector = Connector.new(self, socket)
16
+ self
17
+ }
18
+ end
19
+
20
+ watcher_delegate :@connector
21
+
22
+ def attach(evloop)
23
+ raise RuntimeError, "connection failed" if @failed
24
+
25
+ if @connector
26
+ @connector.attach(evloop)
27
+ return self
28
+ end
29
+
30
+ super
31
+ end
32
+
33
+ # Called upon completion of a socket connection
34
+ def on_connect; end
35
+ event_callback :on_connect
36
+
37
+ # Called if a socket connection failed to complete
38
+ def on_connect_failed; end
39
+ event_callback :on_connect_failed
40
+
41
+ #########
42
+ protected
43
+ #########
44
+
45
+ class Connector < IOWatcher
46
+ def initialize(rev_socket, ruby_socket)
47
+ @rev_socket, @ruby_socket = rev_socket, ruby_socket
48
+ super(ruby_socket, :w)
49
+ end
50
+
51
+ def on_writable
52
+ evl = evloop
53
+ detach
54
+
55
+ if connect_successful?
56
+ @rev_socket.instance_eval { @connector = nil }
57
+ @rev_socket.attach(evl)
58
+ @rev_socket.on_connect
59
+ else
60
+ @rev_socket.instance_eval { @failed = true }
61
+ @rev_socket.on_connect_failed
62
+ end
63
+ end
64
+
65
+ #######
66
+ private
67
+ #######
68
+
69
+ def connect_successful?
70
+ @ruby_socket.getsockopt(::Socket::SOL_SOCKET, ::Socket::SO_ERROR).unpack('i').first == 0
71
+ end
72
+ end
73
+ end
74
+
75
+ class TCPSocket < Socket
76
+ attr_reader :remote_host, :remote_addr, :remote_port, :address_family
77
+ watcher_delegate :@resolver
78
+
79
+ # Perform a non-blocking connect to the given host and port
80
+ def self.connect(addr, port, *args)
81
+ family = nil
82
+
83
+ if (Resolv::IPv4.create(addr) rescue nil)
84
+ family = ::Socket::AF_INET
85
+ elsif(Resolv::IPv6.create(addr) rescue nil)
86
+ family = ::Socket::AF_INET6
87
+ end
88
+
89
+ if family
90
+ return super(TCPConnectSocket.new(family, addr, port), *args)
91
+ end
92
+
93
+ if host = Rev::DNSResolver.hosts(addr)
94
+ return connect(host, port, *args)
95
+ end
96
+
97
+ return allocate.instance_eval {
98
+ @remote_host, @remote_addr, @remote_port = addr, addr, port
99
+ @resolver = TCPConnectResolver.new(self, addr, port, *args)
100
+ }
101
+ end
102
+
103
+ def initialize(socket)
104
+ unless socket.is_a?(::TCPSocket) or socket.is_a?(TCPConnectSocket)
105
+ raise TypeError, "socket must be a TCPSocket"
106
+ end
107
+
108
+ super
109
+
110
+ @address_family, @remote_port, @remote_host, @remote_addr = socket.peeraddr
111
+ end
112
+
113
+ #########
114
+ protected
115
+ #########
116
+
117
+ class TCPConnectSocket < ::Socket
118
+ def initialize(family, addr, port, host = addr)
119
+ @host, @addr, @port = host, addr, port
120
+ @address_family = nil
121
+
122
+ @socket = super(family, ::Socket::SOCK_STREAM, 0)
123
+ begin
124
+ @socket.connect_nonblock(::Socket.sockaddr_in(port, addr))
125
+ rescue Errno::EINPROGRESS
126
+ end
127
+ end
128
+
129
+ def peeraddr
130
+ [
131
+ @address_family == ::Socket::AF_INET ? 'AF_INET' : 'AF_INET6',
132
+ @port,
133
+ @host,
134
+ @addr
135
+ ]
136
+ end
137
+ end
138
+
139
+ class TCPConnectResolver < Rev::DNSResolver
140
+ def initialize(socket, host, port, *args)
141
+ @sock, @host, @port, @args = socket, host, port, args
142
+ super(host)
143
+ end
144
+
145
+ def on_success(addr)
146
+ host, port, args = @host, @port, @args
147
+
148
+ @sock.instance_eval {
149
+ # DNSResolver only supports IPv4 so we can safely assume an IPv4 address
150
+ socket = TCPConnectSocket.new(::Socket::AF_INET, addr, port, host)
151
+ initialize(socket, *args)
152
+ @connector = Connector.new(self, socket)
153
+ @resolver = nil
154
+ }
155
+ @sock.attach(evloop)
156
+ end
157
+
158
+ def on_failure
159
+ @sock.on_connect_failed
160
+ @sock.instance_eval {
161
+ @resolver = nil
162
+ @failed = true
163
+ }
164
+ return
165
+ end
166
+
167
+ alias_method :on_timeout, :on_failure
168
+ end
169
+ end
170
+
171
+ class UNIXSocket < Socket
172
+ attr_reader :path, :address_family
173
+
174
+ # Connect to the given UNIX domain socket
175
+ def self.connect(path, *args)
176
+ new(::UNIXSocket.new(path), *args)
177
+ end
178
+
179
+ def initialize(socket)
180
+ raise ArgumentError, "socket must be a UNIXSocket" unless socket.is_a? ::UNIXSocket
181
+
182
+ super
183
+ @address_family, @path = socket.peeraddr
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,18 @@
1
+ #--
2
+ # Copyright (C)2007 Tony Arcieri
3
+ # You can redistribute this under the terms of the Ruby license
4
+ # See file LICENSE for details
5
+ #++
6
+
7
+ require File.dirname(__FILE__) + '/../rev'
8
+
9
+ module Rev
10
+ class IOWatcher
11
+ # The actual implementation of this class resides in the C extension
12
+ # Here we metaprogram proper event_callbacks for the callback methods
13
+ # These can take a block and store it to be called when the event
14
+ # is actually fired.
15
+
16
+ event_callback :timer_watcher
17
+ end
18
+ end
@@ -0,0 +1,49 @@
1
+ #--
2
+ # Copyright (C)2007 Tony Arcieri
3
+ # You can redistribute this under the terms of the Ruby license
4
+ # See file LICENSE for details
5
+ #++
6
+
7
+ require File.dirname(__FILE__) + '/../rev'
8
+
9
+ module Rev
10
+ class Watcher
11
+ # Use an alternate watcher with the attach/detach/enable/disable methods
12
+ # if it is presently assigned. This is useful if you are waiting for
13
+ # an event to occur before the current watcher can be used in earnest,
14
+ # such as making an outgoing TCP connection.
15
+ def self.watcher_delegate(proxy_var)
16
+ %w{attach detach enable disable}.each do |method|
17
+ module_eval <<-EOD
18
+ def #{method}(*args)
19
+ if #{proxy_var}
20
+ #{proxy_var}.#{method}(*args)
21
+ return self
22
+ end
23
+
24
+ super
25
+ end
26
+ EOD
27
+ end
28
+ end
29
+
30
+ # Define callbacks whose behavior can be changed on-the-fly per instance.
31
+ # This is done by giving a block to the callback method, which is captured
32
+ # as a proc and stored for later. If the method is called without a block,
33
+ # the stored block is executed if present, otherwise it's a noop.
34
+ def self.event_callback(*methods)
35
+ methods.each do |method|
36
+ module_eval <<-EOD
37
+ def #{method}(*args, &block)
38
+ if block
39
+ @#{method}_callback = block
40
+ return
41
+ end
42
+
43
+ @#{method}_callback.(*([self] + args)) if @#{method}_callback
44
+ end
45
+ EOD
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,26 @@
1
+ #
2
+ # Oops, were you expecting some real RSpec?
3
+ #
4
+ # I'm sorry to disappoint. Really, this is pretty damn weak.
5
+ #
6
+ # I swear that after Ruby 1.9 comes out proper, this will become an actual spec
7
+ #
8
+
9
+ require 'socket'
10
+ require File.dirname(__FILE__) + '/../lib/rev'
11
+
12
+ class MyWatcher < Rev::IOWatcher
13
+ def on_readable
14
+ puts "Yippie, I'm readable!"
15
+ detach
16
+ end
17
+ end
18
+
19
+ listen_socket = TCPServer.new 'localhost', 4321
20
+ watcher = MyWatcher.new(listen_socket)
21
+ event_loop = Rev::Loop.new
22
+ watcher.attach(event_loop)
23
+
24
+ puts "Running the event loop in blocking mode on localhost port 4321"
25
+ puts "Telnet there or something and you should get a read event"
26
+ event_loop.run
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
4
+ name: rev
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2007-12-25 00:00:00 -07:00
8
+ summary: Ruby 1.9 binding to the libev high performance event library
9
+ require_paths:
10
+ - lib
11
+ email:
12
+ homepage:
13
+ rubyforge_project:
14
+ description:
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.9.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Tony Arcieri
31
+ files:
32
+ - LICENSE
33
+ - README
34
+ - lib/rev
35
+ - lib/rev/buffered_io.rb
36
+ - lib/rev/dns_resolver.rb
37
+ - lib/rev/io_watcher.rb
38
+ - lib/rev/listener.rb
39
+ - lib/rev/loop.rb
40
+ - lib/rev/server.rb
41
+ - lib/rev/socket.rb
42
+ - lib/rev/timer_watcher.rb
43
+ - lib/rev/watcher.rb
44
+ - lib/rev.rb
45
+ - ext/libev/ev.h
46
+ - ext/libev/ev_vars.h
47
+ - ext/libev/ev_wrap.h
48
+ - ext/rev/rev.h
49
+ - ext/rev/rev_watcher.h
50
+ - ext/libev/ev.c
51
+ - ext/libev/ev_epoll.c
52
+ - ext/libev/ev_kqueue.c
53
+ - ext/libev/ev_poll.c
54
+ - ext/libev/ev_port.c
55
+ - ext/libev/ev_select.c
56
+ - ext/libev/ev_win32.c
57
+ - ext/rev/rev_ext.c
58
+ - ext/rev/rev_io_watcher.c
59
+ - ext/rev/rev_loop.c
60
+ - ext/rev/rev_timer_watcher.c
61
+ - ext/rev/rev_watcher.c
62
+ - ext/rev/extconf.rb
63
+ test_files:
64
+ - spec/rev_spec.rb
65
+ rdoc_options:
66
+ - --title
67
+ - Rev
68
+ - --main
69
+ - README
70
+ - --line-numbers
71
+ extra_rdoc_files:
72
+ - README
73
+ - LICENSE
74
+ - ext/libev/ev.c
75
+ - ext/libev/ev_epoll.c
76
+ - ext/libev/ev_kqueue.c
77
+ - ext/libev/ev_poll.c
78
+ - ext/libev/ev_port.c
79
+ - ext/libev/ev_select.c
80
+ - ext/libev/ev_win32.c
81
+ - ext/rev/rev_ext.c
82
+ - ext/rev/rev_io_watcher.c
83
+ - ext/rev/rev_loop.c
84
+ - ext/rev/rev_timer_watcher.c
85
+ - ext/rev/rev_watcher.c
86
+ executables: []
87
+
88
+ extensions:
89
+ - ext/rev/extconf.rb
90
+ requirements: []
91
+
92
+ dependencies: []
93
+