asynchronous 4.0.0.pre → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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
|