rbgo 0.2.9 → 0.2.15
Sign up to get free protection for your applications and to get access to all the features.
- 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
|