libuv 0.11.4 → 0.11.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +6 -14
  2. data/.gitignore +17 -17
  3. data/.gitmodules +3 -3
  4. data/.rspec +1 -1
  5. data/.travis.yml +16 -16
  6. data/Gemfile +4 -4
  7. data/LICENSE +23 -23
  8. data/README.md +82 -82
  9. data/Rakefile +31 -31
  10. data/lib/libuv.rb +54 -54
  11. data/lib/libuv/async.rb +47 -47
  12. data/lib/libuv/check.rb +55 -55
  13. data/lib/libuv/dns.rb +85 -85
  14. data/lib/libuv/error.rb +73 -73
  15. data/lib/libuv/ext/ext.rb +258 -258
  16. data/lib/libuv/ext/platform/darwin_x64.rb +23 -23
  17. data/lib/libuv/ext/platform/linux.rb +7 -7
  18. data/lib/libuv/ext/platform/unix.rb +29 -29
  19. data/lib/libuv/ext/platform/windows.rb +40 -40
  20. data/lib/libuv/ext/tasks.rb +29 -29
  21. data/lib/libuv/ext/tasks/mac.rb +23 -23
  22. data/lib/libuv/ext/tasks/unix.rb +23 -23
  23. data/lib/libuv/ext/tasks/win.rb +11 -11
  24. data/lib/libuv/ext/types.rb +238 -238
  25. data/lib/libuv/file.rb +191 -191
  26. data/lib/libuv/filesystem.rb +232 -232
  27. data/lib/libuv/fs_event.rb +31 -31
  28. data/lib/libuv/handle.rb +85 -85
  29. data/lib/libuv/idle.rb +56 -56
  30. data/lib/libuv/loop.rb +387 -387
  31. data/lib/libuv/mixins/assertions.rb +23 -23
  32. data/lib/libuv/mixins/fs_checks.rb +55 -55
  33. data/lib/libuv/mixins/listener.rb +34 -34
  34. data/lib/libuv/mixins/net.rb +40 -40
  35. data/lib/libuv/mixins/resource.rb +27 -27
  36. data/lib/libuv/mixins/stream.rb +154 -154
  37. data/lib/libuv/pipe.rb +197 -197
  38. data/lib/libuv/prepare.rb +56 -56
  39. data/lib/libuv/signal.rb +51 -51
  40. data/lib/libuv/tcp.rb +317 -317
  41. data/lib/libuv/timer.rb +91 -91
  42. data/lib/libuv/tty.rb +37 -37
  43. data/lib/libuv/udp.rb +224 -224
  44. data/lib/libuv/version.rb +3 -3
  45. data/lib/libuv/work.rb +75 -75
  46. data/libuv.gemspec +56 -56
  47. data/spec/async_spec.rb +60 -60
  48. data/spec/cpu_spec.rb +10 -10
  49. data/spec/defer_spec.rb +980 -980
  50. data/spec/dns_spec.rb +90 -90
  51. data/spec/filesystem_spec.rb +124 -124
  52. data/spec/idle_spec.rb +56 -56
  53. data/spec/pipe_spec.rb +160 -160
  54. data/spec/tcp_spec.rb +267 -267
  55. metadata +29 -21
