libuv 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +17 -0
  3. data/.gitmodules +3 -0
  4. data/.rspec +1 -0
  5. data/.travis.yml +16 -0
  6. data/Gemfile +2 -0
  7. data/LICENSE +24 -0
  8. data/README.md +73 -0
  9. data/Rakefile +31 -0
  10. data/lib/libuv.rb +34 -0
  11. data/lib/libuv/assertions.rb +23 -0
  12. data/lib/libuv/async.rb +33 -0
  13. data/lib/libuv/check.rb +49 -0
  14. data/lib/libuv/error.rb +70 -0
  15. data/lib/libuv/ext/ext.rb +257 -0
  16. data/lib/libuv/ext/platform/darwin_x64.rb +12 -0
  17. data/lib/libuv/ext/platform/linux.rb +8 -0
  18. data/lib/libuv/ext/platform/unix.rb +14 -0
  19. data/lib/libuv/ext/platform/windows.rb +27 -0
  20. data/lib/libuv/ext/tasks.rb +27 -0
  21. data/lib/libuv/ext/tasks/mac.rb +23 -0
  22. data/lib/libuv/ext/tasks/unix.rb +23 -0
  23. data/lib/libuv/ext/tasks/win.rb +11 -0
  24. data/lib/libuv/ext/types.rb +230 -0
  25. data/lib/libuv/fs_event.rb +31 -0
  26. data/lib/libuv/handle.rb +82 -0
  27. data/lib/libuv/idle.rb +49 -0
  28. data/lib/libuv/listener.rb +34 -0
  29. data/lib/libuv/loop.rb +310 -0
  30. data/lib/libuv/net.rb +38 -0
  31. data/lib/libuv/pipe.rb +97 -0
  32. data/lib/libuv/prepare.rb +49 -0
  33. data/lib/libuv/q.rb +429 -0
  34. data/lib/libuv/resource.rb +28 -0
  35. data/lib/libuv/simple_async.rb +28 -0
  36. data/lib/libuv/stream.rb +124 -0
  37. data/lib/libuv/tcp.rb +194 -0
  38. data/lib/libuv/timer.rb +75 -0
  39. data/lib/libuv/tty.rb +34 -0
  40. data/lib/libuv/udp.rb +256 -0
  41. data/lib/libuv/version.rb +3 -0
  42. data/lib/libuv/work.rb +62 -0
  43. data/libuv.gemspec +54 -0
  44. data/spec/async_spec.rb +60 -0
  45. data/spec/defer_spec.rb +980 -0
  46. data/spec/idle_spec.rb +56 -0
  47. data/spec/pipe_spec.rb +148 -0
  48. data/spec/tcp_spec.rb +188 -0
  49. metadata +382 -0
