rbgo 0.2.9 → 0.2.15
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 +4 -4
- data/README.md +76 -11
- data/lib/rbgo/actor.rb +27 -11
- data/lib/rbgo/corun.rb +51 -1
- data/lib/rbgo/io_machine.rb +229 -11
- data/lib/rbgo/reentrant_mutex.rb +84 -0
- data/lib/rbgo/reentrant_rw_mutex.rb +96 -0
- data/lib/rbgo/semaphore.rb +99 -0
- data/lib/rbgo/synchronized_collection.rb +75 -0
- data/lib/rbgo/task_list.rb +102 -0
- data/lib/rbgo/version.rb +1 -1
- data/lib/rbgo/wait_group.rb +2 -1
- data/lib/rbgo.rb +5 -0
- data/rbgo.gemspec +1 -9
- metadata +8 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c619996e5259a7e326d0a4740643b440895edf64534c7211df3acb912842b807
|
4
|
+
data.tar.gz: f6fc47754f814abd62bd0c148d867e14a4f7f594596ae6a2bbdb942defc219ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aeba922a5d896fa22477dd00d35516613ac0ac90ebfc6ec18205390b8b6944b698a2fc0242d3b576cfee7f6d7605e11a4051dd7f5a6ce92872ae0201e576dcd7
|
7
|
+
data.tar.gz: 49d515cfa355b8e9c1ad74629b37a2887c4ca0fed48e637a6a7f25549724f00cd72e12b5786c88824b05f5b169aaf6338a6ad5bda0973ab98bbd3c0a67c29c95
|
data/README.md
CHANGED
@@ -1,11 +1,3 @@
|
|
1
|
-
# I like the way how golang does in concurrency.
|
2
|
-
|
3
|
-
You can produce a light weight routine easily with a keyword 'go' and communicate with each routine by channel.
|
4
|
-
|
5
|
-
In MRI the GIL prevents you from running code parallelly, but there ara also other ruby implementations such as TruffleRuby or JRuby which can utilize all CPU cores.
|
6
|
-
|
7
|
-
In MRI write program to run concurrently even not parallelly is also important.
|
8
|
-
|
9
1
|
# This project is trying to help writing concurrent program with ruby a little easier.
|
10
2
|
|
11
3
|
# select_chan
|
@@ -129,6 +121,82 @@ actor = Rbgo::Actor.new do|msg, actor|
|
|
129
121
|
|
130
122
|
actor.send_msg :msg1 #won't block
|
131
123
|
|
124
|
+
# TaskList do task in order but do it asynchronously, task may be executed in different thread
|
125
|
+
|
126
|
+
task_list = Rbgo::TaskList.new
|
127
|
+
|
128
|
+
task_1 = proc do
|
129
|
+
puts 'first'
|
130
|
+
1
|
131
|
+
end
|
132
|
+
|
133
|
+
task_2 = proc do |last_result|
|
134
|
+
puts 'second'
|
135
|
+
puts "last task result #{last_result}"
|
136
|
+
sleep 5
|
137
|
+
2
|
138
|
+
end
|
139
|
+
|
140
|
+
task_3 = proc do |last_result|
|
141
|
+
puts 'third'
|
142
|
+
puts "last task result #{last_result}"
|
143
|
+
3
|
144
|
+
end
|
145
|
+
|
146
|
+
task_list << task_1
|
147
|
+
task_list.add(task_2, timeout: 2, skip_on_exception: true)
|
148
|
+
task_list << task_3
|
149
|
+
|
150
|
+
task_list.start
|
151
|
+
task_list.wait
|
152
|
+
p 'wait done.'
|
153
|
+
p task_list.complete?
|
154
|
+
p task_list.last_error
|
155
|
+
|
156
|
+
# Reentrant/ReadWirte Mutex and Semaphore
|
157
|
+
|
158
|
+
m = Rbgo::ReentrantMutex.new
|
159
|
+
m.synchronize do
|
160
|
+
m.synchronize do
|
161
|
+
puts "I'm here!"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
m2 = Rbgo::RWMutex.new
|
166
|
+
5.times do
|
167
|
+
go do
|
168
|
+
m2.synchronize_r do
|
169
|
+
puts "got the read lock"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
go do
|
174
|
+
m2.synchronize_w do
|
175
|
+
puts "got the write lock"
|
176
|
+
m2.synchronize_r do
|
177
|
+
puts 'now, downgrade to read lock'
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
sleep 2
|
183
|
+
|
184
|
+
go do
|
185
|
+
s = Rbgo::Semaphore.new(5)
|
186
|
+
|
187
|
+
s.acquire(4) do
|
188
|
+
puts 'got the permits'
|
189
|
+
end
|
190
|
+
|
191
|
+
s.acquire_all do
|
192
|
+
puts 'got all available permits'
|
193
|
+
end
|
194
|
+
|
195
|
+
ok = s.try_acquire do
|
196
|
+
puts 'try success'
|
197
|
+
end
|
198
|
+
puts 'try ok!' if ok
|
199
|
+
end
|
132
200
|
```
|
133
201
|
# IOMachine
|
134
202
|
IOMachine wrap nio4r to do IO operation asynchronously.
|
@@ -207,9 +275,6 @@ io_w.close
|
|
207
275
|
# fiber.resume
|
208
276
|
# end
|
209
277
|
#
|
210
|
-
# go! do
|
211
|
-
# io_r.yield_read # will not do yield. just do normal IO#read
|
212
|
-
# end
|
213
278
|
|
214
279
|
```
|
215
280
|
|
data/lib/rbgo/actor.rb
CHANGED
@@ -1,23 +1,25 @@
|
|
1
1
|
require 'thread'
|
2
|
-
|
2
|
+
require_relative 'once'
|
3
3
|
|
4
4
|
module Rbgo
|
5
5
|
class Actor
|
6
6
|
private
|
7
|
-
|
7
|
+
|
8
|
+
attr_accessor :mail_box, :once_for_msg_loop
|
8
9
|
|
9
10
|
public
|
10
11
|
|
11
12
|
attr_accessor :handler
|
12
13
|
|
13
14
|
def initialize(&handler)
|
14
|
-
self.handler
|
15
|
-
self.mail_box
|
16
|
-
|
15
|
+
self.handler = handler
|
16
|
+
self.mail_box = Queue.new
|
17
|
+
self.once_for_msg_loop = Once.new
|
17
18
|
end
|
18
19
|
|
19
20
|
def send_msg(msg)
|
20
21
|
mail_box << msg
|
22
|
+
start_msg_loop
|
21
23
|
nil
|
22
24
|
end
|
23
25
|
|
@@ -34,14 +36,28 @@ module Rbgo
|
|
34
36
|
private
|
35
37
|
|
36
38
|
def start_msg_loop
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
once_for_msg_loop.do do
|
40
|
+
CoRun::Routine.new(new_thread: false, queue_tag: :default) do
|
41
|
+
loop do
|
42
|
+
begin
|
43
|
+
msg = mail_box.deq(true)
|
44
|
+
rescue ThreadError
|
45
|
+
self.once_for_msg_loop = Once.new
|
46
|
+
start_msg_loop unless mail_box.empty?
|
47
|
+
break
|
48
|
+
else
|
49
|
+
call_handler(msg)
|
50
|
+
end
|
51
|
+
Fiber.yield
|
52
|
+
end
|
43
53
|
end
|
44
54
|
end
|
45
55
|
end
|
56
|
+
|
57
|
+
def call_handler(msg)
|
58
|
+
handler.call(msg, self) if handler
|
59
|
+
rescue Exception => ex
|
60
|
+
STDERR.puts ex
|
61
|
+
end
|
46
62
|
end
|
47
63
|
end
|
data/lib/rbgo/corun.rb
CHANGED
@@ -8,12 +8,18 @@ require_relative 'once'
|
|
8
8
|
module Rbgo
|
9
9
|
module CoRun
|
10
10
|
IS_CORUN_FIBER = :is_corun_fiber_bbc0f70e
|
11
|
+
LOCAL_TASK_QUEUES = :local_task_queues_bbc0f70e
|
11
12
|
YIELD_IO_OPERATION = :yield_bbc0f70e
|
12
13
|
|
13
14
|
def self.is_in_corun_fiber?
|
14
15
|
!!Thread.current[IS_CORUN_FIBER]
|
15
16
|
end
|
16
17
|
|
18
|
+
def self.have_other_task_on_thread?
|
19
|
+
queues = Thread.current.thread_variable_get(LOCAL_TASK_QUEUES)
|
20
|
+
queues&.any?{|q| !q.empty?}
|
21
|
+
end
|
22
|
+
|
17
23
|
def self.read_from(io, length: nil)
|
18
24
|
if is_in_corun_fiber?
|
19
25
|
return "" if length == 0
|
@@ -62,6 +68,33 @@ module Rbgo
|
|
62
68
|
end
|
63
69
|
end
|
64
70
|
|
71
|
+
def self.recv_from(sock, maxlen:, flags: 0)
|
72
|
+
if is_in_corun_fiber?
|
73
|
+
receipt = Scheduler.instance.io_machine.do_socket_recv(sock, maxlen: maxlen, flags: flags)
|
74
|
+
Fiber.yield [YIELD_IO_OPERATION, receipt]
|
75
|
+
else
|
76
|
+
sock.recv(maxlen, flags)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.recvmsg_from(sock, maxdatalen: nil, flags: 0, maxcontrollen: nil, opts: {})
|
81
|
+
if is_in_corun_fiber?
|
82
|
+
receipt = Scheduler.instance.io_machine.do_socket_recvmsg(sock, maxdatalen: maxdatalen, flags: flags, maxcontrollen: maxcontrollen, opts: opts)
|
83
|
+
Fiber.yield [YIELD_IO_OPERATION, receipt]
|
84
|
+
else
|
85
|
+
sock.recvmsg(maxdatalen, flags, maxcontrollen, opts)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.sendmsg_to(sock, mesg, flags: 0, dest_sockaddr: nil, controls: [])
|
90
|
+
if is_in_corun_fiber?
|
91
|
+
receipt = Scheduler.instance.io_machine.do_socket_sendmsg(sock, mesg, flags: flags, dest_sockaddr: dest_sockaddr, controls: controls)
|
92
|
+
Fiber.yield [YIELD_IO_OPERATION, receipt]
|
93
|
+
else
|
94
|
+
sock.sendmsg(mesg, flags, dest_sockaddr, *controls)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
65
98
|
def self.write_to(io, str:)
|
66
99
|
if is_in_corun_fiber?
|
67
100
|
receipt = Scheduler.instance.io_machine.do_write(io, str: str)
|
@@ -90,7 +123,7 @@ module Rbgo
|
|
90
123
|
end
|
91
124
|
|
92
125
|
def perform
|
93
|
-
self.fiber = Fiber.new do
|
126
|
+
self.fiber = Fiber.new do |*args|
|
94
127
|
Thread.current[IS_CORUN_FIBER] = true
|
95
128
|
blk.call(*args)
|
96
129
|
end if fiber.nil?
|
@@ -169,6 +202,7 @@ module Rbgo
|
|
169
202
|
yield_task_queue = Queue.new
|
170
203
|
pending_io_task_queue = Queue.new
|
171
204
|
local_task_queue = Queue.new
|
205
|
+
Thread.current.thread_variable_set(LOCAL_TASK_QUEUES,[yield_task_queue, pending_io_task_queue, local_task_queue])
|
172
206
|
task_queue = get_queue(queue_tag)
|
173
207
|
local_task_queue << init_task if init_task
|
174
208
|
loop do
|
@@ -338,6 +372,10 @@ module Rbgo
|
|
338
372
|
def go!(*args, &blk)
|
339
373
|
CoRun::Routine.new(*args, new_thread: true, queue_tag: :none, &blk)
|
340
374
|
end
|
375
|
+
|
376
|
+
def have_other_task_on_thread?
|
377
|
+
CoRun.have_other_task_on_thread?
|
378
|
+
end
|
341
379
|
end
|
342
380
|
|
343
381
|
refine IO do
|
@@ -366,6 +404,18 @@ module Rbgo
|
|
366
404
|
def yield_connect(remote_sockaddr)
|
367
405
|
CoRun.connect_to(self, remote_sockaddr: remote_sockaddr)
|
368
406
|
end
|
407
|
+
|
408
|
+
def yield_recv(maxlen, flags = 0)
|
409
|
+
CoRun.recv_from(self, maxlen: maxlen, flags: flags)
|
410
|
+
end
|
411
|
+
|
412
|
+
def yield_recvmsg(maxdatalen = nil, flags = 0, maxcontrollen = nil, opts = {})
|
413
|
+
CoRun.recvmsg_from(self, maxdatalen: maxdatalen, flags: flags, maxcontrollen: maxcontrollen, opts: opts)
|
414
|
+
end
|
415
|
+
|
416
|
+
def yield_sendmsg(mesg, flags = 0, dest_sockaddr = nil, *controls)
|
417
|
+
CoRun.sendmsg_to(self, mesg, flags: flags, dest_sockaddr: dest_sockaddr, controls: controls)
|
418
|
+
end
|
369
419
|
end
|
370
420
|
end
|
371
421
|
end
|
data/lib/rbgo/io_machine.rb
CHANGED
@@ -80,6 +80,27 @@ module Rbgo
|
|
80
80
|
receipt
|
81
81
|
end
|
82
82
|
|
83
|
+
def do_socket_recv(sock, maxlen:, flags: 0)
|
84
|
+
op = [:register_socket_recv, sock, maxlen, flags]
|
85
|
+
receipt = IOReceipt.new(op)
|
86
|
+
actor.send_msg(receipt)
|
87
|
+
receipt
|
88
|
+
end
|
89
|
+
|
90
|
+
def do_socket_recvmsg(sock, maxdatalen: nil, flags: 0, maxcontrollen: nil, opts: {})
|
91
|
+
op = [:register_socket_recvmsg, sock, maxdatalen, flags, maxcontrollen, opts]
|
92
|
+
receipt = IOReceipt.new(op)
|
93
|
+
actor.send_msg(receipt)
|
94
|
+
receipt
|
95
|
+
end
|
96
|
+
|
97
|
+
def do_socket_sendmsg(sock, mesg, flags: 0, dest_sockaddr: nil, controls: [])
|
98
|
+
op = [:register_socket_sendmsg, sock, mesg, flags, dest_sockaddr, controls]
|
99
|
+
receipt = IOReceipt.new(op)
|
100
|
+
actor.send_msg(receipt)
|
101
|
+
receipt
|
102
|
+
end
|
103
|
+
|
83
104
|
def close
|
84
105
|
actor.close
|
85
106
|
selector.close
|
@@ -119,6 +140,12 @@ module Rbgo
|
|
119
140
|
handle_socket_accept_msg(receipt, actor)
|
120
141
|
when :register_socket_connect
|
121
142
|
handle_socket_connect_msg(receipt, actor)
|
143
|
+
when :register_socket_recv
|
144
|
+
handle_socket_recv_msg(receipt, actor)
|
145
|
+
when :register_socket_sendmsg
|
146
|
+
handle_socket_sendmsg_msg(receipt, actor)
|
147
|
+
when :register_socket_recvmsg
|
148
|
+
handle_socket_recvmsg_msg(receipt, actor)
|
122
149
|
end
|
123
150
|
end #end of actor
|
124
151
|
|
@@ -144,8 +171,171 @@ module Rbgo
|
|
144
171
|
nil
|
145
172
|
end
|
146
173
|
|
147
|
-
def
|
174
|
+
def handle_socket_recv_msg(receipt, actor)
|
175
|
+
op = receipt.registered_op
|
176
|
+
io = op[1]
|
177
|
+
maxlen = op[2]
|
178
|
+
flags = op[3]
|
179
|
+
res = ""
|
180
|
+
|
181
|
+
monitor = register(receipt, interest: :r)
|
182
|
+
return if monitor.nil?
|
183
|
+
|
184
|
+
monitor.value ||= []
|
185
|
+
monitor.value[0] = proc do
|
186
|
+
notify_blk = proc do
|
187
|
+
monitors.delete(monitor.io)
|
188
|
+
monitor.close
|
189
|
+
receipt.res = res
|
190
|
+
receipt.notify
|
191
|
+
end
|
192
|
+
catch :exit do
|
193
|
+
begin
|
194
|
+
buf = io.recv_nonblock(maxlen, flags, exception: false)
|
195
|
+
rescue Exception => ex
|
196
|
+
notify_blk.call
|
197
|
+
STDERR.puts ex
|
198
|
+
throw :exit
|
199
|
+
end
|
200
|
+
if buf == :wait_readable
|
201
|
+
throw :exit
|
202
|
+
elsif buf.nil?
|
203
|
+
notify_blk.call
|
204
|
+
throw :exit
|
205
|
+
end
|
206
|
+
res << buf
|
207
|
+
notify_blk.call
|
208
|
+
end
|
209
|
+
end
|
210
|
+
actor.send_msg :do_select
|
211
|
+
end
|
212
|
+
|
213
|
+
def handle_socket_recvmsg_msg(receipt, actor)
|
214
|
+
op = receipt.registered_op
|
215
|
+
sock = op[1]
|
216
|
+
len = op[2]
|
217
|
+
flags = op[3]
|
218
|
+
maxcontrollen = op[4]
|
219
|
+
opts = op[5]
|
220
|
+
|
221
|
+
data = ""
|
222
|
+
sender_addrinfo = nil
|
223
|
+
rflags = 0
|
224
|
+
controls = []
|
225
|
+
|
226
|
+
monitor = register(receipt, interest: :r)
|
227
|
+
return if monitor.nil?
|
228
|
+
notify_blk = proc do
|
229
|
+
monitors.delete(monitor.io)
|
230
|
+
monitor.close
|
231
|
+
if len && len > 0 && data.length == 0
|
232
|
+
receipt.res = nil
|
233
|
+
else
|
234
|
+
receipt.res = [data, sender_addrinfo, rflags, *controls]
|
235
|
+
end
|
236
|
+
receipt.notify
|
237
|
+
end
|
238
|
+
monitor.value ||= []
|
239
|
+
monitor.value[0] = proc do
|
240
|
+
if len.nil?
|
241
|
+
loop do
|
242
|
+
begin
|
243
|
+
buf = sock.recvmsg_nonblock(nil, flags, maxcontrollen, opts.merge(exception: false))
|
244
|
+
rescue Exception => ex
|
245
|
+
notify_blk.call
|
246
|
+
STDERR.puts ex
|
247
|
+
break
|
248
|
+
end
|
249
|
+
if buf == :wait_readable
|
250
|
+
break
|
251
|
+
elsif buf.nil?
|
252
|
+
notify_blk.call
|
253
|
+
break
|
254
|
+
end
|
255
|
+
data << buf[0]
|
256
|
+
sender_addrinfo = buf[1]
|
257
|
+
rflags = buf[2]
|
258
|
+
controls.append(*buf[3..-1])
|
259
|
+
end
|
260
|
+
elsif len == 0
|
261
|
+
notify_blk.call
|
262
|
+
break
|
263
|
+
else
|
264
|
+
bytes_read_n = 0
|
265
|
+
loop do
|
266
|
+
need_read_bytes_n = len - bytes_read_n
|
267
|
+
if need_read_bytes_n <= 0
|
268
|
+
notify_blk.call
|
269
|
+
break
|
270
|
+
end
|
271
|
+
begin
|
272
|
+
buf = sock.recvmsg_nonblock(need_read_bytes_n, flags, maxcontrollen, opts.merge(exception: false))
|
273
|
+
rescue Exception => ex
|
274
|
+
notify_blk.call
|
275
|
+
STDERR.puts ex
|
276
|
+
break
|
277
|
+
end
|
278
|
+
if buf == :wait_readable
|
279
|
+
break
|
280
|
+
elsif buf.nil?
|
281
|
+
notify_blk.call
|
282
|
+
break
|
283
|
+
end
|
284
|
+
data << buf[0]
|
285
|
+
sender_addrinfo = buf[1]
|
286
|
+
rflags = buf[2]
|
287
|
+
controls.append(*buf[3..-1])
|
288
|
+
bytes_read_n += buf[0].bytesize
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
actor.send_msg :do_select
|
293
|
+
end
|
294
|
+
|
295
|
+
def handle_socket_sendmsg_msg(receipt, actor)
|
296
|
+
op = receipt.registered_op
|
297
|
+
sock = op[1]
|
298
|
+
str = op[2].to_s
|
299
|
+
flags = op[3]
|
300
|
+
dest_sockaddr = op[4]
|
301
|
+
controls = op[5]
|
302
|
+
monitor = register(receipt, interest: :w)
|
303
|
+
return if monitor.nil?
|
148
304
|
|
305
|
+
bytes_written_n = 0
|
306
|
+
monitor.value ||= []
|
307
|
+
monitor.value[1] = proc do
|
308
|
+
loop do
|
309
|
+
begin
|
310
|
+
bytes_need_to_write_n = str.size - bytes_written_n
|
311
|
+
if bytes_need_to_write_n > 0
|
312
|
+
n = sock.sendmsg_nonblock(str[bytes_written_n..-1], flags, dest_sockaddr, *controls, exception: false)
|
313
|
+
else
|
314
|
+
monitors.delete(monitor.io)
|
315
|
+
monitor.close
|
316
|
+
receipt.res = str.bytesize
|
317
|
+
receipt.notify
|
318
|
+
break
|
319
|
+
end
|
320
|
+
rescue Exception => ex
|
321
|
+
monitors.delete(monitor.io)
|
322
|
+
monitor.close
|
323
|
+
receipt.res = bytes_written_n
|
324
|
+
receipt.notify
|
325
|
+
STDERR.puts ex
|
326
|
+
break
|
327
|
+
end
|
328
|
+
if n == :wait_writable
|
329
|
+
break
|
330
|
+
end
|
331
|
+
bytes_written_n += n
|
332
|
+
end
|
333
|
+
end
|
334
|
+
monitor.value[1].call
|
335
|
+
actor.send_msg :do_select
|
336
|
+
end
|
337
|
+
|
338
|
+
def handle_socket_connect_msg(receipt, actor)
|
149
339
|
op = receipt.registered_op
|
150
340
|
sock = op[1]
|
151
341
|
remote_sockaddr = op[2]
|
@@ -278,10 +468,17 @@ module Rbgo
|
|
278
468
|
sep = "\n\n" if (sep && sep.length == 0)
|
279
469
|
|
280
470
|
if limit.nil?
|
281
|
-
buf_size = 1 unless sep.nil?
|
471
|
+
buf_size = 1 unless sep.nil? || io.is_a?(BasicSocket)
|
282
472
|
loop do
|
283
473
|
begin
|
284
|
-
|
474
|
+
if sep && io.is_a?(BasicSocket)
|
475
|
+
buf = io.recv_nonblock(buf_size, Socket::MSG_PEEK, exception: false)
|
476
|
+
if buf != :wait_readable && buf.size == 0
|
477
|
+
buf = nil
|
478
|
+
end
|
479
|
+
else
|
480
|
+
buf = io.read_nonblock(buf_size, exception: false)
|
481
|
+
end
|
285
482
|
rescue Exception => ex
|
286
483
|
notify_blk.call
|
287
484
|
STDERR.puts ex
|
@@ -293,7 +490,16 @@ module Rbgo
|
|
293
490
|
notify_blk.call
|
294
491
|
break
|
295
492
|
end
|
296
|
-
|
493
|
+
if sep && io.is_a?(BasicSocket)
|
494
|
+
sep_index = buf.index(sep)
|
495
|
+
if sep_index
|
496
|
+
buf = buf[0...sep_index + sep.length]
|
497
|
+
end
|
498
|
+
res << buf
|
499
|
+
io.recv(buf.size)
|
500
|
+
else
|
501
|
+
res << buf
|
502
|
+
end
|
297
503
|
unless sep.nil?
|
298
504
|
if res.end_with?(sep)
|
299
505
|
notify_blk.call
|
@@ -309,13 +515,20 @@ module Rbgo
|
|
309
515
|
notify_blk.call
|
310
516
|
break
|
311
517
|
end
|
312
|
-
if sep.nil?
|
518
|
+
if sep.nil? || io.is_a?(BasicSocket)
|
313
519
|
buf_size = need_read_bytes_n
|
314
520
|
else
|
315
521
|
buf_size = 1
|
316
522
|
end
|
317
523
|
begin
|
318
|
-
|
524
|
+
if sep && io.is_a?(BasicSocket)
|
525
|
+
buf = io.recv_nonblock(buf_size, Socket::MSG_PEEK, exception: false)
|
526
|
+
if buf != :wait_readable && buf.size == 0
|
527
|
+
buf = nil
|
528
|
+
end
|
529
|
+
else
|
530
|
+
buf = io.read_nonblock(buf_size, exception: false)
|
531
|
+
end
|
319
532
|
rescue Exception => ex
|
320
533
|
notify_blk.call
|
321
534
|
STDERR.puts ex
|
@@ -327,7 +540,16 @@ module Rbgo
|
|
327
540
|
notify_blk.call
|
328
541
|
break
|
329
542
|
end
|
330
|
-
|
543
|
+
if sep && io.is_a?(BasicSocket)
|
544
|
+
sep_index = buf.index(sep)
|
545
|
+
if sep_index
|
546
|
+
buf = buf[0...sep_index + sep.length]
|
547
|
+
end
|
548
|
+
res << buf
|
549
|
+
io.recv(buf.size)
|
550
|
+
else
|
551
|
+
res << buf
|
552
|
+
end
|
331
553
|
bytes_read_n += buf.bytesize
|
332
554
|
unless sep.nil?
|
333
555
|
if res.end_with?(sep)
|
@@ -343,7 +565,6 @@ module Rbgo
|
|
343
565
|
actor.send_msg :do_select
|
344
566
|
end
|
345
567
|
|
346
|
-
|
347
568
|
def handle_read_msg(receipt, actor)
|
348
569
|
op = receipt.registered_op
|
349
570
|
io = op[1]
|
@@ -414,7 +635,6 @@ module Rbgo
|
|
414
635
|
actor.send_msg :do_select
|
415
636
|
end
|
416
637
|
|
417
|
-
|
418
638
|
def handle_write_msg(receipt, actor)
|
419
639
|
op = receipt.registered_op
|
420
640
|
io = op[1]
|
@@ -451,7 +671,6 @@ module Rbgo
|
|
451
671
|
actor.send_msg :do_select
|
452
672
|
end
|
453
673
|
|
454
|
-
|
455
674
|
def register(receipt, interest:)
|
456
675
|
io = receipt.registered_op[1]
|
457
676
|
registered_monitor = monitors[io]
|
@@ -469,6 +688,5 @@ module Rbgo
|
|
469
688
|
end
|
470
689
|
monitor
|
471
690
|
end
|
472
|
-
|
473
691
|
end
|
474
692
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# Copyright (c) 2014 Boris Bera
|
2
|
+
#
|
3
|
+
# MIT License
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
# a copy of this software and associated documentation files (the
|
7
|
+
# "Software"), to deal in the Software without restriction, including
|
8
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
# the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be
|
14
|
+
# included in all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
module Rbgo
|
25
|
+
class ReentrantMutex < Mutex
|
26
|
+
def initialize
|
27
|
+
@count_mutex = Mutex.new
|
28
|
+
@counts = Hash.new(0)
|
29
|
+
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
def synchronize
|
34
|
+
raise ThreadError, 'Must be called with a block' unless block_given?
|
35
|
+
|
36
|
+
begin
|
37
|
+
lock
|
38
|
+
yield
|
39
|
+
ensure
|
40
|
+
unlock
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def lock
|
45
|
+
c = increase_count Thread.current
|
46
|
+
super if c <= 1
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def unlock
|
51
|
+
c = decrease_count Thread.current
|
52
|
+
if c <= 0
|
53
|
+
super
|
54
|
+
delete_count Thread.current
|
55
|
+
end
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def try_lock
|
60
|
+
if owned?
|
61
|
+
lock
|
62
|
+
return true
|
63
|
+
else
|
64
|
+
ok = super
|
65
|
+
increase_count Thread.current if ok
|
66
|
+
return ok
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def increase_count(thread)
|
73
|
+
@count_mutex.synchronize { @counts[thread] += 1 }
|
74
|
+
end
|
75
|
+
|
76
|
+
def decrease_count(thread)
|
77
|
+
@count_mutex.synchronize { @counts[thread] -= 1 }
|
78
|
+
end
|
79
|
+
|
80
|
+
def delete_count(thread)
|
81
|
+
@count_mutex.synchronize { @counts.delete(thread) }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'reentrant_mutex'
|
4
|
+
module Rbgo
|
5
|
+
class RWMutex
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@lock_count = Hash.new(0)
|
9
|
+
@mutex = ReentrantMutex.new
|
10
|
+
@cond = ConditionVariable.new
|
11
|
+
@status = :unlocked
|
12
|
+
end
|
13
|
+
|
14
|
+
def owned?
|
15
|
+
@mutex.synchronize do
|
16
|
+
@lock_count.key? Thread.current
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def locked?
|
21
|
+
@mutex.synchronize do
|
22
|
+
if @lock_count.empty?
|
23
|
+
return false
|
24
|
+
else
|
25
|
+
return true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def synchronize_r
|
31
|
+
raise ThreadError, 'Must be called with a block' unless block_given?
|
32
|
+
|
33
|
+
begin
|
34
|
+
lock_r
|
35
|
+
yield
|
36
|
+
ensure
|
37
|
+
unlock
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def synchronize_w
|
42
|
+
raise ThreadError, 'Must be called with a block' unless block_given?
|
43
|
+
|
44
|
+
begin
|
45
|
+
lock_w
|
46
|
+
yield
|
47
|
+
ensure
|
48
|
+
unlock
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def lock_r
|
55
|
+
@mutex.synchronize do
|
56
|
+
case @status
|
57
|
+
when :locked_for_write
|
58
|
+
until @status == :unlocked || @status == :locked_for_read || owned?
|
59
|
+
@cond.wait(@mutex)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
tmp_status = @status
|
63
|
+
@status = :locked_for_read
|
64
|
+
@lock_count[Thread.current] += 1
|
65
|
+
if owned? && tmp_status == :locked_for_write
|
66
|
+
@cond.broadcast
|
67
|
+
end
|
68
|
+
return self
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def lock_w
|
73
|
+
@mutex.synchronize do
|
74
|
+
until @status == :unlocked || (owned? && @status == :locked_for_write)
|
75
|
+
@cond.wait(@mutex)
|
76
|
+
end
|
77
|
+
@status = :locked_for_write
|
78
|
+
@lock_count[Thread.current] += 1
|
79
|
+
return self
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def unlock
|
84
|
+
@mutex.synchronize do
|
85
|
+
if owned?
|
86
|
+
@lock_count[Thread.current] -= 1
|
87
|
+
@lock_count.delete(Thread.current) if @lock_count[Thread.current] == 0
|
88
|
+
if @lock_count.empty?
|
89
|
+
@status = :unlocked
|
90
|
+
@cond.broadcast
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Rbgo
|
2
|
+
class Semaphore
|
3
|
+
def initialize(count)
|
4
|
+
count = count.to_i
|
5
|
+
raise 'count must be positive' if count <= 0
|
6
|
+
@mutex = Mutex.new
|
7
|
+
@cond = ConditionVariable.new
|
8
|
+
@count = count
|
9
|
+
end
|
10
|
+
|
11
|
+
def available_permits
|
12
|
+
@mutex.synchronize do
|
13
|
+
return @count
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def acquire(permits = 1)
|
18
|
+
raise 'Must be called with a block' unless block_given?
|
19
|
+
|
20
|
+
begin
|
21
|
+
ok = _acquire(permits)
|
22
|
+
yield
|
23
|
+
ensure
|
24
|
+
release(permits) if ok
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def acquire_all
|
29
|
+
raise 'Must be called with a block' unless block_given?
|
30
|
+
|
31
|
+
begin
|
32
|
+
permits = drain_permits
|
33
|
+
yield
|
34
|
+
ensure
|
35
|
+
release(permits)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def try_acquire(permits = 1)
|
40
|
+
raise 'Must be called with a block' unless block_given?
|
41
|
+
|
42
|
+
begin
|
43
|
+
ok = _try_acquire(permits)
|
44
|
+
yield if ok
|
45
|
+
ensure
|
46
|
+
release(permits) if ok
|
47
|
+
end
|
48
|
+
return ok
|
49
|
+
end
|
50
|
+
|
51
|
+
def release(permits = 1)
|
52
|
+
permits = permits.to_i
|
53
|
+
raise 'permits must be positive' if permits <= 0
|
54
|
+
@mutex.synchronize do
|
55
|
+
@count += permits
|
56
|
+
@cond.broadcast
|
57
|
+
end
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def _acquire(permits = 1)
|
64
|
+
permits = permits.to_i
|
65
|
+
raise 'permits must be positive' if permits <= 0
|
66
|
+
@mutex.synchronize do
|
67
|
+
while @count - permits < 0
|
68
|
+
@cond.wait(@mutex)
|
69
|
+
end
|
70
|
+
@count -= permits
|
71
|
+
return self
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def drain_permits
|
76
|
+
@mutex.synchronize do
|
77
|
+
while @count <= 0
|
78
|
+
@cond.wait(@mutex)
|
79
|
+
end
|
80
|
+
tmp_count = @count
|
81
|
+
@count = 0
|
82
|
+
return tmp_count
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def _try_acquire(permits = 1)
|
87
|
+
permits = permits.to_i
|
88
|
+
raise 'permits must be positive' if permits <= 0
|
89
|
+
@mutex.synchronize do
|
90
|
+
if @count - permits < 0
|
91
|
+
return false
|
92
|
+
else
|
93
|
+
@count -= permits
|
94
|
+
return true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'monitor'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Rbgo
|
5
|
+
class SyncArray
|
6
|
+
def self.[](*args)
|
7
|
+
SyncArray.new(Array.[](*args))
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.try_convert(obj)
|
11
|
+
a = Array.try_convert(obj)
|
12
|
+
a.nil? ? nil : SyncArray.new(a)
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(*args, &blk)
|
16
|
+
@a = Array.new(*args, &blk)
|
17
|
+
@a.extend(MonitorMixin)
|
18
|
+
end
|
19
|
+
|
20
|
+
Array.public_instance_methods.each do |m|
|
21
|
+
define_method(m) do |*args, &blk|
|
22
|
+
@a.synchronize do
|
23
|
+
@a.send m, *args, &blk
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class SyncHash
|
30
|
+
def self.[](*args)
|
31
|
+
h = SyncHash.new
|
32
|
+
h.instance_eval do
|
33
|
+
@h = Hash.[](*args)
|
34
|
+
@h.extend(MonitorMixin)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.try_convert(obj)
|
39
|
+
h = Hash.try_convert(obj)
|
40
|
+
h.nil? ? nil : SyncHash.[](h)
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(*args, &blk)
|
44
|
+
@h = Hash.new(*args, &blk)
|
45
|
+
@h.extend(MonitorMixin)
|
46
|
+
end
|
47
|
+
|
48
|
+
Hash.public_instance_methods.each do |m|
|
49
|
+
define_method(m) do |*args, &blk|
|
50
|
+
@h.synchronize do
|
51
|
+
@h.send m, *args, &blk
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class SyncSet
|
58
|
+
def self.[](*args)
|
59
|
+
SyncSet.new(Set.[](*args))
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize(enum = nil)
|
63
|
+
@s = Set.new(enum)
|
64
|
+
@s.extend(MonitorMixin)
|
65
|
+
end
|
66
|
+
|
67
|
+
Set.public_instance_methods.each do |m|
|
68
|
+
define_method(m) do |*args, &blk|
|
69
|
+
@s.synchronize do
|
70
|
+
@s.send m, *args, &blk
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
module Rbgo
|
5
|
+
using CoRunExtensions
|
6
|
+
|
7
|
+
class TaskList
|
8
|
+
attr_accessor :last_error
|
9
|
+
|
10
|
+
def <<(task)
|
11
|
+
task_queue << task
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(task, timeout: nil, skip_on_exception: false)
|
16
|
+
task_queue << proc do |last_task_result|
|
17
|
+
begin
|
18
|
+
Timeout::timeout(timeout) do
|
19
|
+
task.call(last_task_result)
|
20
|
+
end
|
21
|
+
rescue Exception => ex
|
22
|
+
self.last_error = ex
|
23
|
+
raise unless skip_on_exception
|
24
|
+
end
|
25
|
+
end
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def start(arg = nil)
|
30
|
+
start_once.do do
|
31
|
+
_start(arg)
|
32
|
+
end
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def clear_task
|
37
|
+
task_queue.clear
|
38
|
+
end
|
39
|
+
|
40
|
+
def running?
|
41
|
+
running
|
42
|
+
end
|
43
|
+
|
44
|
+
def complete?
|
45
|
+
!running? && task_queue.empty?
|
46
|
+
end
|
47
|
+
|
48
|
+
def wakeup
|
49
|
+
wait_cond.signal
|
50
|
+
end
|
51
|
+
|
52
|
+
def wait(timeout = nil)
|
53
|
+
wait_mutex.synchronize do
|
54
|
+
if running?
|
55
|
+
wait_cond.wait(wait_mutex, timeout)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
attr_accessor :task_queue, :start_once, :running, :wait_mutex, :wait_cond
|
63
|
+
|
64
|
+
def initialize
|
65
|
+
self.task_queue = Queue.new
|
66
|
+
self.start_once = Once.new
|
67
|
+
self.running = false
|
68
|
+
self.wait_mutex = Mutex.new
|
69
|
+
self.wait_cond = ConditionVariable.new
|
70
|
+
end
|
71
|
+
|
72
|
+
def notify
|
73
|
+
wait_mutex.synchronize do
|
74
|
+
self.running = false
|
75
|
+
wait_cond.signal
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def _start(arg = nil)
|
80
|
+
self.last_error = nil unless running?
|
81
|
+
self.running = true
|
82
|
+
go(arg) do |last_task_result|
|
83
|
+
begin
|
84
|
+
task = task_queue.deq(true)
|
85
|
+
rescue ThreadError
|
86
|
+
notify
|
87
|
+
self.start_once = Once.new
|
88
|
+
else
|
89
|
+
begin
|
90
|
+
res = task.call(last_task_result)
|
91
|
+
rescue Exception => ex
|
92
|
+
self.last_error = ex
|
93
|
+
notify
|
94
|
+
self.start_once = Once.new
|
95
|
+
else
|
96
|
+
_start(res)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/rbgo/version.rb
CHANGED
data/lib/rbgo/wait_group.rb
CHANGED
@@ -3,12 +3,13 @@ require 'thread'
|
|
3
3
|
module Rbgo
|
4
4
|
class WaitGroup
|
5
5
|
def initialize(init_count = 0)
|
6
|
-
self.total_count = [0, init_count].max
|
6
|
+
self.total_count = [0, init_count.to_i].max
|
7
7
|
self.mutex = Mutex.new
|
8
8
|
self.cond = ConditionVariable.new
|
9
9
|
end
|
10
10
|
|
11
11
|
def add(count)
|
12
|
+
count = count.to_i
|
12
13
|
mutex.synchronize do
|
13
14
|
c = total_count + count
|
14
15
|
if c < 0
|
data/lib/rbgo.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
require_relative 'rbgo/corun'
|
2
2
|
require_relative 'rbgo/select_chan'
|
3
3
|
require_relative 'rbgo/wait_group'
|
4
|
+
require_relative 'rbgo/reentrant_mutex'
|
5
|
+
require_relative 'rbgo/reentrant_rw_mutex'
|
6
|
+
require_relative 'rbgo/semaphore'
|
7
|
+
require_relative 'rbgo/synchronized_collection'
|
4
8
|
require_relative 'rbgo/once'
|
5
9
|
require_relative 'rbgo/actor'
|
10
|
+
require_relative 'rbgo/task_list'
|
6
11
|
require_relative 'rbgo/io_machine'
|
7
12
|
require_relative 'rbgo/network_service'
|
8
13
|
require_relative 'rbgo/version'
|
data/rbgo.gemspec
CHANGED
@@ -10,15 +10,7 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.email = ["24588062@qq.com"]
|
11
11
|
s.homepage = "https://github.com/wangyin-git/rbgo"
|
12
12
|
s.summary = "Write concurrent program with Ruby in Golang style"
|
13
|
-
s.description =
|
14
|
-
Write concurrent program with Ruby in Golang style.
|
15
|
-
|
16
|
-
You can produce a light weight routine easily with a method 'go' and communicate with each routine by channel.
|
17
|
-
|
18
|
-
In MRI the GIL prevents you from running code parallelly, but there ara also other ruby implementations such as TruffleRuby or JRuby which can utilize all CPU cores.
|
19
|
-
|
20
|
-
In MRI write program to run concurrently even not parallelly is also important.
|
21
|
-
END
|
13
|
+
s.description = "Write concurrent program with Ruby in Golang style."
|
22
14
|
s.add_dependency "system", "~> 0.1.3"
|
23
15
|
s.add_dependency "nio4r", "~> 2.4"
|
24
16
|
s.files = `git ls-files`.split("\n")
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rbgo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wang Yin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-09-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: system
|
@@ -38,13 +38,7 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '2.4'
|
41
|
-
description:
|
42
|
-
\ \n\n You can produce a light weight routine easily with a method
|
43
|
-
'go' and communicate with each routine by channel.\n\n In MRI
|
44
|
-
the GIL prevents you from running code parallelly, but there ara also other ruby
|
45
|
-
implementations such as TruffleRuby or JRuby which can utilize all CPU cores.\n\n
|
46
|
-
\ In MRI write program to run concurrently even not parallelly
|
47
|
-
is also important.\n"
|
41
|
+
description: Write concurrent program with Ruby in Golang style.
|
48
42
|
email:
|
49
43
|
- 24588062@qq.com
|
50
44
|
executables: []
|
@@ -61,7 +55,12 @@ files:
|
|
61
55
|
- lib/rbgo/io_machine.rb
|
62
56
|
- lib/rbgo/network_service.rb
|
63
57
|
- lib/rbgo/once.rb
|
58
|
+
- lib/rbgo/reentrant_mutex.rb
|
59
|
+
- lib/rbgo/reentrant_rw_mutex.rb
|
64
60
|
- lib/rbgo/select_chan.rb
|
61
|
+
- lib/rbgo/semaphore.rb
|
62
|
+
- lib/rbgo/synchronized_collection.rb
|
63
|
+
- lib/rbgo/task_list.rb
|
65
64
|
- lib/rbgo/version.rb
|
66
65
|
- lib/rbgo/wait_group.rb
|
67
66
|
- rbgo.gemspec
|