@@ -1,31 +1,31 @@
1
- module Libuv
2
- class FSEvent < Handle
3
-
4
-
5
- EVENTS = {1 => :rename, 2 => :change}.freeze
6
-
7
-
8
- def initialize(loop, path)
9
- @loop = loop
10
-
11
- fs_event_ptr = ::Libuv::Ext.create_handle(:uv_fs_event)
12
- error = check_result ::Libuv::Ext.fs_event_init(loop.handle, fs_event_ptr, path, callback(:on_fs_event), 0)
13
-
14
- super(fs_event_ptr, error)
15
- end
16
-
17
-
18
- private
19
-
20
-
21
- def on_fs_event(handle, filename, events, status)
22
- e = check_result(status)
23
-
24
- if e
25
- reject(e)
26
- else
27
- defer.notify(filename, EVENTS[events]) # notify of a change
28
- end
29
- end
30
- end
31
- end
1
+ module Libuv
2
+ class FSEvent < Handle
3
+
4
+
5
+ EVENTS = {1 => :rename, 2 => :change}.freeze
6
+
7
+
8
+ def initialize(loop, path)
9
+ @loop = loop
10
+
11
+ fs_event_ptr = ::Libuv::Ext.create_handle(:uv_fs_event)
12
+ error = check_result ::Libuv::Ext.fs_event_init(loop.handle, fs_event_ptr, path, callback(:on_fs_event), 0)
13
+
14
+ super(fs_event_ptr, error)
15
+ end
16
+
17
+
18
+ private
19
+
20
+
21
+ def on_fs_event(handle, filename, events, status)
22
+ e = check_result(status)
23
+
24
+ if e
25
+ reject(e)
26
+ else
27
+ defer.notify(filename, EVENTS[events]) # notify of a change
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,86 +1,86 @@
1
- module Libuv
2
- class Handle < Q::DeferredPromise
3
- include Assertions, Resource, Listener
4
-
5
-
6
- attr_accessor :storage # A place for general storage
7
- attr_reader :closed
8
- attr_reader :loop
9
-
10
-
11
- def initialize(pointer, error)
12
- @pointer = pointer
13
-
14
- # Initialise the promise
15
- super(loop, loop.defer)
16
-
17
- # clean up on init error (always raise here)
18
- if error
19
- ::Libuv::Ext.free(pointer)
20
- defer.reject(error)
21
- @closed = true
22
- raise error
23
- end
24
- end
25
-
26
- # Public: Increment internal ref counter for the handle on the loop. Useful for
27
- # extending the loop with custom watchers that need to make loop not stop
28
- #
29
- # Returns self
30
- def ref
31
- return if @closed
32
- ::Libuv::Ext.ref(handle)
33
- end
34
-
35
- # Public: Decrement internal ref counter for the handle on the loop, useful to stop
36
- # loop even when there are outstanding open handles
37
- #
38
- # Returns self
39
- def unref
40
- return if @closed
41
- ::Libuv::Ext.unref(handle)
42
- end
43
-
44
- def close
45
- return if @closed
46
- @closed = true
47
- Libuv::Ext.close(handle, callback(:on_close))
48
- end
49
-
50
- def active?
51
- ::Libuv::Ext.is_active(handle) > 0
52
- end
53
-
54
- def closing?
55
- ::Libuv::Ext.is_closing(handle) > 0
56
- end
57
-
58
-
59
- protected
60
-
61
-
62
- def handle; @pointer; end
63
- def defer; @defer; end
64
-
65
-
66
- private
67
-
68
-
69
- # Clean up and throw an error
70
- def reject(reason)
71
- @close_error = reason
72
- close
73
- end
74
-
75
- def on_close(pointer)
76
- ::Libuv::Ext.free(pointer)
77
- clear_callbacks
78
-
79
- if @close_error
80
- defer.reject(@close_error)
81
- else
82
- defer.resolve(nil)
83
- end
84
- end
85
- end
1
+ module Libuv
2
+ class Handle < Q::DeferredPromise
3
+ include Assertions, Resource, Listener
4
+
5
+
6
+ attr_accessor :storage # A place for general storage
7
+ attr_reader :closed
8
+ attr_reader :loop
9
+
10
+
11
+ def initialize(pointer, error)
12
+ @pointer = pointer
13
+
14
+ # Initialise the promise
15
+ super(loop, loop.defer)
16
+
17
+ # clean up on init error (always raise here)
18
+ if error
19
+ ::Libuv::Ext.free(pointer)
20
+ defer.reject(error)
21
+ @closed = true
22
+ raise error
23
+ end
24
+ end
25
+
26
+ # Public: Increment internal ref counter for the handle on the loop. Useful for
27
+ # extending the loop with custom watchers that need to make loop not stop
28
+ #
29
+ # Returns self
30
+ def ref
31
+ return if @closed
32
+ ::Libuv::Ext.ref(handle)
33
+ end
34
+
35
+ # Public: Decrement internal ref counter for the handle on the loop, useful to stop
36
+ # loop even when there are outstanding open handles
37
+ #
38
+ # Returns self
39
+ def unref
40
+ return if @closed
41
+ ::Libuv::Ext.unref(handle)
42
+ end
43
+
44
+ def close
45
+ return if @closed
46
+ @closed = true
47
+ Libuv::Ext.close(handle, callback(:on_close))
48
+ end
49
+
50
+ def active?
51
+ ::Libuv::Ext.is_active(handle) > 0
52
+ end
53
+
54
+ def closing?
55
+ ::Libuv::Ext.is_closing(handle) > 0
56
+ end
57
+
58
+
59
+ protected
60
+
61
+
62
+ def handle; @pointer; end
63
+ def defer; @defer; end
64
+
65
+
66
+ private
67
+
68
+
69
+ # Clean up and throw an error
70
+ def reject(reason)
71
+ @close_error = reason
72
+ close
73
+ end
74
+
75
+ def on_close(pointer)
76
+ ::Libuv::Ext.free(pointer)
77
+ clear_callbacks
78
+
79
+ if @close_error
80
+ defer.reject(@close_error)
81
+ else
82
+ defer.resolve(nil)
83
+ end
84
+ end
85
+ end
86
86
  end
