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