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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4811ffce7c609abea9507fc023c9383121d232d6b79cb221e1ac62980ea879a0
4
- data.tar.gz: 297e658752df154a7e5202cd2d6801c5b4aef3399229710f1b37bc0e6e025868
3
+ metadata.gz: c619996e5259a7e326d0a4740643b440895edf64534c7211df3acb912842b807
4
+ data.tar.gz: f6fc47754f814abd62bd0c148d867e14a4f7f594596ae6a2bbdb942defc219ca
5
5
  SHA512:
6
- metadata.gz: 70ba89e8bb29f007a40e51d715306da0cec8ebb9c71331e2f60d08e46005ca12d44eb5e18dace1b531f758b226abe40e1c31f7101d78c95a3864db2410d30c40
7
- data.tar.gz: 8ffd9db409f9714c7d160d994a19f38d0ffbacd0fb115a77ade48574ba326f73a40b8389a105703386fa524676cfa5b318acc663c37e72d71510f421317f9060
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
- attr_accessor :mail_box
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 = handler
15
- self.mail_box = Queue.new
16
- start_msg_loop
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
- CoRun::Routine.new(new_thread: true, queue_tag: :default) do
38
- loop do
39
- break if mail_box.closed?
40
- msg = mail_box.deq
41
- handler.call(msg, self) rescue nil
42
- Fiber.yield
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 |args|
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
@@ -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 handle_socket_connect_msg(receipt, actor)
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
- buf = io.read_nonblock(buf_size, exception: false)
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
- res << buf
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
- buf = io.read_nonblock(buf_size, exception: false)
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
- res << buf
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
@@ -1,3 +1,3 @@
1
1
  module Rbgo
2
- VERSION = "0.2.9"
2
+ VERSION = "0.2.15"
3
3
  end
@@ -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 = <<-END
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.9
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-08-14 00:00:00.000000000 Z
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: " Write concurrent program with Ruby in Golang style.
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