@@ -1,56 +1,56 @@
1
- module Libuv
2
- class Idle < Handle
3
-
4
-
5
- # @param loop [::Libuv::Loop] loop this idle handler will be associated
6
- # @param callback [Proc] callback to be called when the loop is idle
7
- def initialize(loop, callback = nil, &blk)
8
- @loop = loop
9
- @callback = callback || blk
10
-
11
- idle_ptr = ::Libuv::Ext.create_handle(:uv_idle)
12
- error = check_result(::Libuv::Ext.idle_init(loop.handle, idle_ptr))
13
-
14
- super(idle_ptr, error)
15
- end
16
-
17
- # Enables the idle handler.
18
- def start
19
- return if @closed
20
- error = check_result ::Libuv::Ext.idle_start(handle, callback(:on_idle))
21
- reject(error) if error
22
- end
23
-
24
- # Disables the idle handler.
25
- def stop
26
- return if @closed
27
- error = check_result ::Libuv::Ext.idle_stop(handle)
28
- reject(error) if error
29
- end
30
-
31
- # Used to update the callback that will be triggered on idle
32
- #
33
- # @param callback [Proc] the callback to be called on idle trigger
34
- def progress(callback = nil, &blk)
35
- @callback = callback || blk
36
- end
37
-
38
-
39
- private
40
-
41
-
42
- def on_idle(handle, status)
43
- e = check_result(status)
44
-
45
- if e
46
- reject(e)
47
- else
48
- begin
49
- @callback.call
50
- rescue Exception => e
51
- @loop.log :error, :idle_cb, e
52
- end
53
- end
54
- end
55
- end
56
- end
1
+ module Libuv
2
+ class Idle < Handle
3
+
4
+
5
+ # @param loop [::Libuv::Loop] loop this idle handler will be associated
6
+ # @param callback [Proc] callback to be called when the loop is idle
7
+ def initialize(loop, callback = nil, &blk)
8
+ @loop = loop
9
+ @callback = callback || blk
10
+
11
+ idle_ptr = ::Libuv::Ext.create_handle(:uv_idle)
12
+ error = check_result(::Libuv::Ext.idle_init(loop.handle, idle_ptr))
13
+
14
+ super(idle_ptr, error)
15
+ end
16
+
17
+ # Enables the idle handler.
18
+ def start
19
+ return if @closed
20
+ error = check_result ::Libuv::Ext.idle_start(handle, callback(:on_idle))
21
+ reject(error) if error
22
+ end
23
+
24
+ # Disables the idle handler.
25
+ def stop
26
+ return if @closed
27
+ error = check_result ::Libuv::Ext.idle_stop(handle)
28
+ reject(error) if error
29
+ end
30
+
31
+ # Used to update the callback that will be triggered on idle
32
+ #
33
+ # @param callback [Proc] the callback to be called on idle trigger
34
+ def progress(callback = nil, &blk)
35
+ @callback = callback || blk
36
+ end
37
+
38
+
39
+ private
40
+
41
+
42
+ def on_idle(handle, status)
43
+ e = check_result(status)
44
+
45
+ if e
46
+ reject(e)
47
+ else
48
+ begin
49
+ @callback.call
50
+ rescue Exception => e
51
+ @loop.log :error, :idle_cb, e
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,387 +1,387 @@
1
- require 'thread'
2
-
3
- module Libuv
4
- class Loop
5
- include Resource, Assertions
6
-
7
-
8
- LOOPS = ThreadSafe::Cache.new
9
-
10
-
11
- module ClassMethods
12
- # Get default loop
13
- #
14
- # @return [::Libuv::Loop]
15
- def default
16
- return @default ||= create(::Libuv::Ext.default_loop)
17
- end
18
-
19
- # Create new Libuv loop
20
- #
21
- # @return [::Libuv::Loop]
22
- def new
23
- return create(::Libuv::Ext.loop_new)
24
- end
25
-
26
- # Build a Ruby Libuv loop from an existing loop pointer
27
- #
28
- # @return [::Libuv::Loop]
29
- def create(pointer)
30
- allocate.tap { |i| i.send(:initialize, FFI::AutoPointer.new(pointer, ::Libuv::Ext.method(:loop_delete))) }
31
- end
32
-
33
- # Checks for the existence of a loop on the current thread
34
- #
35
- # @return [::Libuv::Loop | nil]
36
- def current
37
- LOOPS[Thread.current]
38
- end
39
- end
40
- extend ClassMethods
41
-
42
-
43
- # Initialize a loop using an FFI::Pointer to a libuv loop
44
- def initialize(pointer) # :notnew:
45
- @pointer = pointer
46
- @loop = self
47
-
48
- # Create an async call for scheduling work from other threads
49
- @run_queue = Queue.new
50
- @queue_proc = proc do
51
- # ensure we only execute what was required for this tick
52
- length = @run_queue.length
53
- length.times do
54
- begin
55
- run = @run_queue.pop true # pop non-block
56
- run.call
57
- rescue Exception => e
58
- @loop.log :error, :next_tick_cb, e
59
- end
60
- end
61
- end
62
- @process_queue = Async.new(@loop, @queue_proc)
63
-
64
- # Create a next tick timer
65
- @next_tick = @loop.timer do
66
- @next_tick_scheduled = false
67
- @queue_proc.call
68
- end
69
-
70
- # Create an async call for ending the loop
71
- @stop_loop = Async.new @loop do
72
- LOOPS.delete(@reactor_thread)
73
- @reactor_thread = nil
74
- @process_queue.close
75
- @stop_loop.close
76
- @next_tick.close
77
-
78
- ::Libuv::Ext.stop(@pointer)
79
- end
80
- end
81
-
82
- def handle; @pointer; end
83
-
84
- # Run the actual event loop. This method will block until the loop is stopped.
85
- #
86
- # @param run_type [:UV_RUN_DEFAULT, :UV_RUN_ONCE, :UV_RUN_NOWAIT]
87
- # @yieldparam promise [::Libuv::Q::Promise] Yields a promise that can be used for logging unhandled
88
- # exceptions on the loop.
89
- def run(run_type = :UV_RUN_DEFAULT)
90
- if @reactor_thread.nil?
91
- @loop_notify = @loop.defer
92
-
93
- begin
94
- @reactor_thread = Thread.current
95
- LOOPS[@reactor_thread] = @loop
96
- yield @loop_notify.promise if block_given?
97
- ::Libuv::Ext.run(@pointer, run_type) # This is blocking
98
- ensure
99
- @reactor_thread = nil
100
- @run_queue.clear
101
- end
102
- elsif block_given?
103
- schedule { yield @loop_notify.promise }
104
- end
105
- @loop
106
- end
107
-
108
-
109
- # Creates a deferred result object for where the result of an operation may only be returned
110
- # at some point in the future or is being processed on a different thread (thread safe)
111
- #
112
- # @return [::Libuv::Q::Deferred]
113
- def defer
114
- Q.defer(@loop)
115
- end
116
-
117
- # Combines multiple promises into a single promise that is resolved when all of the input
118
- # promises are resolved. (thread safe)
119
- #
120
- # @param *promises [::Libuv::Q::Promise] a number of promises that will be combined into a single promise
121
- # @return [::Libuv::Q::Promise] Returns a single promise that will be resolved with an array of values,
122
- # each value corresponding to the promise at the same index in the `promises` array. If any of
123
- # the promises is resolved with a rejection, this resulting promise will be resolved with the
124
- # same rejection.
125
- def all(*promises)
126
- Q.all(@loop, *promises)
127
- end
128
-
129
- #
130
- # Combines multiple promises into a single promise that is resolved when any of the input
131
- # promises are resolved.
132
- #
133
- # @param *promises [::Libuv::Q::Promise] a number of promises that will be combined into a single promise
134
- # @return [::Libuv::Q::Promise] Returns a single promise
135
- def any(*promises)
136
- Q.any(@loop, *promises)
137
- end
138
-
139
- #
140
- # Combines multiple promises into a single promise that is resolved when all of the input
141
- # promises are resolved or rejected.
142
- #
143
- # @param *promises [::Libuv::Q::Promise] a number of promises that will be combined into a single promise
144
- # @return [::Libuv::Q::Promise] Returns a single promise that will be resolved with an array of values,
145
- # each [result, wasResolved] value pair corresponding to a at the same index in the `promises` array.
146
- def finally(*promises)
147
- Q.finally(@loop, *promises)
148
- end
149
-
150
-
151
- # forces loop time update, useful for getting more granular times
152
- #
153
- # @return nil
154
- def update_time
155
- ::Libuv::Ext.update_time(@pointer)
156
- end
157
-
158
- # Get current time in milliseconds
159
- #
160
- # @return [Fixnum]
161
- def now
162
- ::Libuv::Ext.now(@pointer)
163
- end
164
-
165
- # Lookup an error code and return is as an error object
166
- #
167
- # @param err [Integer] The error code to look up.
168
- # @return [::Libuv::Error]
169
- def lookup_error(err)
170
- name = ::Libuv::Ext.err_name(err)
171
- msg = ::Libuv::Ext.strerror(err)
172
-
173
- ::Libuv::Error.const_get(name.to_sym).new(msg)
174
- rescue Exception => e
175
- @loop.log :warn, :error_lookup_failed, e
176
- ::Libuv::Error::UNKNOWN.new("error lookup failed for code #{err} #{name} #{msg}")
177
- end
178
-
179
- # Get a new TCP instance
180
- #
181
- # @return [::Libuv::TCP]
182
- def tcp
183
- TCP.new(@loop)
184
- end
185
-
186
- # Get a new UDP instance
187
- #
188
- # @return [::Libuv::UDP]
189
- def udp
190
- UDP.new(@loop)
191
- end
192
-
193
- # Get a new TTY instance
194
- #
195
- # @param fileno [Integer] Integer file descriptor of a tty device
196
- # @param readable [true, false] Boolean indicating if TTY is readable
197
- # @return [::Libuv::TTY]
198
- def tty(fileno, readable = false)
199
- assert_type(Integer, fileno, "io#fileno must return an integer file descriptor, #{fileno.inspect} given")
200
-
201
- TTY.new(@loop, fileno, readable)
202
- end
203
-
204
- # Get a new Pipe instance
205
- #
206
- # @param ipc [true, false] indicate if a handle will be used for ipc, useful for sharing tcp socket between processes
207
- # @return [::Libuv::Pipe]
208
- def pipe(ipc = false)
209
- Pipe.new(@loop, ipc)
210
- end
211
-
212
- # Get a new timer instance
213
- #
214
- # @param callback [Proc] the callback to be called on timer trigger
215
- # @return [::Libuv::Timer]
216
- def timer(callback = nil, &blk)
217
- Timer.new(@loop, callback || blk)
218
- end
219
-
220
- # Get a new Prepare handle
221
- #
222
- # @return [::Libuv::Prepare]
223
- def prepare(callback = nil, &blk)
224
- Prepare.new(@loop, callback || blk)
225
- end
226
-
227
- # Get a new Check handle
228
- #
229
- # @return [::Libuv::Check]
230
- def check(callback = nil, &blk)
231
- Check.new(@loop, callback || blk)
232
- end
233
-
234
- # Get a new Idle handle
235
- #
236
- # @param callback [Proc] the callback to be called on idle trigger
237
- # @return [::Libuv::Idle]
238
- def idle(callback = nil, &block)
239
- Idle.new(@loop, callback || block)
240
- end
241
-
242
- # Get a new Async handle
243
- #
244
- # @return [::Libuv::Async]
245
- def async(callback = nil, &block)
246
- callback ||= block
247
- handle = Async.new(@loop)
248
- handle.progress callback if callback
249
- handle
250
- end
251
-
252
- # Get a new signal handler
253
- #
254
- # @return [::Libuv::Signal]
255
- def signal(signum = nil, callback = nil, &block)
256
- callback ||= block
257
- handle = Signal.new(@loop)
258
- handle.progress callback if callback
259
- handle.start(signum) if signum
260
- handle
261
- end
262
-
263
- # Queue some work for processing in the libuv thread pool
264
- #
265
- # @param callback [Proc] the callback to be called in the thread pool
266
- # @return [::Libuv::Work]
267
- # @raise [ArgumentError] if block is not given
268
- def work(callback = nil, &block)
269
- callback ||= block
270
- assert_block(callback)
271
- Work.new(@loop, callback) # Work is a promise object
272
- end
273
-
274
- # Lookup a hostname
275
- #
276
- # @param hostname [String] the domain name to lookup
277
- # @param port [Integer, String] the service being connected too
278
- # @param callback [Proc] the callback to be called on success
279
- # @return [::Libuv::Dns]
280
- def lookup(hostname, hint = :IPv4, port = 9, &block)
281
- dns = Dns.new(@loop, hostname, port, hint) # Work is a promise object
282
- dns.then block if block_given?
283
- dns
284
- end
285
-
286
- # Get a new FSEvent instance
287
- #
288
- # @param path [String] the path to the file or folder for watching
289
- # @return [::Libuv::FSEvent]
290
- # @raise [ArgumentError] if path is not a string
291
- def fs_event(path)
292
- assert_type(String, path)
293
- FSEvent.new(@loop, path)
294
- end
295
-
296
- # Opens a file and returns an object that can be used to manipulate it
297
- #
298
- # @param path [String] the path to the file or folder for watching
299
- # @param flags [Integer] see ruby File::Constants
300
- # @param mode [Integer]
301
- # @return [::Libuv::File]
302
- def file(path, flags = 0, mode = 0)
303
- assert_type(String, path, "path must be a String")
304
- assert_type(Integer, flags, "flags must be an Integer")
305
- assert_type(Integer, mode, "mode must be an Integer")
306
- File.new(@loop, path, flags, mode)
307
- end
308
-
309
- # Returns an object for manipulating the filesystem
310
- #
311
- # @return [::Libuv::Filesystem]
312
- def filesystem
313
- Filesystem.new(@loop)
314
- end
315
-
316
- # Schedule some work to be processed on the event loop as soon as possible (thread safe)
317
- #
318
- # @param callback [Proc] the callback to be called on the reactor thread
319
- # @raise [ArgumentError] if block is not given
320
- def schedule(callback = nil, &block)
321
- callback ||= block
322
- assert_block(callback)
323
-
324
- if reactor_thread?
325
- block.call
326
- else
327
- @run_queue << callback
328
- @process_queue.call
329
- end
330
- end
331
-
332
- # Queue some work to be processed in the next iteration of the event loop (thread safe)
333
- #
334
- # @param callback [Proc] the callback to be called on the reactor thread
335
- # @raise [ArgumentError] if block is not given
336
- def next_tick(callback = nil, &block)
337
- callback ||= block
338
- assert_block(callback)
339
-
340
- @run_queue << callback
341
- if reactor_thread?
342
- # Create a next tick timer
343
- if not @next_tick_scheduled
344
- @next_tick.start(0)
345
- @next_tick_scheduled = true
346
- end
347
- else
348
- @process_queue.call
349
- end
350
- end
351
-
352
- # Notifies the loop there was an event that should be logged
353
- #
354
- # @param level [Symbol] the error level (info, warn, error etc)
355
- # @param id [Object] some kind of identifying information
356
- # @param *args [*args] any additional information
357
- def log(level, id, *args)
358
- @loop_notify.notify(level, id, *args)
359
- end
360
-
361
- # Closes handles opened by the loop class and completes the current loop iteration (thread safe)
362
- def stop
363
- @stop_loop.call
364
- end
365
-
366
- # True if the calling thread is the same thread as the reactor.
367
- #
368
- # @return [Boolean]
369
- def reactor_thread?
370
- @reactor_thread == Thread.current
371
- end
372
-
373
- # Exposed to allow joining on the thread, when run in a multithreaded environment. Performing other actions on the thread has undefined semantics (read: a dangerous endevor).
374
- #
375
- # @return [Thread]
376
- def reactor_thread
377
- @reactor_thread
378
- end
379
-
380
- # Tells you whether the Libuv reactor loop is currently running.
381
- #
382
- # @return [Boolean]
383
- def reactor_running?
384
- !@reactor_thread.nil?
385
- end
386
- end
387
- end
1
+ require 'thread'
2
+
3
+ module Libuv
4
+ class Loop
5
+ include Resource, Assertions
6
+
7
+
8
+ LOOPS = ThreadSafe::Cache.new
9
+
10
+
11
+ module ClassMethods
12
+ # Get default loop
13
+ #
14
+ # @return [::Libuv::Loop]
15
+ def default
16
+ return @default ||= create(::Libuv::Ext.default_loop)
17
+ end
18
+
19
+ # Create new Libuv loop
20
+ #
21
+ # @return [::Libuv::Loop]
22
+ def new
23
+ return create(::Libuv::Ext.loop_new)
24
+ end
25
+
26
+ # Build a Ruby Libuv loop from an existing loop pointer
27
+ #
28
+ # @return [::Libuv::Loop]
29
+ def create(pointer)
30
+ allocate.tap { |i| i.send(:initialize, FFI::AutoPointer.new(pointer, ::Libuv::Ext.method(:loop_delete))) }
31
+ end
32
+
33
+ # Checks for the existence of a loop on the current thread
34
+ #
35
+ # @return [::Libuv::Loop | nil]
36
+ def current
37
+ LOOPS[Thread.current]
38
+ end
39
+ end
40
+ extend ClassMethods
41
+
42
+
43
+ # Initialize a loop using an FFI::Pointer to a libuv loop
44
+ def initialize(pointer) # :notnew:
45
+ @pointer = pointer
46
+ @loop = self
47
+
48
+ # Create an async call for scheduling work from other threads
49
+ @run_queue = Queue.new
50
+ @queue_proc = proc do
51
+ # ensure we only execute what was required for this tick
52
+ length = @run_queue.length
53
+ length.times do
54
+ begin
55
+ run = @run_queue.pop true # pop non-block
56
+ run.call
57
+ rescue Exception => e
58
+ @loop.log :error, :next_tick_cb, e
59
+ end
60
+ end
61
+ end
62
+ @process_queue = Async.new(@loop, @queue_proc)
63
+
64
+ # Create a next tick timer
65
+ @next_tick = @loop.timer do
66
+ @next_tick_scheduled = false
67
+ @queue_proc.call
68
+ end
69
+
70
+ # Create an async call for ending the loop
71
+ @stop_loop = Async.new @loop do
72
+ LOOPS.delete(@reactor_thread)
73
+ @reactor_thread = nil
74
+ @process_queue.close
75
+ @stop_loop.close
76
+ @next_tick.close
77
+
78
+ ::Libuv::Ext.stop(@pointer)
79
+ end
80
+ end
81
+
82
+ def handle; @pointer; end
83
+
84
+ # Run the actual event loop. This method will block until the loop is stopped.
85
+ #
86
+ # @param run_type [:UV_RUN_DEFAULT, :UV_RUN_ONCE, :UV_RUN_NOWAIT]
87
+ # @yieldparam promise [::Libuv::Q::Promise] Yields a promise that can be used for logging unhandled
88
+ # exceptions on the loop.
89
+ def run(run_type = :UV_RUN_DEFAULT)
90
+ if @reactor_thread.nil?
91
+ @loop_notify = @loop.defer
92
+
93
+ begin
94
+ @reactor_thread = Thread.current
95
+ LOOPS[@reactor_thread] = @loop
96
+ yield @loop_notify.promise if block_given?
97
+ ::Libuv::Ext.run(@pointer, run_type) # This is blocking
98
+ ensure
99
+ @reactor_thread = nil
100
+ @run_queue.clear
101
+ end
102
+ elsif block_given?
103
+ schedule { yield @loop_notify.promise }
104
+ end
105
+ @loop
106
+ end
107
+
108
+
109
+ # Creates a deferred result object for where the result of an operation may only be returned
110
+ # at some point in the future or is being processed on a different thread (thread safe)
111
+ #
112
+ # @return [::Libuv::Q::Deferred]
113
+ def defer
114
+ Q.defer(@loop)
115
+ end
116
+
117
+ # Combines multiple promises into a single promise that is resolved when all of the input
118
+ # promises are resolved. (thread safe)
119
+ #
120
+ # @param *promises [::Libuv::Q::Promise] a number of promises that will be combined into a single promise
121
+ # @return [::Libuv::Q::Promise] Returns a single promise that will be resolved with an array of values,
122
+ # each value corresponding to the promise at the same index in the `promises` array. If any of
123
+ # the promises is resolved with a rejection, this resulting promise will be resolved with the
124
+ # same rejection.
125
+ def all(*promises)
126
+ Q.all(@loop, *promises)
127
+ end
128
+
129
+ #
130
+ # Combines multiple promises into a single promise that is resolved when any of the input
131
+ # promises are resolved.
132
+ #
133
+ # @param *promises [::Libuv::Q::Promise] a number of promises that will be combined into a single promise
134
+ # @return [::Libuv::Q::Promise] Returns a single promise
135
+ def any(*promises)
136
+ Q.any(@loop, *promises)
137
+ end
138
+
139
+ #
140
+ # Combines multiple promises into a single promise that is resolved when all of the input
141
+ # promises are resolved or rejected.
142
+ #
143
+ # @param *promises [::Libuv::Q::Promise] a number of promises that will be combined into a single promise
144
+ # @return [::Libuv::Q::Promise] Returns a single promise that will be resolved with an array of values,
145
+ # each [result, wasResolved] value pair corresponding to a at the same index in the `promises` array.
146
+ def finally(*promises)
147
+ Q.finally(@loop, *promises)
148
+ end
149
+
150
+
151
+ # forces loop time update, useful for getting more granular times
152
+ #
153
+ # @return nil
154
+ def update_time
155
+ ::Libuv::Ext.update_time(@pointer)
156
+ end
157
+
158
+ # Get current time in milliseconds
159
+ #
160
+ # @return [Fixnum]
161
+ def now
162
+ ::Libuv::Ext.now(@pointer)
163
+ end
164
+
165
+ # Lookup an error code and return is as an error object
166
+ #
167
+ # @param err [Integer] The error code to look up.
168
+ # @return [::Libuv::Error]
169
+ def lookup_error(err)
170
+ name = ::Libuv::Ext.err_name(err)
171
+ msg = ::Libuv::Ext.strerror(err)
172
+
173
+ ::Libuv::Error.const_get(name.to_sym).new(msg)
174
+ rescue Exception => e
175
+ @loop.log :warn, :error_lookup_failed, e
176
+ ::Libuv::Error::UNKNOWN.new("error lookup failed for code #{err} #{name} #{msg}")
177
+ end
178
+
179
+ # Get a new TCP instance
180
+ #
181
+ # @return [::Libuv::TCP]
182
+ def tcp
183
+ TCP.new(@loop)
184
+ end
185
+
186
+ # Get a new UDP instance
187
+ #
188
+ # @return [::Libuv::UDP]
189
+ def udp
190
+ UDP.new(@loop)
191
+ end
192
+
193
+ # Get a new TTY instance
194
+ #
195
+ # @param fileno [Integer] Integer file descriptor of a tty device
196
+ # @param readable [true, false] Boolean indicating if TTY is readable
197
+ # @return [::Libuv::TTY]
198
+ def tty(fileno, readable = false)
199
+ assert_type(Integer, fileno, "io#fileno must return an integer file descriptor, #{fileno.inspect} given")
200
+
201
+ TTY.new(@loop, fileno, readable)
202
+ end
203
+
204
+ # Get a new Pipe instance
205
+ #
206
+ # @param ipc [true, false] indicate if a handle will be used for ipc, useful for sharing tcp socket between processes
207
+ # @return [::Libuv::Pipe]
208
+ def pipe(ipc = false)
209
+ Pipe.new(@loop, ipc)
210
+ end
211
+
212
+ # Get a new timer instance
213
+ #
214
+ # @param callback [Proc] the callback to be called on timer trigger
215
+ # @return [::Libuv::Timer]
216
+ def timer(callback = nil, &blk)
217
+ Timer.new(@loop, callback || blk)
218
+ end
219
+
220
+ # Get a new Prepare handle
221
+ #
222
+ # @return [::Libuv::Prepare]
223
+ def prepare(callback = nil, &blk)
224
+ Prepare.new(@loop, callback || blk)
225
+ end
226
+
227
+ # Get a new Check handle
228
+ #
229
+ # @return [::Libuv::Check]
230
+ def check(callback = nil, &blk)
231
+ Check.new(@loop, callback || blk)
232
+ end
233
+
234
+ # Get a new Idle handle
235
+ #
236
+ # @param callback [Proc] the callback to be called on idle trigger
237
+ # @return [::Libuv::Idle]
238
+ def idle(callback = nil, &block)
239
+ Idle.new(@loop, callback || block)
240
+ end
241
+
242
+ # Get a new Async handle
243
+ #
244
+ # @return [::Libuv::Async]
245
+ def async(callback = nil, &block)
246
+ callback ||= block
247
+ handle = Async.new(@loop)
248
+ handle.progress callback if callback
249
+ handle
250
+ end
251
+
252
+ # Get a new signal handler
253
+ #
254
+ # @return [::Libuv::Signal]
255
+ def signal(signum = nil, callback = nil, &block)
256
+ callback ||= block
257
+ handle = Signal.new(@loop)
258
+ handle.progress callback if callback
259
+ handle.start(signum) if signum
260
+ handle
261
+ end
262
+
263
+ # Queue some work for processing in the libuv thread pool
264
+ #
265
+ # @param callback [Proc] the callback to be called in the thread pool
266
+ # @return [::Libuv::Work]
267
+ # @raise [ArgumentError] if block is not given
268
+ def work(callback = nil, &block)
269
+ callback ||= block
270
+ assert_block(callback)
271
+ Work.new(@loop, callback) # Work is a promise object
272
+ end
273
+
274
+ # Lookup a hostname
275
+ #
276
+ # @param hostname [String] the domain name to lookup
277
+ # @param port [Integer, String] the service being connected too
278
+ # @param callback [Proc] the callback to be called on success
279
+ # @return [::Libuv::Dns]
280
+ def lookup(hostname, hint = :IPv4, port = 9, &block)
281
+ dns = Dns.new(@loop, hostname, port, hint) # Work is a promise object
282
+ dns.then block if block_given?
283
+ dns
284
+ end
285
+
286
+ # Get a new FSEvent instance
287
+ #
288
+ # @param path [String] the path to the file or folder for watching
289
+ # @return [::Libuv::FSEvent]
290
+ # @raise [ArgumentError] if path is not a string
291
+ def fs_event(path)
292
+ assert_type(String, path)
293
+ FSEvent.new(@loop, path)
294
+ end
295
+
296
+ # Opens a file and returns an object that can be used to manipulate it
297
+ #
298
+ # @param path [String] the path to the file or folder for watching
299
+ # @param flags [Integer] see ruby File::Constants
300
+ # @param mode [Integer]
301
+ # @return [::Libuv::File]
302
+ def file(path, flags = 0, mode = 0)
303
+ assert_type(String, path, "path must be a String")
304
+ assert_type(Integer, flags, "flags must be an Integer")
305
+ assert_type(Integer, mode, "mode must be an Integer")
306
+ File.new(@loop, path, flags, mode)
307
+ end
308
+
309
+ # Returns an object for manipulating the filesystem
310
+ #
311
+ # @return [::Libuv::Filesystem]
312
+ def filesystem
313
+ Filesystem.new(@loop)
314
+ end
315
+
316
+ # Schedule some work to be processed on the event loop as soon as possible (thread safe)
317
+ #
318
+ # @param callback [Proc] the callback to be called on the reactor thread
319
+ # @raise [ArgumentError] if block is not given
320
+ def schedule(callback = nil, &block)
321
+ callback ||= block
322
+ assert_block(callback)
323
+
324
+ if reactor_thread?
325
+ block.call
326
+ else
327
+ @run_queue << callback
328
+ @process_queue.call
329
+ end
330
+ end
331
+
332
+ # Queue some work to be processed in the next iteration of the event loop (thread safe)
333
+ #
334
+ # @param callback [Proc] the callback to be called on the reactor thread
335
+ # @raise [ArgumentError] if block is not given
336
+ def next_tick(callback = nil, &block)
337
+ callback ||= block
338
+ assert_block(callback)
339
+
340
+ @run_queue << callback
341
+ if reactor_thread?
342
+ # Create a next tick timer
343
+ if not @next_tick_scheduled
344
+ @next_tick.start(0)
345
+ @next_tick_scheduled = true
346
+ end
347
+ else
348
+ @process_queue.call
349
+ end
350
+ end
351
+
352
+ # Notifies the loop there was an event that should be logged
353
+ #
354
+ # @param level [Symbol] the error level (info, warn, error etc)
355
+ # @param id [Object] some kind of identifying information
356
+ # @param *args [*args] any additional information
357
+ def log(level, id, *args)
358
+ @loop_notify.notify(level, id, *args)
359
+ end
360
+
361
+ # Closes handles opened by the loop class and completes the current loop iteration (thread safe)
362
+ def stop
363
+ @stop_loop.call
364
+ end
365
+
366
+ # True if the calling thread is the same thread as the reactor.
367
+ #
368
+ # @return [Boolean]
369
+ def reactor_thread?
370
+ @reactor_thread == Thread.current
371
+ end
372
+
373
+ # Exposed to allow joining on the thread, when run in a multithreaded environment. Performing other actions on the thread has undefined semantics (read: a dangerous endevor).
374
+ #
375
+ # @return [Thread]
376
+ def reactor_thread
377
+ @reactor_thread
378
+ end
379
+
380
+ # Tells you whether the Libuv reactor loop is currently running.
381
+ #
382
+ # @return [Boolean]
383
+ def reactor_running?
384
+ !@reactor_thread.nil?
385
+ end
386
+ end
387
+ end