libuv 2.0.12 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/README.md +67 -34
- data/lib/libuv.rb +30 -5
- data/lib/libuv/async.rb +16 -10
- data/lib/libuv/check.rb +19 -12
- data/lib/libuv/coroutines.rb +39 -12
- data/lib/libuv/dns.rb +25 -18
- data/lib/libuv/error.rb +2 -0
- data/lib/libuv/ext/ext.rb +28 -36
- data/lib/libuv/ext/platform/darwin_x64.rb +2 -0
- data/lib/libuv/ext/platform/unix.rb +2 -0
- data/lib/libuv/ext/platform/windows.rb +2 -0
- data/lib/libuv/ext/tasks.rb +2 -0
- data/lib/libuv/ext/tasks/mac.rb +2 -0
- data/lib/libuv/ext/tasks/unix.rb +2 -0
- data/lib/libuv/ext/tasks/win.rb +2 -0
- data/lib/libuv/ext/types.rb +2 -1
- data/lib/libuv/file.rb +67 -50
- data/lib/libuv/filesystem.rb +63 -61
- data/lib/libuv/fs_event.rb +7 -4
- data/lib/libuv/handle.rb +30 -14
- data/lib/libuv/idle.rb +17 -10
- data/lib/libuv/mixins/accessors.rb +41 -0
- data/lib/libuv/mixins/assertions.rb +3 -1
- data/lib/libuv/mixins/fs_checks.rb +29 -6
- data/lib/libuv/mixins/listener.rb +4 -2
- data/lib/libuv/mixins/net.rb +4 -2
- data/lib/libuv/mixins/resource.rb +5 -3
- data/lib/libuv/mixins/stream.rb +128 -35
- data/lib/libuv/pipe.rb +54 -27
- data/lib/libuv/prepare.rb +19 -12
- data/lib/libuv/q.rb +109 -101
- data/lib/libuv/{loop.rb → reactor.rb} +163 -85
- data/lib/libuv/signal.rb +13 -5
- data/lib/libuv/tcp.rb +109 -63
- data/lib/libuv/timer.rb +44 -24
- data/lib/libuv/tty.rb +8 -3
- data/lib/libuv/udp.rb +49 -22
- data/lib/libuv/version.rb +3 -1
- data/lib/libuv/work.rb +14 -10
- data/libuv.gemspec +11 -9
- data/spec/async_spec.rb +13 -13
- data/spec/coroutines_spec.rb +20 -50
- data/spec/defer_spec.rb +182 -311
- data/spec/dns_spec.rb +51 -41
- data/spec/dsl_spec.rb +43 -0
- data/spec/filesystem_spec.rb +65 -87
- data/spec/idle_spec.rb +19 -33
- data/spec/pipe_spec.rb +25 -32
- data/spec/tcp_spec.rb +116 -53
- data/spec/timer_spec.rb +3 -3
- data/spec/udp_spec.rb +16 -17
- data/spec/zen_spec.rb +2 -3
- metadata +37 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 328790a2659803772fb2ca5891889bda349b47fd
|
4
|
+
data.tar.gz: 0b534aaeeb7fd1fbfd11c35090e8f048be6db411
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 00f0aed38c13defc6a2e310a67e81f576fad3d90308d00575982794e4bad6d0f95204b20d5f66db130984e22223c616bc02f78b3ffbefcc2d623c8efec9b1516
|
7
|
+
data.tar.gz: da08a1d126e8d25dd0bb8fa8d0deb3a25d05a369725362d2aa5a4fbf5254aa2d5db7f6ec7f10c8b70ad2f24f2f6924018c59259276e83fa4d22eae58554a4599
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -4,60 +4,93 @@
|
|
4
4
|
|
5
5
|
[Libuv](https://github.com/libuv/libuv) is a cross platform asynchronous IO implementation that powers NodeJS. It supports sockets, both UDP and TCP, filesystem watch, TTY, Pipes and other asynchronous primitives like timer, check, prepare and idle.
|
6
6
|
|
7
|
-
The Libuv gem contains Libuv and a Ruby wrapper that implements [pipelined promises](http://en.wikipedia.org/wiki/Futures_and_promises#Promise_pipelining) for asynchronous flow control and [coroutines](http://en.wikipedia.org/wiki/Coroutine) for untangling evented code
|
7
|
+
The Libuv gem contains Libuv and a Ruby wrapper that implements [pipelined promises](http://en.wikipedia.org/wiki/Futures_and_promises#Promise_pipelining) for asynchronous flow control and [coroutines](http://en.wikipedia.org/wiki/Coroutine) / [futures](https://en.wikipedia.org/wiki/Futures_and_promises) for untangling evented code
|
8
8
|
|
9
9
|
## Usage
|
10
10
|
|
11
|
-
|
11
|
+
Libuv supports multiple reactors that can run on different threads.
|
12
|
+
|
13
|
+
For convenience the thread local or default reactor can be accessed via the `reactor` method
|
14
|
+
You can pass a block to be executed on the reactor and the reactor will run until there is nothing left to do.
|
12
15
|
|
13
16
|
```ruby
|
14
17
|
require 'libuv'
|
15
18
|
|
16
|
-
|
17
|
-
|
18
|
-
# loop = Libuv::Loop.new
|
19
|
-
|
20
|
-
loop.run do
|
21
|
-
timer = loop.timer do
|
19
|
+
reactor do |reactor|
|
20
|
+
reactor.timer {
|
22
21
|
puts "5 seconds passed"
|
23
|
-
|
24
|
-
end
|
25
|
-
timer.catch do |error|
|
26
|
-
puts "error with timer: #{error}"
|
27
|
-
end
|
28
|
-
timer.finally do
|
29
|
-
puts "timer handle was closed"
|
30
|
-
end
|
31
|
-
timer.start(5000)
|
22
|
+
}.start(5000)
|
32
23
|
end
|
24
|
+
|
25
|
+
puts "reactor stopped. No more IO to process"
|
33
26
|
```
|
34
27
|
|
35
|
-
|
28
|
+
Promises are used to simplify code flow.
|
36
29
|
|
37
30
|
```ruby
|
38
31
|
require 'libuv'
|
39
|
-
require 'libuv/coroutines'
|
40
32
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
33
|
+
reactor do |reactor|
|
34
|
+
reactor.tcp { |data, socket|
|
35
|
+
puts "received: #{data}"
|
36
|
+
socket.close
|
37
|
+
}
|
38
|
+
.connect('127.0.0.1', 3000) { |socket|
|
39
|
+
socket.start_read
|
40
|
+
.write("GET / HTTP/1.1\r\n\r\n")
|
41
|
+
}
|
42
|
+
.catch { |error|
|
43
|
+
puts "error: #{error}"
|
44
|
+
}
|
45
|
+
.finally {
|
46
|
+
puts "socket closed"
|
47
|
+
}
|
48
|
+
end
|
49
|
+
```
|
49
50
|
|
50
|
-
|
51
|
-
co timer
|
51
|
+
Continuations are used if callbacks are not defined
|
52
52
|
|
53
|
-
|
53
|
+
```ruby
|
54
|
+
require 'libuv'
|
55
|
+
|
56
|
+
reactor do |reactor|
|
57
|
+
begin
|
58
|
+
reactor.tcp { |data, socket|
|
59
|
+
puts "received: #{data}"
|
60
|
+
socket.close
|
61
|
+
}
|
62
|
+
.connect('127.0.0.1', 3000)
|
63
|
+
.start_read
|
64
|
+
.write("GET / HTTP/1.1\r\n\r\n")
|
54
65
|
rescue => error
|
55
|
-
puts "error
|
66
|
+
puts "error: #{error}"
|
56
67
|
end
|
57
68
|
end
|
58
69
|
```
|
59
70
|
|
60
|
-
|
71
|
+
Any promise can be converted into a continuation
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
require 'libuv'
|
75
|
+
|
76
|
+
reactor do |reactor|
|
77
|
+
# Perform work on the thread pool with promises
|
78
|
+
reactor.work {
|
79
|
+
10 * 2
|
80
|
+
}.then { |result|
|
81
|
+
puts "result using a promise #{result}"
|
82
|
+
}
|
83
|
+
|
84
|
+
# Use the coroutine helper to obtain the result without a callback
|
85
|
+
result = reactor.work {
|
86
|
+
10 * 3
|
87
|
+
}.value
|
88
|
+
puts "no additional callbacks here #{result}"
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
|
93
|
+
Check out the [yard documentation](http://rubydoc.info/gems/libuv/Libuv/Reactor)
|
61
94
|
|
62
95
|
|
63
96
|
## Installation
|
@@ -107,6 +140,6 @@ Windows users will additionally require:
|
|
107
140
|
* Filesystem Events
|
108
141
|
* Filesystem manipulation
|
109
142
|
* File manipulation
|
110
|
-
* Errors (with a catch-all fallback for anything unhandled on the event
|
143
|
+
* Errors (with a catch-all fallback for anything unhandled on the event reactor)
|
111
144
|
* Work queue (thread pool)
|
112
|
-
* Coroutines
|
145
|
+
* Coroutines / futures (makes use of Fibers)
|
data/lib/libuv.rb
CHANGED
@@ -1,21 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'forwardable'
|
4
|
+
require 'thread'
|
2
5
|
require 'ffi'
|
3
6
|
|
4
7
|
module Libuv
|
8
|
+
DefaultThread = Thread.current
|
9
|
+
|
5
10
|
require 'libuv/ext/ext' # The libuv ffi ext
|
6
11
|
require 'libuv/error' # List of errors (matching those in uv.h)
|
7
12
|
require 'libuv/q' # The promise library
|
8
13
|
|
9
|
-
# -- The classes required for a
|
14
|
+
# -- The classes required for a reactor instance --
|
10
15
|
require 'libuv/mixins/assertions' # Common code to check arguments
|
16
|
+
require 'libuv/mixins/accessors' # Helper methods for accessing reactor functions
|
11
17
|
require 'libuv/mixins/resource' # Common code to check for errors
|
12
18
|
require 'libuv/mixins/listener' # Common callback code
|
13
19
|
|
14
20
|
require 'libuv/handle' # Base class for most libuv functionality
|
15
|
-
require 'libuv/prepare' # Called at the end of a
|
16
|
-
require 'libuv/async' # Provide a threadsafe way to signal the event
|
21
|
+
require 'libuv/prepare' # Called at the end of a reactor cycle
|
22
|
+
require 'libuv/async' # Provide a threadsafe way to signal the event reactor
|
17
23
|
require 'libuv/timer' # High resolution timer
|
18
|
-
require 'libuv/
|
24
|
+
require 'libuv/reactor' # The libuv reactor or event reactor
|
25
|
+
require 'libuv/coroutines'
|
19
26
|
# --
|
20
27
|
|
21
28
|
autoload :FsChecks, 'libuv/mixins/fs_checks' # Common code to check file system results
|
@@ -25,7 +32,7 @@ module Libuv
|
|
25
32
|
autoload :Filesystem, 'libuv/filesystem' # Async directory manipulation
|
26
33
|
autoload :FSEvent, 'libuv/fs_event' # Notifies of changes to files and folders as they occur
|
27
34
|
autoload :Signal, 'libuv/signal' # Used to handle OS signals
|
28
|
-
autoload :Check, 'libuv/check' # Called before processing events on the
|
35
|
+
autoload :Check, 'libuv/check' # Called before processing events on the reactor
|
29
36
|
autoload :File, 'libuv/file' # Async file reading and writing
|
30
37
|
autoload :Idle, 'libuv/idle' # Called when there are no events to process
|
31
38
|
autoload :Work, 'libuv/work' # Provide work to be completed on another thread (thread pool)
|
@@ -52,4 +59,22 @@ module Libuv
|
|
52
59
|
return nil
|
53
60
|
end
|
54
61
|
end
|
62
|
+
|
63
|
+
# Include all the accessors at this level
|
64
|
+
extend Accessors
|
65
|
+
|
66
|
+
# Run the default reactor
|
67
|
+
at_exit do
|
68
|
+
reactor = Reactor.default
|
69
|
+
reactor.run if $!.nil? && reactor.run_count == 0
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
class Object
|
75
|
+
private
|
76
|
+
|
77
|
+
def reactor(&blk)
|
78
|
+
Libuv.reactor &blk
|
79
|
+
end
|
55
80
|
end
|
data/lib/libuv/async.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Libuv
|
2
4
|
class Async < Handle
|
3
5
|
|
@@ -5,14 +7,14 @@ module Libuv
|
|
5
7
|
define_callback function: :on_async
|
6
8
|
|
7
9
|
|
8
|
-
# @param
|
9
|
-
def initialize(
|
10
|
-
@
|
10
|
+
# @param reactor [::Libuv::Reactor] reactor this async callback will be associated
|
11
|
+
def initialize(reactor, callback = nil, &blk)
|
12
|
+
@reactor = reactor
|
11
13
|
@callback = callback || blk
|
12
14
|
|
13
15
|
async_ptr = ::Libuv::Ext.allocate_handle_async
|
14
16
|
on_async = callback(:on_async, async_ptr.address)
|
15
|
-
error = check_result(::Libuv::Ext.async_init(
|
17
|
+
error = check_result(::Libuv::Ext.async_init(reactor.handle, async_ptr, on_async))
|
16
18
|
|
17
19
|
super(async_ptr, error)
|
18
20
|
end
|
@@ -22,13 +24,15 @@ module Libuv
|
|
22
24
|
return if @closed
|
23
25
|
error = check_result ::Libuv::Ext.async_send(handle)
|
24
26
|
reject(error) if error
|
27
|
+
self
|
25
28
|
end
|
26
29
|
|
27
30
|
# Used to update the callback that will be triggered when async is called
|
28
31
|
#
|
29
|
-
# @param callback [Proc] the callback to be called on
|
32
|
+
# @param callback [Proc] the callback to be called on reactor prepare
|
30
33
|
def progress(callback = nil, &blk)
|
31
34
|
@callback = callback || blk
|
35
|
+
self
|
32
36
|
end
|
33
37
|
|
34
38
|
|
@@ -36,11 +40,13 @@ module Libuv
|
|
36
40
|
|
37
41
|
|
38
42
|
def on_async(handle)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
43
|
+
::Fiber.new {
|
44
|
+
begin
|
45
|
+
@callback.call
|
46
|
+
rescue Exception => e
|
47
|
+
@reactor.log e, 'performing async callback'
|
48
|
+
end
|
49
|
+
}.resume
|
44
50
|
end
|
45
51
|
end
|
46
52
|
end
|
data/lib/libuv/check.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Libuv
|
2
4
|
class Check < Handle
|
3
5
|
|
@@ -5,14 +7,14 @@ module Libuv
|
|
5
7
|
define_callback function: :on_check
|
6
8
|
|
7
9
|
|
8
|
-
# @param
|
9
|
-
# @param callback [Proc] callback to be called on
|
10
|
-
def initialize(
|
11
|
-
@
|
10
|
+
# @param reactor [::Libuv::Reactor] reactor this check will be associated
|
11
|
+
# @param callback [Proc] callback to be called on reactor check
|
12
|
+
def initialize(reactor, callback = nil, &blk)
|
13
|
+
@reactor = reactor
|
12
14
|
@callback = callback || blk
|
13
15
|
|
14
16
|
check_ptr = ::Libuv::Ext.allocate_handle_check
|
15
|
-
error = check_result(::Libuv::Ext.check_init(
|
17
|
+
error = check_result(::Libuv::Ext.check_init(reactor.handle, check_ptr))
|
16
18
|
|
17
19
|
super(check_ptr, error)
|
18
20
|
end
|
@@ -22,6 +24,7 @@ module Libuv
|
|
22
24
|
return if @closed
|
23
25
|
error = check_result ::Libuv::Ext.check_start(handle, callback(:on_check))
|
24
26
|
reject(error) if error
|
27
|
+
self
|
25
28
|
end
|
26
29
|
|
27
30
|
# Disables the check handler.
|
@@ -29,13 +32,15 @@ module Libuv
|
|
29
32
|
return if @closed
|
30
33
|
error = check_result ::Libuv::Ext.check_stop(handle)
|
31
34
|
reject(error) if error
|
35
|
+
self
|
32
36
|
end
|
33
37
|
|
34
|
-
# Used to update the callback that will be triggered on
|
38
|
+
# Used to update the callback that will be triggered on reactor check
|
35
39
|
#
|
36
|
-
# @param callback [Proc] the callback to be called on
|
40
|
+
# @param callback [Proc] the callback to be called on reactor check
|
37
41
|
def progress(callback = nil, &blk)
|
38
42
|
@callback = callback || blk
|
43
|
+
self
|
39
44
|
end
|
40
45
|
|
41
46
|
|
@@ -43,11 +48,13 @@ module Libuv
|
|
43
48
|
|
44
49
|
|
45
50
|
def on_check(handle)
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
+
::Fiber.new {
|
52
|
+
begin
|
53
|
+
@callback.call
|
54
|
+
rescue Exception => e
|
55
|
+
@reactor.log e, 'performing check callback'
|
56
|
+
end
|
57
|
+
}.resume
|
51
58
|
end
|
52
59
|
end
|
53
60
|
end
|
data/lib/libuv/coroutines.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'fiber'
|
2
4
|
|
3
|
-
class
|
4
|
-
|
5
|
+
class CoroutineRejection < RuntimeError
|
6
|
+
attr_accessor :value
|
5
7
|
end
|
6
8
|
|
7
9
|
class Object
|
@@ -15,32 +17,57 @@ class Object
|
|
15
17
|
# @return [Object] Returns the result of a single promise or an array of results if provided multiple promises
|
16
18
|
# @raise [Exception] if the promise is rejected
|
17
19
|
def co(*yieldable, &block)
|
20
|
+
on_reactor = Libuv::Reactor.current
|
21
|
+
raise 'must be running on a reactor thread to use coroutines' unless on_reactor
|
22
|
+
|
18
23
|
f = Fiber.current
|
19
24
|
wasError = false
|
20
25
|
|
21
|
-
# Convert the input into a promise
|
26
|
+
# Convert the input into a promise on the current reactor
|
22
27
|
if yieldable.length == 1
|
23
28
|
promise = yieldable[0]
|
29
|
+
# Passed independently as this is often overwritten for performance
|
30
|
+
promise.progress(block) if block_given?
|
24
31
|
else
|
25
|
-
promise =
|
32
|
+
promise = on_reactor.all(*yieldable)
|
26
33
|
end
|
27
34
|
|
28
35
|
# Use the promise to resume the Fiber
|
29
36
|
promise.then(proc { |res|
|
30
|
-
|
37
|
+
if Libuv::Reactor.current == on_reactor
|
38
|
+
f.resume res
|
39
|
+
else
|
40
|
+
on_reactor.schedule { f.resume(res) }
|
41
|
+
end
|
31
42
|
}, proc { |err|
|
32
43
|
wasError = true
|
33
|
-
|
44
|
+
if Libuv::Reactor.current == on_reactor
|
45
|
+
f.resume err
|
46
|
+
else
|
47
|
+
on_reactor.schedule { f.resume(err) }
|
48
|
+
end
|
34
49
|
})
|
35
50
|
|
36
|
-
#
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
result = Fiber.yield
|
51
|
+
# We want to prevent the reactor from stopping while we are waiting on a response
|
52
|
+
on_reactor.ref
|
53
|
+
result = Fiber.yield # Assign the result from the resume
|
54
|
+
on_reactor.unref
|
41
55
|
|
42
56
|
# Either return the result or raise an error
|
43
|
-
|
57
|
+
if wasError
|
58
|
+
if result.is_a?(Exception)
|
59
|
+
raise result
|
60
|
+
else
|
61
|
+
e = case result
|
62
|
+
when String, Symbol
|
63
|
+
CoroutineRejection.new(result.to_s)
|
64
|
+
else
|
65
|
+
CoroutineRejection.new
|
66
|
+
end
|
67
|
+
e.value = result
|
68
|
+
raise e
|
69
|
+
end
|
70
|
+
end
|
44
71
|
result
|
45
72
|
end
|
46
73
|
end
|
data/lib/libuv/dns.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Libuv
|
2
4
|
class Dns < Q::DeferredPromise
|
3
5
|
include Resource, Listener, Net
|
@@ -28,11 +30,11 @@ module Libuv
|
|
28
30
|
end
|
29
31
|
|
30
32
|
|
31
|
-
# @param
|
33
|
+
# @param reactor [::Libuv::Reactor] reactor this work request will be associated
|
32
34
|
# @param domain [String] the domain name to resolve
|
33
35
|
# @param port [Integer, String] the port we wish to use
|
34
|
-
def initialize(
|
35
|
-
super(
|
36
|
+
def initialize(reactor, domain, port, hint = :IPv4, wait: true)
|
37
|
+
super(reactor, reactor.defer)
|
36
38
|
|
37
39
|
@domain = domain
|
38
40
|
@port = port
|
@@ -42,13 +44,15 @@ module Libuv
|
|
42
44
|
@error = nil # error in callback
|
43
45
|
|
44
46
|
@instance_id = @pointer.address
|
45
|
-
error = check_result ::Libuv::Ext.getaddrinfo(@
|
47
|
+
error = check_result ::Libuv::Ext.getaddrinfo(@reactor, @pointer, callback(:on_complete), domain, port.to_s, HINTS[hint])
|
46
48
|
|
47
49
|
if error
|
48
50
|
::Libuv::Ext.free(@pointer)
|
49
51
|
@complete = true
|
50
52
|
@defer.reject(error)
|
51
53
|
end
|
54
|
+
|
55
|
+
co(@defer.promise) if wait
|
52
56
|
end
|
53
57
|
|
54
58
|
# Indicates if the lookup has completed yet or not.
|
@@ -67,22 +71,25 @@ module Libuv
|
|
67
71
|
::Libuv::Ext.free(req)
|
68
72
|
|
69
73
|
e = check_result(status)
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
begin
|
74
|
-
current = addrinfo
|
75
|
-
@results = []
|
76
|
-
while !current.null?
|
77
|
-
@results << get_ip_and_port(current[:addr])
|
78
|
-
current = current[:next]
|
79
|
-
end
|
80
|
-
@defer.resolve(@results)
|
81
|
-
rescue Exception => e
|
74
|
+
|
75
|
+
::Fiber.new {
|
76
|
+
if e
|
82
77
|
@defer.reject(e)
|
78
|
+
else
|
79
|
+
begin
|
80
|
+
current = addrinfo
|
81
|
+
@results = []
|
82
|
+
while !current.null?
|
83
|
+
@results << get_ip_and_port(current[:addr])
|
84
|
+
current = current[:next]
|
85
|
+
end
|
86
|
+
@defer.resolve(@results)
|
87
|
+
rescue Exception => e
|
88
|
+
@defer.reject(e)
|
89
|
+
end
|
90
|
+
::Libuv::Ext.freeaddrinfo(addrinfo)
|
83
91
|
end
|
84
|
-
|
85
|
-
end
|
92
|
+
}.resume
|
86
93
|
|
87
94
|
# Clean up references
|
88
95
|
cleanup_callbacks
|