libuv 0.11.3 → 0.11.4

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 +14 -6
  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 -70
  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 -385
  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 -315
  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 +24 -30
@@ -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,385 +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 current || create(::Libuv::Ext.default_loop)
17
- end
18
-
19
- # Create new Libuv loop
20
- #
21
- # @return [::Libuv::Loop]
22
- def new
23
- return current || 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
- end
103
- @loop
104
- end
105
-
106
-
107
- # Creates a deferred result object for where the result of an operation may only be returned
108
- # at some point in the future or is being processed on a different thread (thread safe)
109
- #
110
- # @return [::Libuv::Q::Deferred]
111
- def defer
112
- Q.defer(@loop)
113
- end
114
-
115
- # Combines multiple promises into a single promise that is resolved when all of the input
116
- # promises are resolved. (thread safe)
117
- #
118
- # @param *promises [::Libuv::Q::Promise] a number of promises that will be combined into a single promise
119
- # @return [::Libuv::Q::Promise] Returns a single promise that will be resolved with an array of values,
120
- # each value corresponding to the promise at the same index in the `promises` array. If any of
121
- # the promises is resolved with a rejection, this resulting promise will be resolved with the
122
- # same rejection.
123
- def all(*promises)
124
- Q.all(@loop, *promises)
125
- end
126
-
127
- #
128
- # Combines multiple promises into a single promise that is resolved when any of the input
129
- # promises are resolved.
130
- #
131
- # @param *promises [::Libuv::Q::Promise] a number of promises that will be combined into a single promise
132
- # @return [::Libuv::Q::Promise] Returns a single promise
133
- def any(*promises)
134
- Q.any(@loop, *promises)
135
- end
136
-
137
- #
138
- # Combines multiple promises into a single promise that is resolved when all of the input
139
- # promises are resolved or rejected.
140
- #
141
- # @param *promises [::Libuv::Q::Promise] a number of promises that will be combined into a single promise
142
- # @return [::Libuv::Q::Promise] Returns a single promise that will be resolved with an array of values,
143
- # each [result, wasResolved] value pair corresponding to a at the same index in the `promises` array.
144
- def finally(*promises)
145
- Q.finally(@loop, *promises)
146
- end
147
-
148
-
149
- # forces loop time update, useful for getting more granular times
150
- #
151
- # @return nil
152
- def update_time
153
- ::Libuv::Ext.update_time(@pointer)
154
- end
155
-
156
- # Get current time in milliseconds
157
- #
158
- # @return [Fixnum]
159
- def now
160
- ::Libuv::Ext.now(@pointer)
161
- end
162
-
163
- # Lookup an error code and return is as an error object
164
- #
165
- # @param err [Integer] The error code to look up.
166
- # @return [::Libuv::Error]
167
- def lookup_error(err)
168
- name = ::Libuv::Ext.err_name(err)
169
- msg = ::Libuv::Ext.strerror(err)
170
-
171
- ::Libuv::Error.const_get(name.to_sym).new(msg)
172
- rescue Exception => e
173
- @loop.log :warn, :error_lookup_failed, e
174
- ::Libuv::Error::UNKNOWN.new("error lookup failed for code #{err} #{name} #{msg}")
175
- end
176
-
177
- # Get a new TCP instance
178
- #
179
- # @return [::Libuv::TCP]
180
- def tcp
181
- TCP.new(@loop)
182
- end
183
-
184
- # Get a new UDP instance
185
- #
186
- # @return [::Libuv::UDP]
187
- def udp
188
- UDP.new(@loop)
189
- end
190
-
191
- # Get a new TTY instance
192
- #
193
- # @param fileno [Integer] Integer file descriptor of a tty device
194
- # @param readable [true, false] Boolean indicating if TTY is readable
195
- # @return [::Libuv::TTY]
196
- def tty(fileno, readable = false)
197
- assert_type(Integer, fileno, "io#fileno must return an integer file descriptor, #{fileno.inspect} given")
198
-
199
- TTY.new(@loop, fileno, readable)
200
- end
201
-
202
- # Get a new Pipe instance
203
- #
204
- # @param ipc [true, false] indicate if a handle will be used for ipc, useful for sharing tcp socket between processes
205
- # @return [::Libuv::Pipe]
206
- def pipe(ipc = false)
207
- Pipe.new(@loop, ipc)
208
- end
209
-
210
- # Get a new timer instance
211
- #
212
- # @param callback [Proc] the callback to be called on timer trigger
213
- # @return [::Libuv::Timer]
214
- def timer(callback = nil, &blk)
215
- Timer.new(@loop, callback || blk)
216
- end
217
-
218
- # Get a new Prepare handle
219
- #
220
- # @return [::Libuv::Prepare]
221
- def prepare(callback = nil, &blk)
222
- Prepare.new(@loop, callback || blk)
223
- end
224
-
225
- # Get a new Check handle
226
- #
227
- # @return [::Libuv::Check]
228
- def check(callback = nil, &blk)
229
- Check.new(@loop, callback || blk)
230
- end
231
-
232
- # Get a new Idle handle
233
- #
234
- # @param callback [Proc] the callback to be called on idle trigger
235
- # @return [::Libuv::Idle]
236
- def idle(callback = nil, &block)
237
- Idle.new(@loop, callback || block)
238
- end
239
-
240
- # Get a new Async handle
241
- #
242
- # @return [::Libuv::Async]
243
- def async(callback = nil, &block)
244
- callback ||= block
245
- handle = Async.new(@loop)
246
- handle.progress callback if callback
247
- handle
248
- end
249
-
250
- # Get a new signal handler
251
- #
252
- # @return [::Libuv::Signal]
253
- def signal(signum = nil, callback = nil, &block)
254
- callback ||= block
255
- handle = Signal.new(@loop)
256
- handle.progress callback if callback
257
- handle.start(signum) if signum
258
- handle
259
- end
260
-
261
- # Queue some work for processing in the libuv thread pool
262
- #
263
- # @param callback [Proc] the callback to be called in the thread pool
264
- # @return [::Libuv::Work]
265
- # @raise [ArgumentError] if block is not given
266
- def work(callback = nil, &block)
267
- callback ||= block
268
- assert_block(callback)
269
- Work.new(@loop, callback) # Work is a promise object
270
- end
271
-
272
- # Lookup a hostname
273
- #
274
- # @param hostname [String] the domain name to lookup
275
- # @param port [Integer, String] the service being connected too
276
- # @param callback [Proc] the callback to be called on success
277
- # @return [::Libuv::Dns]
278
- def lookup(hostname, hint = :IPv4, port = 9, &block)
279
- dns = Dns.new(@loop, hostname, port, hint) # Work is a promise object
280
- dns.then block if block_given?
281
- dns
282
- end
283
-
284
- # Get a new FSEvent instance
285
- #
286
- # @param path [String] the path to the file or folder for watching
287
- # @return [::Libuv::FSEvent]
288
- # @raise [ArgumentError] if path is not a string
289
- def fs_event(path)
290
- assert_type(String, path)
291
- FSEvent.new(@loop, path)
292
- end
293
-
294
- # Opens a file and returns an object that can be used to manipulate it
295
- #
296
- # @param path [String] the path to the file or folder for watching
297
- # @param flags [Integer] see ruby File::Constants
298
- # @param mode [Integer]
299
- # @return [::Libuv::File]
300
- def file(path, flags = 0, mode = 0)
301
- assert_type(String, path, "path must be a String")
302
- assert_type(Integer, flags, "flags must be an Integer")
303
- assert_type(Integer, mode, "mode must be an Integer")
304
- File.new(@loop, path, flags, mode)
305
- end
306
-
307
- # Returns an object for manipulating the filesystem
308
- #
309
- # @return [::Libuv::Filesystem]
310
- def filesystem
311
- Filesystem.new(@loop)
312
- end
313
-
314
- # Schedule some work to be processed on the event loop as soon as possible (thread safe)
315
- #
316
- # @param callback [Proc] the callback to be called on the reactor thread
317
- # @raise [ArgumentError] if block is not given
318
- def schedule(callback = nil, &block)
319
- callback ||= block
320
- assert_block(callback)
321
-
322
- if reactor_thread?
323
- block.call
324
- else
325
- @run_queue << callback
326
- @process_queue.call
327
- end
328
- end
329
-
330
- # Queue some work to be processed in the next iteration of the event loop (thread safe)
331
- #
332
- # @param callback [Proc] the callback to be called on the reactor thread
333
- # @raise [ArgumentError] if block is not given
334
- def next_tick(callback = nil, &block)
335
- callback ||= block
336
- assert_block(callback)
337
-
338
- @run_queue << callback
339
- if reactor_thread?
340
- # Create a next tick timer
341
- if not @next_tick_scheduled
342
- @next_tick.start(0)
343
- @next_tick_scheduled = true
344
- end
345
- else
346
- @process_queue.call
347
- end
348
- end
349
-
350
- # Notifies the loop there was an event that should be logged
351
- #
352
- # @param level [Symbol] the error level (info, warn, error etc)
353
- # @param id [Object] some kind of identifying information
354
- # @param *args [*args] any additional information
355
- def log(level, id, *args)
356
- @loop_notify.notify(level, id, *args)
357
- end
358
-
359
- # Closes handles opened by the loop class and completes the current loop iteration (thread safe)
360
- def stop
361
- @stop_loop.call
362
- end
363
-
364
- # True if the calling thread is the same thread as the reactor.
365
- #
366
- # @return [Boolean]
367
- def reactor_thread?
368
- @reactor_thread == Thread.current
369
- end
370
-
371
- # 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).
372
- #
373
- # @return [Thread]
374
- def reactor_thread
375
- @reactor_thread
376
- end
377
-
378
- # Tells you whether the Libuv reactor loop is currently running.
379
- #
380
- # @return [Boolean]
381
- def reactor_running?
382
- !@reactor_thread.nil?
383
- end
384
- end
385
- 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