libuv 0.11.21 → 0.11.22

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 +4 -4
  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 +89 -88
  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 +74 -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 +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 +269 -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 +58 -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 +154 -154
  37. data/lib/libuv/pipe.rb +203 -203
  38. data/lib/libuv/prepare.rb +56 -56
  39. data/lib/libuv/signal.rb +51 -51
  40. data/lib/libuv/tcp.rb +334 -332
  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 +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 +261 -250
  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 +55 -61
@@ -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