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 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