asynchronous 4.0.0.pre → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +3 -1
- data/Gemfile.lock +19 -1
- data/README.md +19 -104
- data/VERSION +1 -1
- data/examples/basic.rb +11 -0
- data/examples/bootstrap.rb +1 -1
- data/lib/asynchronous.rb +3 -3
- data/lib/asynchronous/core_ext.rb +13 -0
- data/lib/asynchronous/thread.rb +87 -0
- data/lib/asynchronous/utils.rb +10 -0
- data/lib/asynchronous/zombi_killer.rb +20 -0
- data/spec/asynchronous/thread_spec.rb +152 -0
- data/spec/spec_helper.rb +34 -0
- data/t.rb +10 -0
- metadata +16 -13
- data/examples/async_patterns.rb +0 -78
- data/examples/no_zombie_test.rb +0 -29
- data/examples/parallelism.rb +0 -35
- data/lib/async.rb +0 -1
- data/lib/asynchronous/concurrency.rb +0 -44
- data/lib/asynchronous/kernel.rb +0 -42
- data/lib/asynchronous/parallelism.rb +0 -135
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b45ba44d151028eeee66d2cd93f6dccf1da66940
|
4
|
+
data.tar.gz: 50e3542ab8a04221d7f3cef1cad1afd400fd2adb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8698c24aaf589aa730a168b15ec1803d9c4575325470a8799d31e2f2fdfc6efb07918a1d3e2d1d39317ceca7827c63a18df52407954df812ed7c71dff29f8ff0
|
7
|
+
data.tar.gz: 81e768d82ad49ff3333ff7ba4b3b2f837c701077fc4a367d9d76adb34245feac5ee6d407b25970c2eafdb23250da2a1f35058b43f66a40a0ae6d6b1cca7acf9c
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,14 +1,32 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
asynchronous (4.0.0
|
4
|
+
asynchronous (4.0.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: http://rubygems.org/
|
8
8
|
specs:
|
9
|
+
diff-lcs (1.3)
|
10
|
+
rspec (3.7.0)
|
11
|
+
rspec-core (~> 3.7.0)
|
12
|
+
rspec-expectations (~> 3.7.0)
|
13
|
+
rspec-mocks (~> 3.7.0)
|
14
|
+
rspec-core (3.7.1)
|
15
|
+
rspec-support (~> 3.7.0)
|
16
|
+
rspec-expectations (3.7.0)
|
17
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
18
|
+
rspec-support (~> 3.7.0)
|
19
|
+
rspec-mocks (3.7.0)
|
20
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
21
|
+
rspec-support (~> 3.7.0)
|
22
|
+
rspec-support (3.7.0)
|
9
23
|
|
10
24
|
PLATFORMS
|
11
25
|
ruby
|
12
26
|
|
13
27
|
DEPENDENCIES
|
14
28
|
asynchronous!
|
29
|
+
rspec
|
30
|
+
|
31
|
+
BUNDLED WITH
|
32
|
+
1.16.0
|
data/README.md
CHANGED
@@ -2,19 +2,32 @@ Asynchronous
|
|
2
2
|
============
|
3
3
|
|
4
4
|
Asynchronous Patterns for Ruby Based on Pure MRI CRuby code
|
5
|
-
The goal is to use the original MRI C libs for
|
6
|
-
real async
|
5
|
+
The goal is to use the original MRI C libs for achieve
|
6
|
+
real async processing in ruby
|
7
7
|
|
8
|
-
|
8
|
+
This is good for where cpu focused for included,
|
9
|
+
for example csv transformation
|
10
|
+
|
11
|
+
### Example
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
require 'asynchronous/core_ext'
|
15
|
+
|
16
|
+
thr = async do
|
17
|
+
"some expensive work"
|
18
|
+
end
|
19
|
+
|
20
|
+
thr.value #> "some expensive work"
|
21
|
+
```
|
9
22
|
|
10
23
|
|
11
24
|
### Quoting Sun's Multithreaded Programming Guide:
|
12
25
|
|
13
|
-
Parallelism:
|
26
|
+
Parallelism:
|
14
27
|
- A condition that arises when at least two threads are executing simultaneously.
|
15
28
|
|
16
|
-
Concurrency:
|
17
|
-
- A condition that exists when at least two threads are making progress.
|
29
|
+
Concurrency:
|
30
|
+
- A condition that exists when at least two threads are making progress.
|
18
31
|
- A more generalized form of parallelism that can include time-slicing as a form of virtual parallelism
|
19
32
|
|
20
33
|
#### for short:
|
@@ -25,101 +38,3 @@ Eg. multitasking on a single-core machine.
|
|
25
38
|
|
26
39
|
Parallelism is when tasks literally run at the same time.
|
27
40
|
Eg. on a multicore processor.
|
28
|
-
|
29
|
-
|
30
|
-
### OS managed thread (Native Threads)
|
31
|
-
|
32
|
-
copy on write memory share,
|
33
|
-
so you cant change anything in the mother process
|
34
|
-
only with the return value later.
|
35
|
-
This method is stronger the more CPU core the os have
|
36
|
-
|
37
|
-
Ideal for Big jobs that require lot of memory allocation or
|
38
|
-
heavy CPU load to get a value
|
39
|
-
Like parsing (in some case)
|
40
|
-
|
41
|
-
```ruby
|
42
|
-
|
43
|
-
thr = async :parallelism do
|
44
|
-
|
45
|
-
sleep 4
|
46
|
-
# remember, everything you
|
47
|
-
# write here only mather in this block
|
48
|
-
# will not affect the real process only
|
49
|
-
# the last return value of the block
|
50
|
-
4 * 5
|
51
|
-
|
52
|
-
end
|
53
|
-
|
54
|
-
# to call the value:
|
55
|
-
thr.value
|
56
|
-
|
57
|
-
```
|
58
|
-
|
59
|
-
## VM managed thread (Green Threads)
|
60
|
-
|
61
|
-
you can use simple :c also instead of :concurrency as sym,
|
62
|
-
remember :concurrency is all about GIL case, so
|
63
|
-
you can modify the objects in memory
|
64
|
-
This is ideal for little operations in simultaneously or
|
65
|
-
when you need to update objects in the memory and not the
|
66
|
-
return value what is mather.
|
67
|
-
Remember well that GarbageCollector will affect the speed.
|
68
|
-
|
69
|
-
```ruby
|
70
|
-
|
71
|
-
thr = async { sleep 3; 4 * 3 }
|
72
|
-
|
73
|
-
# to call the value:
|
74
|
-
calculation = thr.value
|
75
|
-
|
76
|
-
```
|
77
|
-
|
78
|
-
### Example
|
79
|
-
|
80
|
-
you can use simple :p or :parallelism as nametag
|
81
|
-
remember :parallelism is all about real OS thread case,
|
82
|
-
so you CANT modify the objects in the memory,
|
83
|
-
the normal variables will only be copy on write modify
|
84
|
-
|
85
|
-
This is ideal for big operations where you need do a big process
|
86
|
-
w/o the fear of the Garbage collector slowness or the GIL lock
|
87
|
-
when you need to update objects in the memory use SharedMemory
|
88
|
-
|
89
|
-
Remember! if the hardware only got 1 cpu, it will be like a harder
|
90
|
-
to use concurrency with an expensive memory allocation
|
91
|
-
|
92
|
-
```ruby
|
93
|
-
|
94
|
-
thr = async :OS do
|
95
|
-
|
96
|
-
sleep 4
|
97
|
-
4 * 5
|
98
|
-
|
99
|
-
end
|
100
|
-
|
101
|
-
calculation = thr.value
|
102
|
-
calculation += 1
|
103
|
-
|
104
|
-
puts calculation
|
105
|
-
|
106
|
-
```
|
107
|
-
|
108
|
-
|
109
|
-
there are other examples that you can check in the exampels folder
|
110
|
-
|
111
|
-
### known bugs
|
112
|
-
|
113
|
-
In rare case when you get object_buffer error
|
114
|
-
* use .join method on the async variable
|
115
|
-
|
116
|
-
```ruby
|
117
|
-
|
118
|
-
thr = async :OS do
|
119
|
-
sleep 4
|
120
|
-
4 * 5
|
121
|
-
end
|
122
|
-
|
123
|
-
thr.join #> thr
|
124
|
-
|
125
|
-
```
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
4.0.0
|
1
|
+
4.0.0
|
data/examples/basic.rb
ADDED
data/examples/bootstrap.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__),'..','lib'))
|
2
|
-
require 'asynchronous'
|
2
|
+
require 'asynchronous/core_ext'
|
data/lib/asynchronous.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#encoding: UTF-8
|
2
2
|
module Asynchronous
|
3
|
+
require 'asynchronous/utils'
|
3
4
|
require 'asynchronous/error'
|
4
|
-
require 'asynchronous/
|
5
|
-
require 'asynchronous/
|
6
|
-
require 'asynchronous/kernel'
|
5
|
+
require 'asynchronous/thread'
|
6
|
+
require 'asynchronous/zombi_killer'
|
7
7
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'asynchronous'
|
2
|
+
#
|
3
|
+
# kernel method for asyncron calls
|
4
|
+
# basic version is :
|
5
|
+
#
|
6
|
+
# thr = async { "ruby Code here" }
|
7
|
+
# thr.value #> "ruby Code here"
|
8
|
+
#
|
9
|
+
Kernel.class_eval do
|
10
|
+
def async(&block)
|
11
|
+
::Asynchronous::Thread.new(&block)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
class Asynchronous::Thread
|
2
|
+
def initialize(&callable)
|
3
|
+
@value = nil
|
4
|
+
@value_received = false
|
5
|
+
@read, @write = ::IO.pipe
|
6
|
+
read_buffer
|
7
|
+
|
8
|
+
@pid = fork(&callable)
|
9
|
+
end
|
10
|
+
|
11
|
+
def join
|
12
|
+
wait
|
13
|
+
receive_value! unless @value_received
|
14
|
+
raise(@value.wrapped_error) if @value.is_a?(::Asynchronous::Error)
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def kill
|
19
|
+
::Process.kill('KILL', @pid)
|
20
|
+
|
21
|
+
wait
|
22
|
+
rescue Errno::ESRCH
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def status
|
27
|
+
alive? ? 'run' : false
|
28
|
+
end
|
29
|
+
|
30
|
+
def value
|
31
|
+
join; @value
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def alive?
|
37
|
+
::Asynchronous::Utils.alive?(@pid)
|
38
|
+
end
|
39
|
+
|
40
|
+
def receive_value!
|
41
|
+
@write.close
|
42
|
+
read_buffer.join
|
43
|
+
@read.close
|
44
|
+
@value_received = true
|
45
|
+
end
|
46
|
+
|
47
|
+
def wait(flag = 0)
|
48
|
+
::Process.wait(@pid, flag)
|
49
|
+
rescue Errno::ECHILD
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def fork(&callable)
|
54
|
+
::Kernel.fork { main(&callable) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def read_buffer
|
58
|
+
@read_buffer ||= ::Thread.new do
|
59
|
+
@value = Marshal.load(@read)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def main
|
64
|
+
::Asynchronous::ZombiKiller.antidote
|
65
|
+
|
66
|
+
@read.close
|
67
|
+
|
68
|
+
result = begin
|
69
|
+
yield
|
70
|
+
rescue ::Exception => ex
|
71
|
+
::Asynchronous::Error.new(ex)
|
72
|
+
end
|
73
|
+
|
74
|
+
write_result!(result)
|
75
|
+
end
|
76
|
+
|
77
|
+
def write_result!(result)
|
78
|
+
return unless Asynchronous::ZombiKiller.how_is_mom?
|
79
|
+
::Marshal.dump(result, @write)
|
80
|
+
@write.flush
|
81
|
+
@write.close
|
82
|
+
rescue Errno::EPIPE
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Marshal.load(File.binread(FNAME))
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Asynchronous::ZombiKiller
|
2
|
+
MOTHER_PID ||= $$
|
3
|
+
|
4
|
+
def how_is_mom?
|
5
|
+
Asynchronous::Utils.alive?(MOTHER_PID)
|
6
|
+
end
|
7
|
+
|
8
|
+
def antidote
|
9
|
+
Thread.main[:ZombiKiller] ||= ::Thread.new do
|
10
|
+
loop do
|
11
|
+
::Kernel.exit unless how_is_mom?
|
12
|
+
|
13
|
+
::Kernel.sleep(1)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module_function :antidote, :how_is_mom?
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Asynchronous::Thread do
|
4
|
+
subject(:thread) { described_class.new(&block) }
|
5
|
+
|
6
|
+
describe '#join' do
|
7
|
+
subject(:joining) { thread.join }
|
8
|
+
let(:block) { proc { sleep(1.5) } }
|
9
|
+
|
10
|
+
it { is_expected.to be thread }
|
11
|
+
it { expect { 3.times { thread.join } }.to_not raise_error }
|
12
|
+
it { expect { Timeout.timeout(1) { joining } }.to raise_error(Timeout::Error) }
|
13
|
+
it { expect { joining; Process.kill(0, thread.instance_variable_get(:@pid)) }.to raise_error(Errno::ESRCH) }
|
14
|
+
|
15
|
+
context 'when exception raised in the thread' do
|
16
|
+
let(:block) { proc { raise 'boom' } }
|
17
|
+
|
18
|
+
it { expect { joining }.to raise_error 'boom' }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#value' do
|
23
|
+
subject(:value) { thread.value }
|
24
|
+
|
25
|
+
[
|
26
|
+
true,
|
27
|
+
false,
|
28
|
+
|
29
|
+
42,
|
30
|
+
-42,
|
31
|
+
(2**30),
|
32
|
+
(2**30) - 1,
|
33
|
+
|
34
|
+
42.13,
|
35
|
+
-42.13,
|
36
|
+
(2**30) + 0.1,
|
37
|
+
(2**30) + 0.1 - 1,
|
38
|
+
|
39
|
+
[1, 2, 3],
|
40
|
+
|
41
|
+
{ :a => :b },
|
42
|
+
|
43
|
+
'hello world!',
|
44
|
+
:hello_world,
|
45
|
+
:"hello world!",
|
46
|
+
|
47
|
+
StandardError.new('Boom!')
|
48
|
+
].each do |expected_value|
|
49
|
+
context "when the return value is a #{expected_value.class}" do
|
50
|
+
let(:block) { proc { expected_value } }
|
51
|
+
|
52
|
+
it { is_expected.to eq expected_value }
|
53
|
+
it { is_expected.to be_a expected_value.class }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "when it's a custom class" do
|
58
|
+
expected_value = ExampleCustomClassForValue.new('test')
|
59
|
+
let(:block) { proc { expected_value } }
|
60
|
+
|
61
|
+
get_instance_variables = lambda do |object|
|
62
|
+
object.instance_variables.map { |iv| object.instance_variable_get(iv) }.sort
|
63
|
+
end
|
64
|
+
|
65
|
+
it { is_expected.to be_a expected_value.class }
|
66
|
+
it { expect(get_instance_variables.call(value)).to eq get_instance_variables.call(expected_value) }
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when it's a huge string" do
|
70
|
+
long_string = 'X' * 1024 * 100
|
71
|
+
let(:block) { proc { long_string } }
|
72
|
+
it { is_expected.to eq long_string }
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'when an exception raised in the async process' do
|
76
|
+
let(:block) { proc { raise 'boom!' } }
|
77
|
+
it { expect { value }.to raise_error 'boom!' }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#kill' do
|
82
|
+
subject(:killing) { thread.kill }
|
83
|
+
|
84
|
+
context 'given the thread running in the background' do
|
85
|
+
let(:block) { proc { sleep(10) } }
|
86
|
+
|
87
|
+
it { expect { killing }.to_not raise_error }
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'given thread not running in the background anymore' do
|
91
|
+
let(:block) { proc {} }
|
92
|
+
|
93
|
+
it { expect { sleep(1); killing }.to_not raise_error }
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'given thread already killed' do
|
97
|
+
let(:block) { proc {} }
|
98
|
+
|
99
|
+
it { expect { sleep(1); 3.times { thread.kill } }.to_not raise_error }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe '#status' do
|
104
|
+
subject(:status) { thread.status }
|
105
|
+
|
106
|
+
context 'when thread is alive' do
|
107
|
+
let(:block) { proc { sleep(5) } }
|
108
|
+
|
109
|
+
it { is_expected.to eq 'run' }
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'when thread is completed' do
|
113
|
+
let(:block) { proc {} }
|
114
|
+
before { thread.join }
|
115
|
+
it { is_expected.to eq false }
|
116
|
+
end
|
117
|
+
|
118
|
+
context 'when thread is killed' do
|
119
|
+
let(:block) { proc { sleep(10) } }
|
120
|
+
before { thread.kill }
|
121
|
+
it { is_expected.to be false }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe 'no zombies' do
|
126
|
+
subject(:pid) { `ruby -r "#{BOOTSTRAP_FILE_PATH}" -e "pid = async { sleep(15) }.instance_variable_get(:@pid); puts pid "`.to_i }
|
127
|
+
|
128
|
+
it { expect { pid; sleep(2); Process.kill(0, pid) }.to raise_error Errno::ESRCH }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# http://ruby-doc.org/core-2.2.0/Thread.html
|
133
|
+
# join
|
134
|
+
# key?
|
135
|
+
# keys
|
136
|
+
# kill
|
137
|
+
# pending_interrupt?
|
138
|
+
# priority
|
139
|
+
# priority=
|
140
|
+
# raise
|
141
|
+
# run
|
142
|
+
# safe_level
|
143
|
+
# set_trace_func
|
144
|
+
# status
|
145
|
+
# stop?
|
146
|
+
# terminate
|
147
|
+
# thread_variable?
|
148
|
+
# thread_variable_get
|
149
|
+
# thread_variable_set
|
150
|
+
# thread_variables
|
151
|
+
# value
|
152
|
+
# wakeup
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'timeout'
|
3
|
+
require 'asynchronous'
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.expect_with :rspec do |expectations|
|
7
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
8
|
+
end
|
9
|
+
|
10
|
+
config.mock_with :rspec do |mocks|
|
11
|
+
mocks.verify_partial_doubles = true
|
12
|
+
end
|
13
|
+
|
14
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
15
|
+
end
|
16
|
+
|
17
|
+
Kernel.at_exit do
|
18
|
+
if Asynchronous::ZombiKiller::MOTHER_PID == $PROCESS_ID
|
19
|
+
begin
|
20
|
+
loop { Process.kill('KILL', Process.wait(-1, Process::WUNTRACED)) }
|
21
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
22
|
+
puts 'done'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class ExampleCustomClassForValue
|
28
|
+
attr_reader :a
|
29
|
+
def initialize(a)
|
30
|
+
@a = a
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
BOOTSTRAP_FILE_PATH = File.expand_path(File.join(File.dirname(__FILE__), '..', 'examples', 'bootstrap.rb'))
|
data/t.rb
ADDED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: asynchronous
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.0.0
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Luzsi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-01-26 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: 'DSL for for dead simple to use asynchronous patterns in both VM managed
|
14
14
|
and OS managed way (Concurrency and Parallelism) '
|
@@ -24,17 +24,18 @@ files:
|
|
24
24
|
- Rakefile
|
25
25
|
- VERSION
|
26
26
|
- asynchronous.gemspec
|
27
|
-
- examples/
|
27
|
+
- examples/basic.rb
|
28
28
|
- examples/bootstrap.rb
|
29
|
-
- examples/no_zombie_test.rb
|
30
|
-
- examples/parallelism.rb
|
31
29
|
- files.rb
|
32
|
-
- lib/async.rb
|
33
30
|
- lib/asynchronous.rb
|
34
|
-
- lib/asynchronous/
|
31
|
+
- lib/asynchronous/core_ext.rb
|
35
32
|
- lib/asynchronous/error.rb
|
36
|
-
- lib/asynchronous/
|
37
|
-
- lib/asynchronous/
|
33
|
+
- lib/asynchronous/thread.rb
|
34
|
+
- lib/asynchronous/utils.rb
|
35
|
+
- lib/asynchronous/zombi_killer.rb
|
36
|
+
- spec/asynchronous/thread_spec.rb
|
37
|
+
- spec/spec_helper.rb
|
38
|
+
- t.rb
|
38
39
|
homepage: https://github.com/adamluzsi/asynchronous
|
39
40
|
licenses: []
|
40
41
|
metadata: {}
|
@@ -49,13 +50,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
49
50
|
version: '0'
|
50
51
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
52
|
requirements:
|
52
|
-
- - "
|
53
|
+
- - ">="
|
53
54
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
55
|
+
version: '0'
|
55
56
|
requirements: []
|
56
57
|
rubyforge_project:
|
57
|
-
rubygems_version: 2.
|
58
|
+
rubygems_version: 2.6.13
|
58
59
|
signing_key:
|
59
60
|
specification_version: 4
|
60
61
|
summary: Simple Async Based on standard CRuby
|
61
|
-
test_files:
|
62
|
+
test_files:
|
63
|
+
- spec/asynchronous/thread_spec.rb
|
64
|
+
- spec/spec_helper.rb
|
data/examples/async_patterns.rb
DELETED
@@ -1,78 +0,0 @@
|
|
1
|
-
# Require Gemfile gems
|
2
|
-
require_relative 'bootstrap'
|
3
|
-
|
4
|
-
# you can use simple :c also instead of :concurrency
|
5
|
-
# remember :concurrency is all about GIL case, so
|
6
|
-
# you can modify the objects in memory
|
7
|
-
# This is ideal for little operations in simultaneously or
|
8
|
-
# when you need to update objects in the memory
|
9
|
-
thr1 = async :concurrency do
|
10
|
-
|
11
|
-
sleep 2
|
12
|
-
4 * 2
|
13
|
-
|
14
|
-
end
|
15
|
-
puts "hello concurrency"
|
16
|
-
|
17
|
-
calculation1 = thr1.value
|
18
|
-
calculation1 += 1
|
19
|
-
|
20
|
-
puts calculation1
|
21
|
-
|
22
|
-
#>--------------------------------------------------
|
23
|
-
# or you can use simple {} without sym and this will be by default a
|
24
|
-
# :concurrency pattern
|
25
|
-
|
26
|
-
thr2 = async { sleep 3; 4 * 3 }
|
27
|
-
|
28
|
-
puts "hello simple concurrency"
|
29
|
-
|
30
|
-
calculation2 = thr2.value
|
31
|
-
calculation2 += 1
|
32
|
-
|
33
|
-
# remember you have to use to cal the return value from the code block!
|
34
|
-
puts calculation2
|
35
|
-
|
36
|
-
|
37
|
-
#>--------------------------------------------------
|
38
|
-
# now let's see the Parallelism
|
39
|
-
# you can use simple :p or :parallelism as nametag
|
40
|
-
# remember :parallelism is all about real OS thread case, so
|
41
|
-
# you CANT modify the objects in memory only in sharedmemories,
|
42
|
-
# the normal variables will only be copy on write modify
|
43
|
-
# This is ideal for big operations where you need do a big process
|
44
|
-
# w/o the fear of the Garbage collector slowness or the GIL lock
|
45
|
-
# when you need to update objects in the memory use SharedMemory
|
46
|
-
thr3 = async :parallelism do
|
47
|
-
|
48
|
-
sleep 4
|
49
|
-
4 * 5
|
50
|
-
|
51
|
-
end
|
52
|
-
puts "hello parallelism"
|
53
|
-
|
54
|
-
calculation3 = thr3.value
|
55
|
-
calculation3 += 1
|
56
|
-
|
57
|
-
puts calculation3
|
58
|
-
|
59
|
-
#>--------------------------------------------------
|
60
|
-
|
61
|
-
# more complex way
|
62
|
-
|
63
|
-
puts "mixed usecase with arrays as return obj"
|
64
|
-
thr4 = async :parallelism do
|
65
|
-
|
66
|
-
sleep 4
|
67
|
-
# some big database processing brutal memory eater stuff
|
68
|
-
[4*5,"hy"]
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
thr5 = async {
|
73
|
-
[5+1,"sup!"]
|
74
|
-
}
|
75
|
-
|
76
|
-
puts 'calc1 is eql calc2:',thr4.value == thr5.value
|
77
|
-
puts (thr4.value+thr5.value).inspect
|
78
|
-
|
data/examples/no_zombie_test.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
require_relative 'bootstrap'
|
2
|
-
|
3
|
-
thr = async :parallelism do
|
4
|
-
|
5
|
-
# Zombie!
|
6
|
-
loop do
|
7
|
-
sleep 1
|
8
|
-
puts "brains(#{$$})"
|
9
|
-
end
|
10
|
-
# inf loop
|
11
|
-
#
|
12
|
-
|
13
|
-
end
|
14
|
-
|
15
|
-
puts $$
|
16
|
-
Thread.new do
|
17
|
-
|
18
|
-
sleep 5
|
19
|
-
|
20
|
-
# this want to demonstrate that,
|
21
|
-
# if the main process is killed,
|
22
|
-
# you wont leave zombies behind!
|
23
|
-
system "kill -9 #{$$}"
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
loop do
|
28
|
-
sleep 1
|
29
|
-
end
|
data/examples/parallelism.rb
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
require_relative 'bootstrap'
|
2
|
-
|
3
|
-
async1= async :OS do
|
4
|
-
1000000*5
|
5
|
-
end
|
6
|
-
|
7
|
-
async2= async :OS do
|
8
|
-
|
9
|
-
var = ("!" * 1000000)
|
10
|
-
puts "the superHuge String length in the pipe is: #{var.length}"
|
11
|
-
|
12
|
-
var
|
13
|
-
end
|
14
|
-
|
15
|
-
async2_error= async :OS do
|
16
|
-
|
17
|
-
raise(SystemExit)
|
18
|
-
puts 'never happen, to system error happened!'
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
async3= async :OS do
|
23
|
-
1000000*5.0
|
24
|
-
end
|
25
|
-
|
26
|
-
begin
|
27
|
-
async2_error.value
|
28
|
-
rescue Exception => ex
|
29
|
-
puts "\n",'when error happen:',ex
|
30
|
-
end
|
31
|
-
|
32
|
-
puts "\n",'result length based on string form:'
|
33
|
-
puts async1.value.to_s.length
|
34
|
-
puts async2.value.to_s.length
|
35
|
-
puts async3.value.to_s.length
|
data/lib/async.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require 'asynchronous'
|
@@ -1,44 +0,0 @@
|
|
1
|
-
# you can use simple :c also instead of :concurrency
|
2
|
-
# remember :concurrency is all about GIL case, so
|
3
|
-
# you can modify the objects in memory
|
4
|
-
# This is ideal for little operations in simultaneously or
|
5
|
-
# when you need to update objects in the memory
|
6
|
-
class Asynchronous::Concurrency
|
7
|
-
|
8
|
-
def initialize(&block)
|
9
|
-
|
10
|
-
@rescue_state= nil
|
11
|
-
@try_count = 0
|
12
|
-
|
13
|
-
begin
|
14
|
-
@value= nil
|
15
|
-
@thread ||= ::Thread.new { block.call }
|
16
|
-
@rescue_state= nil
|
17
|
-
rescue ThreadError
|
18
|
-
@rescue_state ||= true
|
19
|
-
@try_count += 1
|
20
|
-
if 3 <= @try_count
|
21
|
-
@value= block.call
|
22
|
-
@rescue_state= nil
|
23
|
-
else
|
24
|
-
sleep 5
|
25
|
-
retry
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
end
|
30
|
-
|
31
|
-
def join
|
32
|
-
if @value.nil?
|
33
|
-
until @rescue_state.nil?
|
34
|
-
sleep 1
|
35
|
-
end
|
36
|
-
@value= @thread.value
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def value
|
41
|
-
join; @value
|
42
|
-
end
|
43
|
-
|
44
|
-
end
|
data/lib/asynchronous/kernel.rb
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
# kernel method for asyncron calls
|
2
|
-
# basic version is :
|
3
|
-
#
|
4
|
-
# var= async { "ruby Code here" }
|
5
|
-
# var.value #> "ruby Code here"
|
6
|
-
#
|
7
|
-
# or
|
8
|
-
#
|
9
|
-
# var = async :parallelism do
|
10
|
-
# "some awsome ruby code here!"
|
11
|
-
# end
|
12
|
-
#
|
13
|
-
module Asynchronous::DCI
|
14
|
-
|
15
|
-
extend self
|
16
|
-
|
17
|
-
def async(type= :VM, &block)
|
18
|
-
case type.to_s.downcase[0]
|
19
|
-
|
20
|
-
# Concurrency / VM / Green
|
21
|
-
when *%W[ c v g ]
|
22
|
-
::Asynchronous::Concurrency.new(&block)
|
23
|
-
|
24
|
-
# Parallelism / OS / Native
|
25
|
-
when *%W[ p o n ]
|
26
|
-
::Asynchronous::Parallelism.new(&block)
|
27
|
-
|
28
|
-
else
|
29
|
-
::Asynchronous::Concurrency.new(&block)
|
30
|
-
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
end
|
35
|
-
|
36
|
-
Kernel.class_eval do
|
37
|
-
|
38
|
-
def async(type=nil, &block)
|
39
|
-
::Asynchronous::DCI.async(type, &block)
|
40
|
-
end
|
41
|
-
|
42
|
-
end
|
@@ -1,135 +0,0 @@
|
|
1
|
-
# now let's see the Parallelism
|
2
|
-
# you can use simple :p also instead of :parallelism
|
3
|
-
# remember :parallelism is all about real OS thread case, so
|
4
|
-
# you CANT modify the objects in memory only copy on write modify
|
5
|
-
# This is ideal for big operations where you need do a big process
|
6
|
-
# and only get the return value so you can do big works without the fear of the
|
7
|
-
# Garbage collector slowness or the GIL lock
|
8
|
-
# when you need to update objects in the memory use :concurrency
|
9
|
-
class Asynchronous::Parallelism
|
10
|
-
|
11
|
-
attr_reader :pid
|
12
|
-
|
13
|
-
@@pids ||= []
|
14
|
-
@@motherpid ||= $$
|
15
|
-
|
16
|
-
def initialize(disable_gc=true,&callable)
|
17
|
-
|
18
|
-
@disable_gc = !!disable_gc
|
19
|
-
|
20
|
-
@comm_line = ::IO.pipe
|
21
|
-
|
22
|
-
@value = nil
|
23
|
-
@read_buffer = nil
|
24
|
-
|
25
|
-
read_buffer
|
26
|
-
|
27
|
-
@pid = fork(&callable)
|
28
|
-
@@pids.push(@pid)
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.alive?(pid)
|
33
|
-
::Process.kill(0, pid)
|
34
|
-
return true
|
35
|
-
rescue ::Errno::ESRCH
|
36
|
-
return false
|
37
|
-
end
|
38
|
-
|
39
|
-
def join
|
40
|
-
if @value.nil?
|
41
|
-
|
42
|
-
::Process.wait(@pid, ::Process::WNOHANG)
|
43
|
-
|
44
|
-
@comm_line[1].close
|
45
|
-
@read_buffer.join
|
46
|
-
@comm_line[0].close
|
47
|
-
|
48
|
-
if @value.is_a?(::Asynchronous::Error)
|
49
|
-
raise(@value.wrapped_error)
|
50
|
-
end
|
51
|
-
|
52
|
-
end; self
|
53
|
-
end
|
54
|
-
|
55
|
-
def value
|
56
|
-
join; @value
|
57
|
-
end
|
58
|
-
|
59
|
-
protected
|
60
|
-
|
61
|
-
def pipe_read
|
62
|
-
@comm_line[0]
|
63
|
-
end
|
64
|
-
|
65
|
-
def pipe_write
|
66
|
-
@comm_line[1]
|
67
|
-
end
|
68
|
-
|
69
|
-
def fork(&block)
|
70
|
-
return ::Kernel.fork do
|
71
|
-
|
72
|
-
GC.disable if @disable_gc
|
73
|
-
|
74
|
-
begin
|
75
|
-
::Kernel.trap("TERM") do
|
76
|
-
::Kernel.exit
|
77
|
-
end
|
78
|
-
::Thread.new do
|
79
|
-
::Kernel.loop do
|
80
|
-
begin
|
81
|
-
::Kernel.sleep 1
|
82
|
-
if ::Asynchronous::Parallelism.alive?(@@motherpid) == false
|
83
|
-
::Kernel.exit!
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
@comm_line[0].close
|
91
|
-
|
92
|
-
result = begin
|
93
|
-
block.call
|
94
|
-
rescue ::Exception => ex
|
95
|
-
::Asynchronous::Error.new(ex)
|
96
|
-
end
|
97
|
-
|
98
|
-
dumped_result = ::Marshal.dump(result).to_s
|
99
|
-
|
100
|
-
@comm_line[1].write(dumped_result)
|
101
|
-
::Kernel.sleep(dumped_result.length.to_f*0.005)
|
102
|
-
|
103
|
-
@comm_line[1].flush
|
104
|
-
@comm_line[1].close
|
105
|
-
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def read_buffer
|
110
|
-
@read_buffer = ::Thread.new do
|
111
|
-
while !@comm_line[0].eof?
|
112
|
-
|
113
|
-
begin
|
114
|
-
@value = ::Marshal.load(@comm_line[0])
|
115
|
-
rescue ::ArgumentError => ex
|
116
|
-
sleep(1)
|
117
|
-
retry if ex.respond_to?(:message) && ex.message =~ /marshal data too short/i
|
118
|
-
end
|
119
|
-
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
# kill kidos at Kernel Exit
|
125
|
-
::Kernel.at_exit do
|
126
|
-
@@pids.each do |pid|
|
127
|
-
begin
|
128
|
-
::Process.kill(:TERM, pid)
|
129
|
-
rescue ::Errno::ESRCH, ::Errno::ECHILD
|
130
|
-
#::STDOUT.puts "`kill': No such process (Errno::ESRCH)"
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
end
|