libuv 0.10.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.
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