libuv 0.10.0 → 0.10.2
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.
- data/.gitignore +17 -17
- data/.gitmodules +3 -3
- data/.rspec +1 -1
- data/.travis.yml +16 -16
- data/Gemfile +2 -2
- data/LICENSE +23 -23
- data/README.md +82 -73
- data/Rakefile +31 -31
- data/lib/libuv.rb +53 -34
- data/lib/libuv/async.rb +47 -33
- data/lib/libuv/check.rb +55 -48
- data/lib/libuv/error.rb +70 -70
- data/lib/libuv/ext/ext.rb +264 -256
- data/lib/libuv/ext/platform/darwin_x64.rb +12 -12
- data/lib/libuv/ext/platform/linux.rb +7 -7
- data/lib/libuv/ext/platform/unix.rb +13 -13
- data/lib/libuv/ext/platform/windows.rb +26 -26
- data/lib/libuv/ext/tasks.rb +27 -27
- data/lib/libuv/ext/tasks/mac.rb +23 -23
- data/lib/libuv/ext/tasks/unix.rb +23 -23
- data/lib/libuv/ext/tasks/win.rb +11 -11
- data/lib/libuv/ext/types.rb +234 -229
- data/lib/libuv/file.rb +192 -0
- data/lib/libuv/filesystem.rb +233 -0
- data/lib/libuv/fs_event.rb +31 -31
- data/lib/libuv/handle.rb +85 -81
- data/lib/libuv/idle.rb +56 -49
- data/lib/libuv/loop.rb +338 -310
- data/lib/libuv/{assertions.rb → mixins/assertions.rb} +23 -23
- data/lib/libuv/mixins/fs_checks.rb +55 -0
- data/lib/libuv/{listener.rb → mixins/listener.rb} +34 -34
- data/lib/libuv/{net.rb → mixins/net.rb} +37 -37
- data/lib/libuv/{resource.rb → mixins/resource.rb} +27 -27
- data/lib/libuv/{stream.rb → mixins/stream.rb} +143 -123
- data/lib/libuv/pipe.rb +197 -97
- data/lib/libuv/prepare.rb +56 -49
- data/lib/libuv/q.rb +1 -1
- data/lib/libuv/signal.rb +51 -0
- data/lib/libuv/tcp.rb +204 -193
- data/lib/libuv/timer.rb +88 -75
- data/lib/libuv/tty.rb +37 -34
- data/lib/libuv/udp.rb +273 -255
- data/lib/libuv/version.rb +3 -3
- data/lib/libuv/work.rb +63 -61
- data/libuv.gemspec +54 -54
- data/spec/async_spec.rb +60 -60
- data/spec/cpu_spec.rb +10 -0
- data/spec/defer_spec.rb +980 -980
- data/spec/filesystem_spec.rb +119 -0
- data/spec/idle_spec.rb +56 -56
- data/spec/pipe_spec.rb +153 -148
- data/spec/tcp_spec.rb +203 -188
- metadata +73 -49
- checksums.yaml +0 -15
- data/lib/libuv/simple_async.rb +0 -28
@@ -1,23 +1,23 @@
|
|
1
|
-
module Libuv
|
2
|
-
module Assertions
|
3
|
-
MSG_NO_PROC = 'no block given'
|
4
|
-
|
5
|
-
def assert_block(proc, msg = MSG_NO_PROC)
|
6
|
-
raise ArgumentError, msg, caller if proc.nil?
|
7
|
-
end
|
8
|
-
|
9
|
-
def assert_type(type, actual, msg = nil)
|
10
|
-
if not actual.kind_of?(type)
|
11
|
-
msg ||= "value #{actual.inspect} is not a valid #{type}"
|
12
|
-
raise ArgumentError, msg, caller
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def assert_boolean(actual, msg = nil)
|
17
|
-
if not (actual.kind_of?(TrueClass) || actual.kind_of?(FalseClass))
|
18
|
-
msg ||= "value #{actual.inspect} is not a valid Boolean"
|
19
|
-
raise ArgumentError, msg, caller
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
1
|
+
module Libuv
|
2
|
+
module Assertions
|
3
|
+
MSG_NO_PROC = 'no block given'
|
4
|
+
|
5
|
+
def assert_block(proc, msg = MSG_NO_PROC)
|
6
|
+
raise ArgumentError, msg, caller if proc.nil?
|
7
|
+
end
|
8
|
+
|
9
|
+
def assert_type(type, actual, msg = nil)
|
10
|
+
if not actual.kind_of?(type)
|
11
|
+
msg ||= "value #{actual.inspect} is not a valid #{type}"
|
12
|
+
raise ArgumentError, msg, caller
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def assert_boolean(actual, msg = nil)
|
17
|
+
if not (actual.kind_of?(TrueClass) || actual.kind_of?(FalseClass))
|
18
|
+
msg ||= "value #{actual.inspect} is not a valid Boolean"
|
19
|
+
raise ArgumentError, msg, caller
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
|
2
|
+
module Libuv
|
3
|
+
module FsChecks
|
4
|
+
|
5
|
+
|
6
|
+
def stat
|
7
|
+
@stat_deferred = @loop.defer
|
8
|
+
|
9
|
+
request = ::Libuv::Ext.create_request(:uv_fs)
|
10
|
+
pre_check @stat_deferred, request, ::Libuv::Ext.fs_fstat(@loop.handle, request, @fileno, callback(:on_stat))
|
11
|
+
@stat_deferred.promise
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
|
18
|
+
def on_stat(req)
|
19
|
+
if post_check(req, @stat_deferred)
|
20
|
+
uv_stat = req[:stat]
|
21
|
+
uv_members = uv_stat.members
|
22
|
+
values = UvStat.members.map { |k| uv_members.include?(k) ? uv_stat[k] : nil }
|
23
|
+
uv_stat = UvStat.new(*values)
|
24
|
+
|
25
|
+
cleanup(req)
|
26
|
+
@stat_deferred.resolve(uv_stat)
|
27
|
+
end
|
28
|
+
@stat_deferred = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def pre_check(deferrable, request, result)
|
32
|
+
error = check_result result
|
33
|
+
if error
|
34
|
+
::Libuv::Ext.free(request)
|
35
|
+
deferrable.reject(error)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def cleanup(req)
|
40
|
+
::Libuv::Ext.fs_req_cleanup(req)
|
41
|
+
::Libuv::Ext.free(req)
|
42
|
+
end
|
43
|
+
|
44
|
+
def post_check(req, deferrable)
|
45
|
+
error = check_result(req[:result])
|
46
|
+
if error
|
47
|
+
cleanup(req)
|
48
|
+
deferrable.reject(error)
|
49
|
+
false
|
50
|
+
else
|
51
|
+
true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -1,34 +1,34 @@
|
|
1
|
-
require 'thread_safe'
|
2
|
-
require 'set'
|
3
|
-
|
4
|
-
module Libuv
|
5
|
-
module Listener
|
6
|
-
|
7
|
-
|
8
|
-
private
|
9
|
-
|
10
|
-
|
11
|
-
CALLBACKS = ThreadSafe::Cache.new
|
12
|
-
|
13
|
-
|
14
|
-
def callbacks
|
15
|
-
@callbacks ||= Set.new
|
16
|
-
end
|
17
|
-
|
18
|
-
def callback(name)
|
19
|
-
const_name = "#{name}_#{object_id}".to_sym
|
20
|
-
unless CALLBACKS[const_name]
|
21
|
-
callbacks << const_name
|
22
|
-
CALLBACKS[const_name] = method(name)
|
23
|
-
end
|
24
|
-
CALLBACKS[const_name]
|
25
|
-
end
|
26
|
-
|
27
|
-
def clear_callbacks
|
28
|
-
callbacks.each do |name|
|
29
|
-
CALLBACKS.delete(name)
|
30
|
-
end
|
31
|
-
callbacks.clear
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
1
|
+
require 'thread_safe'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Libuv
|
5
|
+
module Listener
|
6
|
+
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
|
11
|
+
CALLBACKS = ThreadSafe::Cache.new
|
12
|
+
|
13
|
+
|
14
|
+
def callbacks
|
15
|
+
@callbacks ||= Set.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def callback(name)
|
19
|
+
const_name = "#{name}_#{object_id}".to_sym
|
20
|
+
unless CALLBACKS[const_name]
|
21
|
+
callbacks << const_name
|
22
|
+
CALLBACKS[const_name] = method(name)
|
23
|
+
end
|
24
|
+
CALLBACKS[const_name]
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear_callbacks
|
28
|
+
callbacks.each do |name|
|
29
|
+
CALLBACKS.delete(name)
|
30
|
+
end
|
31
|
+
callbacks.clear
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,38 +1,38 @@
|
|
1
|
-
require 'socket'
|
2
|
-
|
3
|
-
module Libuv
|
4
|
-
module Net
|
5
|
-
|
6
|
-
|
7
|
-
IP_ARGUMENT_ERROR = "ip must be a String".freeze # Arguments specifying an IP address
|
8
|
-
PORT_ARGUMENT_ERROR = "port must be an Integer".freeze # Arguments specifying an IP port
|
9
|
-
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
|
14
|
-
def get_sockaddr_and_len
|
15
|
-
sockaddr = FFI::MemoryPointer.new(UV::Sockaddr)
|
16
|
-
len = FFI::MemoryPointer.new(:int)
|
17
|
-
len.put_int(0, UV::Sockaddr.size)
|
18
|
-
[sockaddr, len]
|
19
|
-
end
|
20
|
-
|
21
|
-
def get_ip_and_port(sockaddr, len=nil)
|
22
|
-
if sockaddr[:sa_family] == Socket::Constants::AF_INET6
|
23
|
-
len ||= Socket::Constants::INET6_ADDRSTRLEN
|
24
|
-
sockaddr_in6 = UV::SockaddrIn6.new(sockaddr.pointer)
|
25
|
-
ip_ptr = FFI::MemoryPointer.new(:char, len)
|
26
|
-
::Libuv::Ext.ip6_name(sockaddr_in6, ip_ptr, len)
|
27
|
-
port = ::Libuv::Ext.ntohs(sockaddr_in6[:sin6_port])
|
28
|
-
else
|
29
|
-
len ||= Socket::Constants::INET_ADDRSTRLEN
|
30
|
-
sockaddr_in = UV::SockaddrIn.new(sockaddr.pointer)
|
31
|
-
ip_ptr = FFI::MemoryPointer.new(:char, len)
|
32
|
-
::Libuv::Ext.ip4_name(sockaddr_in, ip_ptr, len)
|
33
|
-
port = ::Libuv::Ext.ntohs(sockaddr_in[:sin_port])
|
34
|
-
end
|
35
|
-
[ip_ptr.read_string, port]
|
36
|
-
end
|
37
|
-
end
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Libuv
|
4
|
+
module Net
|
5
|
+
|
6
|
+
|
7
|
+
IP_ARGUMENT_ERROR = "ip must be a String".freeze # Arguments specifying an IP address
|
8
|
+
PORT_ARGUMENT_ERROR = "port must be an Integer".freeze # Arguments specifying an IP port
|
9
|
+
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
|
14
|
+
def get_sockaddr_and_len
|
15
|
+
sockaddr = FFI::MemoryPointer.new(UV::Sockaddr)
|
16
|
+
len = FFI::MemoryPointer.new(:int)
|
17
|
+
len.put_int(0, UV::Sockaddr.size)
|
18
|
+
[sockaddr, len]
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_ip_and_port(sockaddr, len=nil)
|
22
|
+
if sockaddr[:sa_family] == Socket::Constants::AF_INET6
|
23
|
+
len ||= Socket::Constants::INET6_ADDRSTRLEN
|
24
|
+
sockaddr_in6 = UV::SockaddrIn6.new(sockaddr.pointer)
|
25
|
+
ip_ptr = FFI::MemoryPointer.new(:char, len)
|
26
|
+
::Libuv::Ext.ip6_name(sockaddr_in6, ip_ptr, len)
|
27
|
+
port = ::Libuv::Ext.ntohs(sockaddr_in6[:sin6_port])
|
28
|
+
else
|
29
|
+
len ||= Socket::Constants::INET_ADDRSTRLEN
|
30
|
+
sockaddr_in = UV::SockaddrIn.new(sockaddr.pointer)
|
31
|
+
ip_ptr = FFI::MemoryPointer.new(:char, len)
|
32
|
+
::Libuv::Ext.ip4_name(sockaddr_in, ip_ptr, len)
|
33
|
+
port = ::Libuv::Ext.ntohs(sockaddr_in[:sin_port])
|
34
|
+
end
|
35
|
+
[ip_ptr.read_string, port]
|
36
|
+
end
|
37
|
+
end
|
38
38
|
end
|
@@ -1,28 +1,28 @@
|
|
1
|
-
module Libuv
|
2
|
-
module Resource
|
3
|
-
|
4
|
-
|
5
|
-
def resolve(deferred, rc)
|
6
|
-
if rc.nil? || rc >= 0
|
7
|
-
deferred.resolve(nil)
|
8
|
-
else
|
9
|
-
deferred.reject(@loop.lookup_error(rc))
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def check_result!(rc)
|
14
|
-
e = @loop.lookup_error(rc) unless rc.nil? || rc >= 0
|
15
|
-
raise e if e
|
16
|
-
end
|
17
|
-
|
18
|
-
def check_result(rc)
|
19
|
-
@loop.lookup_error(rc) unless rc.nil? || rc >= 0
|
20
|
-
end
|
21
|
-
|
22
|
-
def to_ptr
|
23
|
-
@pointer
|
24
|
-
end
|
25
|
-
|
26
|
-
|
27
|
-
end
|
1
|
+
module Libuv
|
2
|
+
module Resource
|
3
|
+
|
4
|
+
|
5
|
+
def resolve(deferred, rc)
|
6
|
+
if rc.nil? || rc >= 0
|
7
|
+
deferred.resolve(nil)
|
8
|
+
else
|
9
|
+
deferred.reject(@loop.lookup_error(rc))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def check_result!(rc)
|
14
|
+
e = @loop.lookup_error(rc) unless rc.nil? || rc >= 0
|
15
|
+
raise e if e
|
16
|
+
end
|
17
|
+
|
18
|
+
def check_result(rc)
|
19
|
+
@loop.lookup_error(rc) unless rc.nil? || rc >= 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_ptr
|
23
|
+
@pointer
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
end
|
28
28
|
end
|
@@ -1,124 +1,144 @@
|
|
1
|
-
module Libuv
|
2
|
-
module Stream
|
3
|
-
|
4
|
-
|
5
|
-
BACKLOG_ERROR = "backlog must be an Integer".freeze
|
6
|
-
WRITE_ERROR = "data must be a String".freeze
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
deferred
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
e
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
1
|
+
module Libuv
|
2
|
+
module Stream
|
3
|
+
|
4
|
+
|
5
|
+
BACKLOG_ERROR = "backlog must be an Integer".freeze
|
6
|
+
WRITE_ERROR = "data must be a String".freeze
|
7
|
+
STREAM_CLOSED_ERROR = "unable to write to a closed stream".freeze
|
8
|
+
CLOSED_HANDLE_ERROR = "handle closed before accept called".freeze
|
9
|
+
|
10
|
+
|
11
|
+
def listen(backlog)
|
12
|
+
return if @closed
|
13
|
+
assert_type(Integer, backlog, BACKLOG_ERROR)
|
14
|
+
error = check_result ::Libuv::Ext.listen(handle, Integer(backlog), callback(:on_listen))
|
15
|
+
reject(error) if error
|
16
|
+
end
|
17
|
+
|
18
|
+
# Starts reading from the handle
|
19
|
+
def start_read
|
20
|
+
return if @closed
|
21
|
+
error = check_result ::Libuv::Ext.read_start(handle, callback(:on_allocate), callback(:on_read))
|
22
|
+
reject(error) if error
|
23
|
+
end
|
24
|
+
|
25
|
+
# Stops reading from the handle
|
26
|
+
def stop_read
|
27
|
+
return if @closed
|
28
|
+
error = check_result ::Libuv::Ext.read_stop(handle)
|
29
|
+
reject(error) if error
|
30
|
+
end
|
31
|
+
|
32
|
+
# Shutsdown the writes on the handle waiting until the last write is complete before triggering the callback
|
33
|
+
def shutdown
|
34
|
+
return if @closed
|
35
|
+
error = check_result ::Libuv::Ext.shutdown(::Libuv::Ext.create_request(:uv_shutdown), handle, callback(:on_shutdown))
|
36
|
+
reject(error) if error
|
37
|
+
end
|
38
|
+
|
39
|
+
def write(data)
|
40
|
+
# NOTE:: Similar to udp.rb -> write
|
41
|
+
deferred = @loop.defer
|
42
|
+
if !@closed
|
43
|
+
begin
|
44
|
+
assert_type(String, data, WRITE_ERROR)
|
45
|
+
|
46
|
+
size = data.respond_to?(:bytesize) ? data.bytesize : data.size
|
47
|
+
buffer = ::Libuv::Ext.buf_init(FFI::MemoryPointer.from_string(data), size)
|
48
|
+
|
49
|
+
# local as this variable will be avaliable until the handle is closed
|
50
|
+
@write_callbacks ||= []
|
51
|
+
|
52
|
+
#
|
53
|
+
# create the curried callback
|
54
|
+
#
|
55
|
+
callback = FFI::Function.new(:void, [:pointer, :int]) do |req, status|
|
56
|
+
::Libuv::Ext.free(req)
|
57
|
+
# remove the callback from the array
|
58
|
+
# assumes writes are done in order
|
59
|
+
promise = @write_callbacks.shift[0]
|
60
|
+
resolve promise, status
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
@write_callbacks << [deferred, callback]
|
65
|
+
req = ::Libuv::Ext.create_request(:uv_write)
|
66
|
+
error = check_result ::Libuv::Ext.write(req, handle, buffer, 1, callback)
|
67
|
+
|
68
|
+
if error
|
69
|
+
@write_callbacks.pop
|
70
|
+
::Libuv::Ext.free(req)
|
71
|
+
deferred.reject(error)
|
72
|
+
|
73
|
+
reject(error) # close the handle
|
74
|
+
end
|
75
|
+
rescue Exception => e
|
76
|
+
deferred.reject(e) # this write exception may not be fatal
|
77
|
+
end
|
78
|
+
else
|
79
|
+
deferred.reject(RuntimeError.new(STREAM_CLOSED_ERROR))
|
80
|
+
end
|
81
|
+
deferred.promise
|
82
|
+
end
|
83
|
+
|
84
|
+
def readable?
|
85
|
+
return false if @closed
|
86
|
+
::Libuv::Ext.is_readable(handle) > 0
|
87
|
+
end
|
88
|
+
|
89
|
+
def writable?
|
90
|
+
return false if @closed
|
91
|
+
::Libuv::Ext.is_writable(handle) > 0
|
92
|
+
end
|
93
|
+
|
94
|
+
def progress(callback = nil, &blk)
|
95
|
+
@progress = callback || blk
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
|
102
|
+
def on_listen(server, status)
|
103
|
+
e = check_result(status)
|
104
|
+
|
105
|
+
if e
|
106
|
+
reject(e) # is this cause for closing the handle?
|
107
|
+
else
|
108
|
+
begin
|
109
|
+
@on_listen.call(self)
|
110
|
+
rescue Exception => e
|
111
|
+
@loop.log :error, :stream_listen_cb, e
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def on_allocate(client, suggested_size)
|
117
|
+
::Libuv::Ext.buf_init(::Libuv::Ext.malloc(suggested_size), suggested_size)
|
118
|
+
end
|
119
|
+
|
120
|
+
def on_read(handle, nread, buf)
|
121
|
+
e = check_result(nread)
|
122
|
+
base = buf[:base]
|
123
|
+
|
124
|
+
if e
|
125
|
+
::Libuv::Ext.free(base)
|
126
|
+
reject(e)
|
127
|
+
else
|
128
|
+
data = base.read_string(nread)
|
129
|
+
::Libuv::Ext.free(base)
|
130
|
+
begin
|
131
|
+
@progress.call data, self
|
132
|
+
rescue Exception => e
|
133
|
+
@loop.log :error, :stream_progress_cb, e
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def on_shutdown(req, status)
|
139
|
+
::Libuv::Ext.free(req)
|
140
|
+
@close_error = check_result(status)
|
141
|
+
close
|
142
|
+
end
|
143
|
+
end
|
124
144
|
end
|