libuv 0.11.4 → 0.11.5

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 (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