mt-libuv 4.1.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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.gitmodules +6 -0
- data/.rspec +1 -0
- data/.travis.yml +24 -0
- data/Gemfile +9 -0
- data/LICENSE +24 -0
- data/README.md +195 -0
- data/Rakefile +31 -0
- data/ext/README.md +6 -0
- data/ext/Rakefile +28 -0
- data/lib/mt-libuv/async.rb +51 -0
- data/lib/mt-libuv/check.rb +59 -0
- data/lib/mt-libuv/coroutines.rb +79 -0
- data/lib/mt-libuv/dns.rb +98 -0
- data/lib/mt-libuv/error.rb +88 -0
- data/lib/mt-libuv/ext/ext.rb +322 -0
- data/lib/mt-libuv/ext/platform/darwin_x64.rb +61 -0
- data/lib/mt-libuv/ext/platform/unix.rb +69 -0
- data/lib/mt-libuv/ext/platform/windows.rb +83 -0
- data/lib/mt-libuv/ext/tasks/mac.rb +24 -0
- data/lib/mt-libuv/ext/tasks/unix.rb +42 -0
- data/lib/mt-libuv/ext/tasks/win.rb +29 -0
- data/lib/mt-libuv/ext/tasks.rb +27 -0
- data/lib/mt-libuv/ext/types.rb +253 -0
- data/lib/mt-libuv/fiber_pool.rb +83 -0
- data/lib/mt-libuv/file.rb +309 -0
- data/lib/mt-libuv/filesystem.rb +263 -0
- data/lib/mt-libuv/fs_event.rb +37 -0
- data/lib/mt-libuv/handle.rb +108 -0
- data/lib/mt-libuv/idle.rb +59 -0
- data/lib/mt-libuv/mixins/accessors.rb +41 -0
- data/lib/mt-libuv/mixins/assertions.rb +25 -0
- data/lib/mt-libuv/mixins/fs_checks.rb +96 -0
- data/lib/mt-libuv/mixins/listener.rb +69 -0
- data/lib/mt-libuv/mixins/net.rb +42 -0
- data/lib/mt-libuv/mixins/resource.rb +30 -0
- data/lib/mt-libuv/mixins/stream.rb +276 -0
- data/lib/mt-libuv/pipe.rb +217 -0
- data/lib/mt-libuv/prepare.rb +59 -0
- data/lib/mt-libuv/q.rb +475 -0
- data/lib/mt-libuv/reactor.rb +567 -0
- data/lib/mt-libuv/signal.rb +62 -0
- data/lib/mt-libuv/spawn.rb +113 -0
- data/lib/mt-libuv/tcp.rb +465 -0
- data/lib/mt-libuv/timer.rb +107 -0
- data/lib/mt-libuv/tty.rb +42 -0
- data/lib/mt-libuv/udp.rb +302 -0
- data/lib/mt-libuv/version.rb +5 -0
- data/lib/mt-libuv/work.rb +86 -0
- data/lib/mt-libuv.rb +80 -0
- data/mt-libuv.gemspec +62 -0
- data/spec/async_spec.rb +67 -0
- data/spec/coroutines_spec.rb +121 -0
- data/spec/cpu_spec.rb +10 -0
- data/spec/defer_spec.rb +906 -0
- data/spec/dns_spec.rb +110 -0
- data/spec/dsl_spec.rb +43 -0
- data/spec/filesystem_spec.rb +270 -0
- data/spec/idle_spec.rb +44 -0
- data/spec/pipe_spec.rb +151 -0
- data/spec/spawn_spec.rb +119 -0
- data/spec/tcp_spec.rb +272 -0
- data/spec/test.sh +4 -0
- data/spec/test_fail.sh +3 -0
- data/spec/test_read.sh +3 -0
- data/spec/timer_spec.rb +14 -0
- data/spec/udp_spec.rb +73 -0
- data/spec/zen_spec.rb +34 -0
- metadata +196 -0
@@ -0,0 +1,567 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
module MTLibuv
|
6
|
+
class Reactor
|
7
|
+
include Resource, Assertions
|
8
|
+
extend Accessors
|
9
|
+
|
10
|
+
|
11
|
+
LIBUV_MIN_POOL = ENV['LIBUV_MIN_POOL'] || 8
|
12
|
+
LIBUV_MAX_POOL = ENV['LIBUV_MAX_POOL'] || 40
|
13
|
+
LIBUV_MAX_QUEUE = ENV['LIBUV_MAX_QUEUE'] || 50000
|
14
|
+
THREAD_POOL = ::Concurrent::ThreadPoolExecutor.new(
|
15
|
+
min_threads: LIBUV_MIN_POOL,
|
16
|
+
max_threads: LIBUV_MAX_POOL,
|
17
|
+
max_queue: LIBUV_MAX_QUEUE
|
18
|
+
)
|
19
|
+
CRITICAL = ::Mutex.new
|
20
|
+
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
# Get default reactor
|
24
|
+
#
|
25
|
+
# @return [::MTLibuv::Reactor]
|
26
|
+
def default
|
27
|
+
return @default unless @default.nil?
|
28
|
+
CRITICAL.synchronize {
|
29
|
+
return @default ||= create(::MTLibuv::Ext.default_loop)
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Create new MTLibuv reactor
|
34
|
+
#
|
35
|
+
# @return [::MTLibuv::Reactor]
|
36
|
+
def new(&blk)
|
37
|
+
memory = ::MTLibuv::Ext::LIBC.malloc(::MTLibuv::Ext.loop_size)
|
38
|
+
::MTLibuv::Ext.loop_init(memory)
|
39
|
+
|
40
|
+
thread = create(memory)
|
41
|
+
if block_given?
|
42
|
+
::Thread.new do
|
43
|
+
thread.run &blk
|
44
|
+
end
|
45
|
+
end
|
46
|
+
thread
|
47
|
+
end
|
48
|
+
|
49
|
+
# Build a Ruby MTLibuv reactor from an existing reactor pointer
|
50
|
+
#
|
51
|
+
# @return [::MTLibuv::Reactor]
|
52
|
+
def create(pointer)
|
53
|
+
allocate.tap { |i| i.send(:initialize, pointer) }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Checks for the existence of a reactor on the current thread
|
57
|
+
#
|
58
|
+
# @return [::MTLibuv::Reactor | nil]
|
59
|
+
def current
|
60
|
+
Thread.current.thread_variable_get(:reactor)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
extend ClassMethods
|
64
|
+
|
65
|
+
|
66
|
+
# Initialize a reactor using an FFI::Pointer to a libuv reactor
|
67
|
+
def initialize(pointer) # :notnew:
|
68
|
+
@pointer = pointer
|
69
|
+
@reactor = self
|
70
|
+
@run_count = 0
|
71
|
+
@ref_count = 0
|
72
|
+
@fiber_pool = FiberPool.new(self)
|
73
|
+
|
74
|
+
# Create an async call for scheduling work from other threads
|
75
|
+
@run_queue = Queue.new
|
76
|
+
@process_queue = @reactor.async { process_queue_cb }
|
77
|
+
@process_queue.unref
|
78
|
+
|
79
|
+
# Create a next tick timer
|
80
|
+
@next_tick = @reactor.timer { next_tick_cb }
|
81
|
+
@next_tick.unref
|
82
|
+
|
83
|
+
# Create an async call for ending the reactor
|
84
|
+
@stop_reactor = @reactor.async { stop_cb }
|
85
|
+
@stop_reactor.unref
|
86
|
+
|
87
|
+
# MTLibuv can prevent the application shutting down once the main thread has ended
|
88
|
+
# The addition of a prepare function prevents this from happening.
|
89
|
+
@reactor_prep = prepare {}
|
90
|
+
@reactor_prep.unref
|
91
|
+
@reactor_prep.start
|
92
|
+
|
93
|
+
# LibUV ingnores program interrupt by default.
|
94
|
+
# We provide normal behaviour and allow this to be overriden
|
95
|
+
@on_signal = []
|
96
|
+
sig_callback = proc { signal_cb }
|
97
|
+
self.signal(:INT, &sig_callback).unref
|
98
|
+
self.signal(:HUP, &sig_callback).unref
|
99
|
+
self.signal(:TERM, &sig_callback).unref
|
100
|
+
|
101
|
+
# Notify of errors
|
102
|
+
@throw_on_exit = nil
|
103
|
+
@reactor_notify_default = @reactor_notify = proc { |error|
|
104
|
+
@throw_on_exit = error
|
105
|
+
}
|
106
|
+
@fiber_pool.on_error &@reactor_notify
|
107
|
+
end
|
108
|
+
|
109
|
+
attr_reader :run_count, :fiber_pool
|
110
|
+
|
111
|
+
|
112
|
+
protected
|
113
|
+
|
114
|
+
|
115
|
+
def stop_cb
|
116
|
+
return unless @reactor_running
|
117
|
+
Thread.current.thread_variable_set(:reactor, nil)
|
118
|
+
@reactor_running = false
|
119
|
+
|
120
|
+
::MTLibuv::Ext.stop(@pointer)
|
121
|
+
end
|
122
|
+
|
123
|
+
def signal_cb
|
124
|
+
if @on_signal.empty?
|
125
|
+
stop_cb
|
126
|
+
else
|
127
|
+
@on_signal.each(&:call)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def next_tick_cb
|
132
|
+
@next_tick_scheduled = false
|
133
|
+
@next_tick.unref
|
134
|
+
process_queue_cb
|
135
|
+
end
|
136
|
+
|
137
|
+
def process_queue_cb
|
138
|
+
# ensure we only execute what was required for this tick
|
139
|
+
length = @run_queue.length
|
140
|
+
update_time
|
141
|
+
length.times do
|
142
|
+
# This allows any item to pause its execution without effecting this loop
|
143
|
+
@fiber_pool.exec { process_item }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def process_item
|
148
|
+
begin
|
149
|
+
run = @run_queue.pop true # pop non-block
|
150
|
+
run.call
|
151
|
+
rescue Exception => e
|
152
|
+
@reactor.log e, 'performing next tick callback'
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
public
|
158
|
+
|
159
|
+
|
160
|
+
# Overwrite as errors in jRuby can literally hang VM when inspecting
|
161
|
+
# as many many classes will reference this class
|
162
|
+
def inspect
|
163
|
+
"#<#{self.class}:0x#{self.__id__.to_s(16)} NT=#{@run_queue.length}>"
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
def handle; @pointer; end
|
168
|
+
|
169
|
+
# Run the actual event reactor. This method will block until the reactor is stopped.
|
170
|
+
#
|
171
|
+
# @param run_type [:UV_RUN_DEFAULT, :UV_RUN_ONCE, :UV_RUN_NOWAIT]
|
172
|
+
# @yieldparam promise [::MTLibuv::Q::Promise] Yields a promise that can be used for logging unhandled
|
173
|
+
# exceptions on the reactor.
|
174
|
+
def run(run_type = :UV_RUN_DEFAULT)
|
175
|
+
if not @reactor_running
|
176
|
+
begin
|
177
|
+
@reactor_running = true
|
178
|
+
raise 'only one reactor allowed per-thread' if Thread.current.thread_variable_get(:reactor)
|
179
|
+
|
180
|
+
Thread.current.thread_variable_set(:reactor, @reactor)
|
181
|
+
@throw_on_exit = nil
|
182
|
+
update_time
|
183
|
+
@fiber_pool.reset
|
184
|
+
@fiber_pool.exec { yield @reactor } if block_given?
|
185
|
+
@run_count += 1
|
186
|
+
::MTLibuv::Ext.run(@pointer, run_type) # This is blocking
|
187
|
+
ensure
|
188
|
+
Thread.current.thread_variable_set(:reactor, nil)
|
189
|
+
@reactor_running = false
|
190
|
+
@run_queue.clear
|
191
|
+
end
|
192
|
+
|
193
|
+
# Raise the last unhandled error to occur on the reactor thread
|
194
|
+
raise @throw_on_exit if @throw_on_exit
|
195
|
+
|
196
|
+
elsif block_given?
|
197
|
+
if reactor_thread?
|
198
|
+
update_time
|
199
|
+
yield @reactor
|
200
|
+
else
|
201
|
+
raise 'reactor already running on another thread'
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
@reactor
|
206
|
+
end
|
207
|
+
|
208
|
+
# Execute the provided block of code in a fiber from the pool
|
209
|
+
def exec
|
210
|
+
@fiber_pool.exec { yield }
|
211
|
+
end
|
212
|
+
|
213
|
+
# Prevents the reactor loop from stopping
|
214
|
+
def ref
|
215
|
+
if reactor_thread? && reactor_running?
|
216
|
+
@process_queue.ref if @ref_count == 0
|
217
|
+
@ref_count += 1
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# Allows the reactor loop to stop
|
222
|
+
def unref
|
223
|
+
if reactor_thread? && reactor_running? && @ref_count > 0
|
224
|
+
@ref_count -= 1
|
225
|
+
@process_queue.unref if @ref_count == 0
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Return the number of active handles in the event loop
|
230
|
+
def active_handles
|
231
|
+
uvloop = Ext::UvLoop.new @pointer
|
232
|
+
uvloop[:active_handles]
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
# Provides a promise notifier for receiving un-handled exceptions
|
237
|
+
#
|
238
|
+
# @return [::MTLibuv::Q::Promise]
|
239
|
+
def notifier
|
240
|
+
@reactor_notify = if block_given?
|
241
|
+
Proc.new
|
242
|
+
else
|
243
|
+
@reactor_notify_default
|
244
|
+
end
|
245
|
+
self
|
246
|
+
end
|
247
|
+
|
248
|
+
# Creates a deferred result object for where the result of an operation may only be returned
|
249
|
+
# at some point in the future or is being processed on a different thread (thread safe)
|
250
|
+
#
|
251
|
+
# @return [::MTLibuv::Q::Deferred]
|
252
|
+
def defer
|
253
|
+
Q.defer(@reactor)
|
254
|
+
end
|
255
|
+
|
256
|
+
# Combines multiple promises into a single promise that is resolved when all of the input
|
257
|
+
# promises are resolved. (thread safe)
|
258
|
+
#
|
259
|
+
# @param *promises [::MTLibuv::Q::Promise] a number of promises that will be combined into a single promise
|
260
|
+
# @return [::MTLibuv::Q::Promise] Returns a single promise that will be resolved with an array of values,
|
261
|
+
# each value corresponding to the promise at the same index in the `promises` array. If any of
|
262
|
+
# the promises is resolved with a rejection, this resulting promise will be resolved with the
|
263
|
+
# same rejection.
|
264
|
+
def all(*promises)
|
265
|
+
Q.all(@reactor, *promises)
|
266
|
+
end
|
267
|
+
|
268
|
+
#
|
269
|
+
# Combines multiple promises into a single promise that is resolved when any of the input
|
270
|
+
# promises are resolved.
|
271
|
+
#
|
272
|
+
# @param *promises [::MTLibuv::Q::Promise] a number of promises that will be combined into a single promise
|
273
|
+
# @return [::MTLibuv::Q::Promise] Returns a single promise
|
274
|
+
def any(*promises)
|
275
|
+
Q.any(@reactor, *promises)
|
276
|
+
end
|
277
|
+
|
278
|
+
#
|
279
|
+
# Combines multiple promises into a single promise that is resolved when all of the input
|
280
|
+
# promises are resolved or rejected.
|
281
|
+
#
|
282
|
+
# @param *promises [::MTLibuv::Q::Promise] a number of promises that will be combined into a single promise
|
283
|
+
# @return [::MTLibuv::Q::Promise] Returns a single promise that will be resolved with an array of values,
|
284
|
+
# each [result, wasResolved] value pair corresponding to a at the same index in the `promises` array.
|
285
|
+
def finally(*promises)
|
286
|
+
Q.finally(@reactor, *promises)
|
287
|
+
end
|
288
|
+
|
289
|
+
# Creates a promise that is resolved as rejected with the specified reason. This api should be
|
290
|
+
# used to forward rejection in a chain of promises. If you are dealing with the last promise in
|
291
|
+
# a promise chain, you don't need to worry about it.
|
292
|
+
def reject(reason)
|
293
|
+
Q.reject(@reactor, reason)
|
294
|
+
end
|
295
|
+
|
296
|
+
# forces reactor time update, useful for getting more granular times
|
297
|
+
#
|
298
|
+
# @return nil
|
299
|
+
def update_time
|
300
|
+
::MTLibuv::Ext.update_time(@pointer)
|
301
|
+
self
|
302
|
+
end
|
303
|
+
|
304
|
+
# Get current time in milliseconds
|
305
|
+
#
|
306
|
+
# @return [Integer]
|
307
|
+
def now
|
308
|
+
::MTLibuv::Ext.now(@pointer)
|
309
|
+
end
|
310
|
+
|
311
|
+
# Lookup an error code and return is as an error object
|
312
|
+
#
|
313
|
+
# @param err [Integer] The error code to look up.
|
314
|
+
# @return [::MTLibuv::Error]
|
315
|
+
def lookup_error(err)
|
316
|
+
name = ::MTLibuv::Ext.err_name(err)
|
317
|
+
|
318
|
+
if name
|
319
|
+
msg = ::MTLibuv::Ext.strerror(err)
|
320
|
+
::MTLibuv::Error.const_get(name.to_sym).new("#{msg}, #{name}:#{err}")
|
321
|
+
else
|
322
|
+
# We want a back-trace in this case
|
323
|
+
raise "error lookup failed for code #{err}"
|
324
|
+
end
|
325
|
+
rescue Exception => e
|
326
|
+
@reactor.log e, 'performing error lookup'
|
327
|
+
e
|
328
|
+
end
|
329
|
+
|
330
|
+
def sleep(msecs)
|
331
|
+
fiber = Fiber.current
|
332
|
+
time = timer {
|
333
|
+
time.close
|
334
|
+
fiber.resume
|
335
|
+
}.start(msecs)
|
336
|
+
Fiber.yield
|
337
|
+
end
|
338
|
+
|
339
|
+
# Get a new TCP instance
|
340
|
+
#
|
341
|
+
# @return [::MTLibuv::TCP]
|
342
|
+
def tcp(**opts, &callback)
|
343
|
+
TCP.new(@reactor, progress: callback, **opts)
|
344
|
+
end
|
345
|
+
|
346
|
+
# Get a new UDP instance
|
347
|
+
#
|
348
|
+
# @return [::MTLibuv::UDP]
|
349
|
+
def udp(**opts, &callback)
|
350
|
+
UDP.new(@reactor, progress: callback, **opts)
|
351
|
+
end
|
352
|
+
|
353
|
+
# Get a new TTY instance
|
354
|
+
#
|
355
|
+
# @param fileno [Integer] Integer file descriptor of a tty device
|
356
|
+
# @param readable [true, false] Boolean indicating if TTY is readable
|
357
|
+
# @return [::MTLibuv::TTY]
|
358
|
+
def tty(fileno, readable = false)
|
359
|
+
assert_type(Integer, fileno, "io#fileno must return an integer file descriptor, #{fileno.inspect} given")
|
360
|
+
|
361
|
+
TTY.new(@reactor, fileno, readable)
|
362
|
+
end
|
363
|
+
|
364
|
+
# Get a new Pipe instance
|
365
|
+
#
|
366
|
+
# @param ipc [true, false] indicate if a handle will be used for ipc, useful for sharing tcp socket between processes
|
367
|
+
# @return [::MTLibuv::Pipe]
|
368
|
+
def pipe(ipc = false)
|
369
|
+
Pipe.new(@reactor, ipc)
|
370
|
+
end
|
371
|
+
|
372
|
+
# Get a new timer instance
|
373
|
+
#
|
374
|
+
# @param callback [Proc] the callback to be called on timer trigger
|
375
|
+
# @return [::MTLibuv::Timer]
|
376
|
+
def timer
|
377
|
+
handle = Timer.new(@reactor)
|
378
|
+
handle.progress &Proc.new if block_given?
|
379
|
+
handle
|
380
|
+
end
|
381
|
+
|
382
|
+
# Get a new Prepare handle
|
383
|
+
#
|
384
|
+
# @return [::MTLibuv::Prepare]
|
385
|
+
def prepare
|
386
|
+
handle = Prepare.new(@reactor)
|
387
|
+
handle.progress &Proc.new if block_given?
|
388
|
+
handle
|
389
|
+
end
|
390
|
+
|
391
|
+
# Get a new Check handle
|
392
|
+
#
|
393
|
+
# @return [::MTLibuv::Check]
|
394
|
+
def check
|
395
|
+
handle = Check.new(@reactor)
|
396
|
+
handle.progress &Proc.new if block_given?
|
397
|
+
handle
|
398
|
+
end
|
399
|
+
|
400
|
+
# Get a new Idle handle
|
401
|
+
#
|
402
|
+
# @param callback [Proc] the callback to be called on idle trigger
|
403
|
+
# @return [::MTLibuv::Idle]
|
404
|
+
def idle
|
405
|
+
handle = Idle.new(@reactor)
|
406
|
+
handle.progress &Proc.new if block_given?
|
407
|
+
handle
|
408
|
+
end
|
409
|
+
|
410
|
+
# Get a new Async handle
|
411
|
+
#
|
412
|
+
# @return [::MTLibuv::Async]
|
413
|
+
def async
|
414
|
+
handle = Async.new(@reactor)
|
415
|
+
handle.progress &Proc.new if block_given?
|
416
|
+
handle
|
417
|
+
end
|
418
|
+
|
419
|
+
# Get a new signal handler
|
420
|
+
#
|
421
|
+
# @return [::MTLibuv::Signal]
|
422
|
+
def signal(signum = nil)
|
423
|
+
handle = Signal.new(@reactor)
|
424
|
+
handle.progress &Proc.new if block_given?
|
425
|
+
handle.start(signum) if signum
|
426
|
+
handle
|
427
|
+
end
|
428
|
+
|
429
|
+
# Allows user defined behaviour when sig int is received
|
430
|
+
def on_program_interrupt(&callback)
|
431
|
+
@on_signal << callback
|
432
|
+
self
|
433
|
+
end
|
434
|
+
|
435
|
+
# Queue some work for processing in the libuv thread pool
|
436
|
+
#
|
437
|
+
# @param callback [Proc] the callback to be called in the thread pool
|
438
|
+
# @return [::MTLibuv::Work]
|
439
|
+
# @raise [ArgumentError] if block is not given
|
440
|
+
def work
|
441
|
+
ref
|
442
|
+
d = defer
|
443
|
+
THREAD_POOL.post do
|
444
|
+
begin
|
445
|
+
d.resolve(yield)
|
446
|
+
rescue Exception => e
|
447
|
+
d.reject(e)
|
448
|
+
end
|
449
|
+
end
|
450
|
+
promise = d.promise
|
451
|
+
promise.finally { unref }
|
452
|
+
promise
|
453
|
+
end
|
454
|
+
|
455
|
+
# Lookup a hostname
|
456
|
+
#
|
457
|
+
# @param hostname [String] the domain name to lookup
|
458
|
+
# @param port [Integer, String] the service being connected too
|
459
|
+
# @param callback [Proc] the callback to be called on success
|
460
|
+
# @return [::MTLibuv::Dns]
|
461
|
+
def lookup(hostname, hint = :IPv4, port = 9, wait: true)
|
462
|
+
dns = Dns.new(@reactor, hostname, port, hint, wait: wait) # Work is a promise object
|
463
|
+
if wait
|
464
|
+
dns.results
|
465
|
+
else
|
466
|
+
dns.then &Proc.new if block_given?
|
467
|
+
dns
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
# Get a new FSEvent instance
|
472
|
+
#
|
473
|
+
# @param path [String] the path to the file or folder for watching
|
474
|
+
# @return [::MTLibuv::FSEvent]
|
475
|
+
# @raise [ArgumentError] if path is not a string
|
476
|
+
def fs_event(path)
|
477
|
+
assert_type(String, path)
|
478
|
+
FSEvent.new(@reactor, path)
|
479
|
+
end
|
480
|
+
|
481
|
+
# Opens a file and returns an object that can be used to manipulate it
|
482
|
+
#
|
483
|
+
# @param path [String] the path to the file or folder for watching
|
484
|
+
# @param flags [Integer] see ruby File::Constants
|
485
|
+
# @param mode [Integer]
|
486
|
+
# @return [::MTLibuv::File]
|
487
|
+
def file(path, flags = 0, mode: 0, **opts, &blk)
|
488
|
+
assert_type(String, path, "path must be a String")
|
489
|
+
assert_type(Integer, flags, "flags must be an Integer")
|
490
|
+
assert_type(Integer, mode, "mode must be an Integer")
|
491
|
+
File.new(@reactor, path, flags, mode: mode, **opts, &blk)
|
492
|
+
end
|
493
|
+
|
494
|
+
# Returns an object for manipulating the filesystem
|
495
|
+
#
|
496
|
+
# @return [::MTLibuv::Filesystem]
|
497
|
+
def filesystem
|
498
|
+
Filesystem.new(@reactor)
|
499
|
+
end
|
500
|
+
|
501
|
+
def spawn(cmd, **args)
|
502
|
+
Spawn.new(@reactor, cmd, **args)
|
503
|
+
end
|
504
|
+
|
505
|
+
# Schedule some work to be processed on the event reactor as soon as possible (thread safe)
|
506
|
+
#
|
507
|
+
# @yield the callback to be called on the reactor thread
|
508
|
+
def schedule
|
509
|
+
if reactor_thread?
|
510
|
+
yield
|
511
|
+
else
|
512
|
+
@run_queue << Proc.new
|
513
|
+
@process_queue.call
|
514
|
+
end
|
515
|
+
self
|
516
|
+
end
|
517
|
+
|
518
|
+
# Queue some work to be processed in the next iteration of the event reactor (thread safe)
|
519
|
+
#
|
520
|
+
# @param callback [Proc] the callback to be called on the reactor thread
|
521
|
+
def next_tick(&block)
|
522
|
+
@run_queue << block
|
523
|
+
if reactor_thread?
|
524
|
+
# Create a next tick timer
|
525
|
+
if not @next_tick_scheduled
|
526
|
+
@next_tick.start(0)
|
527
|
+
@next_tick_scheduled = true
|
528
|
+
@next_tick.ref
|
529
|
+
end
|
530
|
+
else
|
531
|
+
@process_queue.call
|
532
|
+
end
|
533
|
+
|
534
|
+
self
|
535
|
+
end
|
536
|
+
|
537
|
+
# Notifies the reactor there was an event that should be logged
|
538
|
+
#
|
539
|
+
# @param error [Exception] the error
|
540
|
+
# @param msg [String|nil] optional context on the error
|
541
|
+
# @param trace [Array<String>] optional additional trace of caller if async
|
542
|
+
def log(error, msg = nil, trace = nil)
|
543
|
+
@reactor_notify.call(error, msg, trace)
|
544
|
+
end
|
545
|
+
|
546
|
+
# Closes handles opened by the reactor class and completes the current reactor iteration (thread safe)
|
547
|
+
def stop
|
548
|
+
return unless @reactor_running
|
549
|
+
@stop_reactor.call
|
550
|
+
end
|
551
|
+
|
552
|
+
# True if the calling thread is the same thread as the reactor.
|
553
|
+
#
|
554
|
+
# @return [Boolean]
|
555
|
+
def reactor_thread?
|
556
|
+
self == Thread.current.thread_variable_get(:reactor)
|
557
|
+
end
|
558
|
+
|
559
|
+
# Tells you whether the MTLibuv reactor reactor is currently running.
|
560
|
+
#
|
561
|
+
# @return [Boolean]
|
562
|
+
def reactor_running?
|
563
|
+
@reactor_running
|
564
|
+
end
|
565
|
+
alias_method :running?, :reactor_running?
|
566
|
+
end
|
567
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MTLibuv
|
4
|
+
class Signal < Handle
|
5
|
+
|
6
|
+
|
7
|
+
define_callback function: :on_sig, params: [:pointer, :int]
|
8
|
+
|
9
|
+
|
10
|
+
SIGNALS = {
|
11
|
+
:HUP => 1,
|
12
|
+
:SIGHUP => 1,
|
13
|
+
:INT => 2,
|
14
|
+
:SIGINT => 2,
|
15
|
+
:TERM => 15,
|
16
|
+
:SIGTERM => 15,
|
17
|
+
:BREAK => 21,
|
18
|
+
:SIGBREAK => 21,
|
19
|
+
:WINCH => 28,
|
20
|
+
:SIGWINCH => 28
|
21
|
+
}
|
22
|
+
|
23
|
+
|
24
|
+
# @param reactor [::MTLibuv::Reactor] reactor this signal handler will be associated
|
25
|
+
# @param callback [Proc] callback to be called when the signal is triggered
|
26
|
+
def initialize(reactor)
|
27
|
+
@reactor = reactor
|
28
|
+
|
29
|
+
signal_ptr = ::MTLibuv::Ext.allocate_handle_signal
|
30
|
+
error = check_result(::MTLibuv::Ext.signal_init(reactor.handle, signal_ptr))
|
31
|
+
|
32
|
+
super(signal_ptr, error)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Enables the signal handler.
|
36
|
+
def start(signal)
|
37
|
+
return if @closed
|
38
|
+
signal = SIGNALS[signal] if signal.is_a? Symbol
|
39
|
+
error = check_result ::MTLibuv::Ext.signal_start(handle, callback(:on_sig), signal)
|
40
|
+
reject(error) if error
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
# Disables the signal handler.
|
45
|
+
def stop
|
46
|
+
return if @closed
|
47
|
+
error = check_result ::MTLibuv::Ext.signal_stop(handle)
|
48
|
+
reject(error) if error
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
|
56
|
+
def on_sig(handle, signal)
|
57
|
+
@reactor.exec do
|
58
|
+
defer.notify(signal) # notify of a call
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|