data/lib/libuv/idle.rb ADDED
@@ -0,0 +1,49 @@
1
+ module Libuv
2
+ class Idle < Handle
3
+
4
+
5
+ def initialize(loop, callback = nil, &blk)
6
+ @loop = loop
7
+ @callback = callback || blk
8
+
9
+ idle_ptr = ::Libuv::Ext.create_handle(:uv_idle)
10
+ error = check_result(::Libuv::Ext.idle_init(loop.handle, idle_ptr))
11
+
12
+ super(idle_ptr, error)
13
+ end
14
+
15
+ def start
16
+ return if @closed
17
+ error = check_result ::Libuv::Ext.idle_start(handle, callback(:on_idle))
18
+ reject(error) if error
19
+ end
20
+
21
+ def stop
22
+ return if @closed
23
+ error = check_result ::Libuv::Ext.idle_stop(handle)
24
+ reject(error) if error
25
+ end
26
+
27
+ def progress(callback = nil, &blk)
28
+ @callback = callback || blk
29
+ end
30
+
31
+
32
+ private
33
+
34
+
35
+ def on_idle(handle, status)
36
+ e = check_result(status)
37
+
38
+ if e
39
+ reject(e)
40
+ else
41
+ begin
42
+ @callback.call
43
+ rescue Exception => e
44
+ @loop.log :error, :idle_cb, e
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,34 @@
1
+ require 'thread_safe'
2
+ require 'set'
3
+
4
+ module Libuv
5
+ module Listener
6
+
7
+
8
+ private
9
+
10
+
11
+ CALLBACKS = ThreadSafe::Cache.new
12
+
13
+
14
+ def callbacks
15
+ @callbacks ||= Set.new
16
+ end
17
+
18
+ def callback(name)
19
+ const_name = "#{name}_#{object_id}".to_sym
20
+ unless CALLBACKS[const_name]
21
+ callbacks << const_name
22
+ CALLBACKS[const_name] = method(name)
23
+ end
24
+ CALLBACKS[const_name]
25
+ end
26
+
27
+ def clear_callbacks
28
+ callbacks.each do |name|
29
+ CALLBACKS.delete(name)
30
+ end
31
+ callbacks.clear
32
+ end
33
+ end
34
+ end
data/lib/libuv/loop.rb ADDED
@@ -0,0 +1,310 @@
1
+ require 'thread'
2
+
3
+ module Libuv
4
+ class Loop
5
+ include Resource, Assertions
6
+
7
+
8
+ module ClassMethods
9
+ # Get default loop
10
+ #
11
+ # @return [::Libuv::Loop]
12
+ def default
13
+ create(::Libuv::Ext.default_loop)
14
+ end
15
+
16
+ # Create new loop
17
+ #
18
+ # @return [::Libuv::Loop]
19
+ def new
20
+ create(::Libuv::Ext.loop_new)
21
+ end
22
+
23
+ # Create custom loop from pointer
24
+ #
25
+ # @return [::Libuv::Loop]
26
+ def create(pointer)
27
+ allocate.tap { |i| i.send(:initialize, FFI::AutoPointer.new(pointer, ::Libuv::Ext.method(:loop_delete))) }
28
+ end
29
+ end
30
+ extend ClassMethods
31
+
32
+
33
+ # Initialize a loop using an FFI::Pointer
34
+ #
35
+ # @return [::Libuv::Loop]
36
+ def initialize(pointer) # :notnew:
37
+ @pointer = pointer
38
+ @loop = self
39
+
40
+ # Create an async call for scheduling work from other threads
41
+ @run_queue = Queue.new
42
+ @queue_proc = proc do
43
+ # Rubinius fix for promises
44
+ # Anything calling schedule will
45
+ # be delayed a tick outside of promise callbacks on rubinius (see https://github.com/ffi/ffi/issues/279)
46
+ #@reactor_thread = Thread.current # Should work in rubinius 2.0
47
+
48
+ # ensure we only execute what was required for this tick
49
+ length = @run_queue.length
50
+ length.times do
51
+ begin
52
+ run = @run_queue.pop true # pop non-block
53
+ run.call
54
+ rescue Exception => e
55
+ @loop.log :error, :next_tick_cb, e
56
+ end
57
+ end
58
+ end
59
+ @process_queue = SimpleAsync.new(@loop, @queue_proc)
60
+
61
+ # Create a next tick timer
62
+ @next_tick = @loop.timer do
63
+ @next_tick_scheduled = false
64
+ @queue_proc.call
65
+ end
66
+
67
+ # Create an async call for ending the loop
68
+ @stop_loop = SimpleAsync.new @loop do
69
+ @process_queue.close
70
+ @stop_loop.close
71
+ @next_tick.close
72
+
73
+ ::Libuv::Ext.stop(@pointer)
74
+ end
75
+ end
76
+
77
+ def handle; @pointer; end
78
+
79
+ # Run the actual event loop. This method will block for the duration of event loop unless
80
+ # it is run inside an existing event loop, where a new thread will be created for it
81
+ #
82
+ # @param run_type [:UV_RUN_DEFAULT, :UV_RUN_ONCE, :UV_RUN_NOWAIT]
83
+ # @yieldparam promise [::Libuv::Loop] Yields a promise that can be used for logging unhandled
84
+ # exceptions on the loop.
85
+ # @return [::Libuv::Q::Promise]
86
+ def run(run_type = :UV_RUN_DEFAULT)
87
+ @loop_notify = @loop.defer
88
+
89
+ begin
90
+ @reactor_thread = Thread.current
91
+ yield @loop_notify.promise if block_given?
92
+ ::Libuv::Ext.run(@pointer, run_type) # This is blocking
93
+ ensure
94
+ @reactor_thread = nil
95
+ @run_queue.clear
96
+ end
97
+
98
+ @loop
99
+ end
100
+
101
+
102
+ # Creates a deferred result object for where the result of an operation may only be returned
103
+ # at some point in the future or is being processed on a different thread (thread safe)
104
+ #
105
+ # @return [::Libuv::Q::Deferred]
106
+ def defer
107
+ Q.defer(@loop)
108
+ end
109
+
110
+ # Combines multiple promises into a single promise that is resolved when all of the input
111
+ # promises are resolved. (thread safe)
112
+ #
113
+ # @param [*Promise] Promises a number of promises that will be combined into a single promise
114
+ # @return [Promise] Returns a single promise that will be resolved with an array of values,
115
+ # each value corresponding to the promise at the same index in the `promises` array. If any of
116
+ # the promises is resolved with a rejection, this resulting promise will be resolved with the
117
+ # same rejection.
118
+ def all(*promises)
119
+ Q.all(@loop, *promises)
120
+ end
121
+
122
+ #
123
+ # Combines multiple promises into a single promise that is resolved when any of the input
124
+ # promises are resolved.
125
+ #
126
+ # @param [*Promise] Promises a number of promises that will be combined into a single promise
127
+ # @return [Promise] Returns a single promise
128
+ def any(*promises)
129
+ Q.any(@loop, *promises)
130
+ end
131
+
132
+ #
133
+ # Combines multiple promises into a single promise that is resolved when all of the input
134
+ # promises are resolved or rejected.
135
+ #
136
+ # @param [*Promise] Promises a number of promises that will be combined into a single promise
137
+ # @return [Promise] Returns a single promise that will be resolved with an array of values,
138
+ # each [result, wasResolved] value pair corresponding to a at the same index in the `promises` array.
139
+ def finally(*promises)
140
+ Q.finally(@loop, *promises)
141
+ end
142
+
143
+
144
+ # forces loop time update, useful for getting more granular times
145
+ #
146
+ # @return nil
147
+ def update_time
148
+ ::Libuv::Ext.update_time(@pointer)
149
+ end
150
+
151
+ # Get current time in microseconds
152
+ #
153
+ # @return [Fixnum]
154
+ def now
155
+ ::Libuv::Ext.now(@pointer)
156
+ end
157
+
158
+ # Lookup an error code and return is as an error object
159
+ #
160
+ # @param err [Integer] The error code to look up.
161
+ # @return [::Libuv::Error]
162
+ def lookup_error(err)
163
+ name = ::Libuv::Ext.err_name(err)
164
+ msg = ::Libuv::Ext.strerror(err)
165
+
166
+ ::Libuv::Error.const_get(name.to_sym).new(msg)
167
+ rescue Exception => e
168
+ @loop.log :warn, :error_lookup_failed, e
169
+ ::Libuv::Error::UNKNOWN.new("error lookup failed for code #{err} #{name} #{msg}")
170
+ end
171
+
172
+ # Get a new TCP instance
173
+ #
174
+ # @return [::Libuv::TCP]
175
+ def tcp
176
+ TCP.new(@loop)
177
+ end
178
+
179
+ # Get a new UDP instance
180
+ #
181
+ # @return [::Libuv::UDP]
182
+ def udp
183
+ UDP.new(@loop)
184
+ end
185
+
186
+ # Get a new TTY instance
187
+ #
188
+ # @param fileno [Integer] Integer file descriptor of a tty device
189
+ # @param readable [true, false] Boolean indicating if TTY is readable
190
+ # @return [::Libuv::TTY]
191
+ def tty(fileno, readable = false)
192
+ assert_type(Integer, fileno, "io#fileno must return an integer file descriptor, #{fileno.inspect} given")
193
+
194
+ TTY.new(@loop, fileno, readable)
195
+ end
196
+
197
+ # Get a new Pipe instance
198
+ #
199
+ # @param ipc [true, false]
200
+ # indicate if a handle will be used for ipc, useful for sharing tcp socket between processes
201
+ # @return [::Libuv::Pipe]
202
+ def pipe(ipc = false)
203
+ Pipe.new(@loop, ipc)
204
+ end
205
+
206
+ # Get a new timer instance
207
+ #
208
+ # @return [::Libuv::Timer]
209
+ def timer(callback = nil, &blk)
210
+ Timer.new(@loop, callback || blk)
211
+ end
212
+
213
+ # Get a new Prepare handle
214
+ #
215
+ # @return [::Libuv::Prepare]
216
+ def prepare
217
+ Prepare.new(@loop)
218
+ end
219
+
220
+ # Get a new Check handle
221
+ #
222
+ # @return [::Libuv::Check]
223
+ def check
224
+ Check.new(@loop)
225
+ end
226
+
227
+ # Get a new Idle handle
228
+ #
229
+ # @return [::Libuv::Idle]
230
+ def idle(callback = nil, &block)
231
+ Idle.new(@loop, callback || block)
232
+ end
233
+
234
+ # Get a new Async handle
235
+ #
236
+ # @return [::Libuv::Async]
237
+ def async(callback = nil, &block)
238
+ callback ||= block
239
+ handle = Async.new(@loop)
240
+ handle.progress callback if callback
241
+ handle
242
+ end
243
+
244
+ # Queue some work for processing in the libuv thread pool
245
+ #
246
+ # @return [::Libuv::Work]
247
+ # @raise [ArgumentError] if block is not given
248
+ def work(callback = nil, &block)
249
+ Work.new(@loop, callback || block) # Work is a promise object
250
+ end
251
+
252
+ # Get a new FSEvent instance
253
+ #
254
+ # @return [::Libuv::FSEvent]
255
+ def fs_event(path)
256
+ assert_type(String, path)
257
+ FSEvent.new(@loop, path)
258
+ end
259
+
260
+
261
+ # Schedule some work to be processed on the event loop (thread safe)
262
+ #
263
+ # @return [nil]
264
+ def schedule(&block)
265
+ assert_block(block)
266
+
267
+ if @reactor_thread == Thread.current
268
+ block.call
269
+ else
270
+ @run_queue << block
271
+ @process_queue.call
272
+ end
273
+ end
274
+
275
+ # Schedule some work to be processed in the next iteration of the event loop (thread safe)
276
+ #
277
+ # @return [nil]
278
+ def next_tick(&block)
279
+ assert_block(block)
280
+
281
+ @run_queue << block
282
+ if @reactor_thread == Thread.current
283
+ # Create a next tick timer
284
+ if not @next_tick_scheduled
285
+ @next_tick.start(0)
286
+ @next_tick_scheduled = true
287
+ end
288
+ else
289
+ @process_queue.call
290
+ end
291
+ end
292
+
293
+ # Notifies the loop there was an event that should be logged
294
+ #
295
+ # @param level [Symbol] the error level (info, warn, error etc)
296
+ # @param id [Object] some kind of identifying information
297
+ # @param *args [*args] any additional information
298
+ # @return [nil]
299
+ def log(level, id, *args)
300
+ @loop_notify.notify(level, id, *args)
301
+ end
302
+
303
+ # Closes handles opened by the loop class and completes the current loop iteration (thread safe)
304
+ #
305
+ # @return [nil]
306
+ def stop
307
+ @stop_loop.call
308
+ end
309
+ end
310
+ end
data/lib/libuv/net.rb ADDED
@@ -0,0 +1,38 @@
1
+ require 'socket'
2
+
3
+ module Libuv
4
+ module Net
5
+
6
+
7
+ IP_ARGUMENT_ERROR = "ip must be a String".freeze # Arguments specifying an IP address
8
+ PORT_ARGUMENT_ERROR = "port must be an Integer".freeze # Arguments specifying an IP port
9
+
10
+
11
+ private
12
+
13
+
14
+ def get_sockaddr_and_len
15
+ sockaddr = FFI::MemoryPointer.new(UV::Sockaddr)
16
+ len = FFI::MemoryPointer.new(:int)
17
+ len.put_int(0, UV::Sockaddr.size)
18
+ [sockaddr, len]
19
+ end
20
+
21
+ def get_ip_and_port(sockaddr, len=nil)
22
+ if sockaddr[:sa_family] == Socket::Constants::AF_INET6
23
+ len ||= Socket::Constants::INET6_ADDRSTRLEN
24
+ sockaddr_in6 = UV::SockaddrIn6.new(sockaddr.pointer)
25
+ ip_ptr = FFI::MemoryPointer.new(:char, len)
26
+ ::Libuv::Ext.ip6_name(sockaddr_in6, ip_ptr, len)
27
+ port = ::Libuv::Ext.ntohs(sockaddr_in6[:sin6_port])
28
+ else
29
+ len ||= Socket::Constants::INET_ADDRSTRLEN
30
+ sockaddr_in = UV::SockaddrIn.new(sockaddr.pointer)
31
+ ip_ptr = FFI::MemoryPointer.new(:char, len)
32
+ ::Libuv::Ext.ip4_name(sockaddr_in, ip_ptr, len)
33
+ port = ::Libuv::Ext.ntohs(sockaddr_in[:sin_port])
34
+ end
35
+ [ip_ptr.read_string, port]
36
+ end
37
+ end
38
+ end
data/lib/libuv/pipe.rb ADDED
@@ -0,0 +1,97 @@
1
+ module Libuv
2
+ class Pipe < Handle
3
+ include Stream
4
+
5
+
6
+ def initialize(loop, ipc, acceptor = nil)
7
+ @loop, @ipc = loop, ipc
8
+
9
+ pipe_ptr = ::Libuv::Ext.create_handle(:uv_pipe)
10
+ error = check_result(::Libuv::Ext.pipe_init(loop.handle, pipe_ptr, ipc ? 1 : 0))
11
+ error = check_result(::Libuv::Ext.accept(acceptor, pipe_ptr)) if acceptor && error.nil?
12
+
13
+ super(pipe_ptr, error)
14
+ end
15
+
16
+ def bind(name, callback = nil, &blk)
17
+ @on_listen = callback || blk
18
+ assert_type(String, name, "name must be a String")
19
+ name = windows_path name if FFI::Platform.windows?
20
+
21
+ error = check_result ::Libuv::Ext.pipe_bind(handle, name)
22
+ reject(error) if error
23
+ end
24
+
25
+ def accept(callback = nil, &blk)
26
+ pipe = nil
27
+ begin
28
+ pipe = Pipe.new(loop, @ipc, handle)
29
+ rescue Exception => e
30
+ @loop.log :info, :pipe_accept_failed, e
31
+ end
32
+ if pipe
33
+ begin
34
+ (callback || blk).call(pipe)
35
+ rescue Exception => e
36
+ @loop.log :error, :pipe_accept_cb, e
37
+ end
38
+ end
39
+ nil
40
+ end
41
+
42
+ def open(fileno, callback = nil, &blk)
43
+ @callback = callback || blk
44
+ assert_type(Integer, fileno, "io#fileno must return an integer file descriptor")
45
+ begin
46
+ check_result! ::Libuv::Ext.pipe_open(handle, fileno)
47
+
48
+ # Emulate on_connect behavior
49
+ begin
50
+ @callback.call(self)
51
+ rescue Exception => e
52
+ @loop.log :error, :pipe_connect_cb, e
53
+ end
54
+ rescue Exception => e
55
+ reject(e)
56
+ end
57
+ end
58
+
59
+ def connect(name, callback = nil, &blk)
60
+ @callback = callback || blk
61
+ assert_type(String, name, "name must be a String")
62
+ begin
63
+ name = windows_path name if FFI::Platform.windows?
64
+ ::Libuv::Ext.pipe_connect(::Libuv::Ext.create_request(:uv_connect), handle, name, callback(:on_connect))
65
+ rescue Exception => e
66
+ reject(e)
67
+ end
68
+ end
69
+
70
+ # Windows only
71
+ def pending_instances=(count)
72
+ assert_type(Integer, count, "count must be an Integer")
73
+ ::Libuv::Ext.pipe_pending_instances(handle, count)
74
+ end
75
+
76
+
77
+ private
78
+
79
+
80
+ def on_connect(req, status)
81
+ ::Libuv::Ext.free(req)
82
+ begin
83
+ @callback.call(self)
84
+ rescue Exception => e
85
+ @loop.log :error, :pipe_connect_cb, e
86
+ end
87
+ end
88
+
89
+ def windows_path(name)
90
+ # test for \\\\.\\pipe
91
+ if not name =~ /(\/|\\){2}\.(\/|\\)pipe/i
92
+ name = ::File.join("\\\\.\\pipe", name)
93
+ end
94
+ name.gsub("/", "\\")
95
+ end
96
+ end
97
+ end