async_enum 0.0.5 → 0.0.6
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 +8 -8
- data/README.md +14 -5
- data/async_enum.gemspec +1 -1
- data/demos/lock.rb +2 -1
- data/demos/pool_size.rb +1 -1
- data/demos/sleep.rb +1 -1
- data/lib/enumerator/async.rb +32 -77
- data/test/async_enum_test/enumerator_async_test.rb +0 -10
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
OTY4MmYzYjc3ZjgxMDRjNjE5MWM4OGZhYTUzYWQwZmVkMTg4NDc2Mw==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YTY3NjJjYmI1ZGE4NzUxMjNmODJjN2YwMDMxOTYwYzM0NzZjOTFiOA==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
OWQ1NzEzMDQ4OGRmYmU2NjE0N2IzZjlkYjcyYTVmNGM5YzQ5NzUzYTZiMjIy
|
10
|
+
OThmOGNmZDUxZjdlYjEwOTU2MDlhZWU0YWU1M2ZhNzNkODU2NGU4OTYwNWEw
|
11
|
+
YjA0OGFkMWYyNDYzZGM1NTkwOTNlMTc5ZDg0Zjc2YzJmNzEwZmE=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MTVmZjc3M2JhYmEwNTMxMjQzNGY1NWM0Y2UwYWExMmM4NzNjMTQ4NWI4ZDU3
|
14
|
+
NTRkYTI0ZjdkYjhmYjcyYzg0NTE3YTQyOTVmZGZjYTlmMDNlMWNjMzEyMjE3
|
15
|
+
NjM3ZDMxMjhlY2RhNmNmNjMzNTcxOWM3ZmE0OWJhNjNhODY2NGM=
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# async\_enum
|
1
|
+
# async\_enum 
|
2
2
|
|
3
3
|
Inspired by Enumerable#lazy coupled with Enumerator::Lazy, and the Async.js library, I thought it would be cool to have syntax like `urls.async.map{ |url| http_get(url) }`.
|
4
4
|
|
@@ -83,12 +83,15 @@ end
|
|
83
83
|
|
84
84
|
#### Preventing race conditions
|
85
85
|
|
86
|
-
When programming concurrently, nasty bugs can come up because some operations aren't atomic. For instance, incrementing a variable `x += 1` will not necessarily work as expected.
|
86
|
+
When programming concurrently, nasty bugs can come up because some operations aren't atomic. For instance, incrementing a variable `x += 1` will not necessarily work as expected. Use a lock when encountering these types of errors.
|
87
87
|
|
88
88
|
```ruby
|
89
|
+
require 'async_enum'
|
90
|
+
|
89
91
|
count = 0
|
92
|
+
mutex = Mutex.new
|
90
93
|
('a'..'z').async.each do
|
91
|
-
|
94
|
+
mutex.synchronize do
|
92
95
|
count += 1
|
93
96
|
end
|
94
97
|
end
|
@@ -96,7 +99,7 @@ count
|
|
96
99
|
# => 26
|
97
100
|
```
|
98
101
|
|
99
|
-
|
102
|
+
There used to be a lock DSL syntax, but it ruined using async_enum in scoped method calls.
|
100
103
|
|
101
104
|
## Notes
|
102
105
|
|
@@ -116,6 +119,12 @@ $ cd async_enum
|
|
116
119
|
$ ruby -I lib demos/sleep.rb
|
117
120
|
```
|
118
121
|
|
119
|
-
|
122
|
+
#### Contributions
|
123
|
+
|
124
|
+
* Yoshida Tetsuya ([@yoshida-eth0](https://github.com/yoshida-eth0)) fixed the fiber error issue, and made the gem work with hashes.
|
120
125
|
|
121
126
|
Please report errors and feel free to contribute and improve things!
|
127
|
+
|
128
|
+
|
129
|
+
[](https://bitdeli.com/free "Bitdeli Badge")
|
130
|
+
|
data/async_enum.gemspec
CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = 'async_enum'
|
7
|
-
s.version = '0.0.
|
7
|
+
s.version = '0.0.6'
|
8
8
|
s.authors = %w(aj0strow)
|
9
9
|
s.email = 'alexander.ostrow@gmail.com'
|
10
10
|
s.description = 'iterate over enumerable objects concurrently'
|
data/demos/lock.rb
CHANGED
data/demos/pool_size.rb
CHANGED
data/demos/sleep.rb
CHANGED
data/lib/enumerator/async.rb
CHANGED
@@ -3,35 +3,17 @@ require 'thread'
|
|
3
3
|
class Enumerator
|
4
4
|
class Async < Enumerator
|
5
5
|
|
6
|
-
class Lockset
|
7
|
-
def initialize
|
8
|
-
@semaphores = Hash.new do |locks, key|
|
9
|
-
locks[key] = Mutex.new
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def lock(key = :__default__, &thread_unsafe_block)
|
14
|
-
@semaphores[key].synchronize(&thread_unsafe_block)
|
15
|
-
end
|
16
|
-
|
17
|
-
alias_method :evaluate, :instance_exec
|
18
|
-
end
|
19
|
-
|
20
6
|
EOQ = Object.new
|
21
7
|
private_constant :EOQ
|
22
8
|
|
23
9
|
def initialize(enum, pool_size = nil)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
29
|
-
pool_size = pool_size.to_i
|
10
|
+
pool_size = (pool_size || enum.count).to_i
|
11
|
+
unless pool_size >= 1
|
12
|
+
message = "Thread pool size is invalid! Expected a positive integer but got: #{pool_size}"
|
13
|
+
raise ArgumentError, message
|
30
14
|
end
|
31
15
|
|
32
|
-
@enum = enum
|
33
|
-
@pool_size = pool_size
|
34
|
-
@lockset = Lockset.new
|
16
|
+
@enum, @pool_size = enum, pool_size
|
35
17
|
end
|
36
18
|
|
37
19
|
def to_a
|
@@ -41,86 +23,59 @@ class Enumerator
|
|
41
23
|
def sync
|
42
24
|
@enum
|
43
25
|
end
|
44
|
-
|
45
26
|
alias_method :to_enum, :sync
|
46
27
|
|
47
28
|
def size
|
48
29
|
@enum.size
|
49
30
|
end
|
50
31
|
|
51
|
-
def each
|
52
|
-
raise_error(
|
32
|
+
def each
|
33
|
+
raise_error(:each) unless block_given?
|
53
34
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
item != EOQ ? evaluate(item, &work) : break
|
62
|
-
end
|
35
|
+
queue = SizedQueue.new @pool_size
|
36
|
+
|
37
|
+
threads = @pool_size.times.map do
|
38
|
+
Thread.new do
|
39
|
+
loop do
|
40
|
+
item = queue.pop
|
41
|
+
item != EOQ ? yield(item) : break
|
63
42
|
end
|
64
43
|
end
|
65
|
-
|
66
|
-
begin
|
67
|
-
loop { queue.push @enum.next }
|
68
|
-
rescue StopIteration
|
69
|
-
ensure
|
70
|
-
@pool_size.times { queue.push EOQ }
|
71
|
-
end
|
72
|
-
|
73
|
-
threads.each(&:join)
|
74
|
-
@enum.rewind
|
75
|
-
else
|
76
|
-
unlimited_threads(&work).each(&:join)
|
77
44
|
end
|
45
|
+
|
46
|
+
begin
|
47
|
+
loop { queue.push @enum.next }
|
48
|
+
rescue StopIteration
|
49
|
+
ensure
|
50
|
+
@pool_size.times { queue.push EOQ }
|
51
|
+
end
|
52
|
+
|
53
|
+
threads.each(&:join)
|
54
|
+
@enum.rewind
|
78
55
|
self
|
79
56
|
end
|
80
57
|
|
81
58
|
def with_index(start = 0, &work)
|
82
59
|
@enum = @enum.with_index(start)
|
83
|
-
|
84
|
-
each(&work)
|
85
|
-
else
|
86
|
-
self
|
87
|
-
end
|
60
|
+
block_given? ? each(&work) : self
|
88
61
|
end
|
89
62
|
|
90
|
-
def with_object(
|
91
|
-
@enum = @enum.with_object(
|
92
|
-
|
93
|
-
each(&work); obj
|
94
|
-
else
|
95
|
-
self
|
96
|
-
end
|
63
|
+
def with_object(object, &work)
|
64
|
+
@enum = @enum.with_object(object)
|
65
|
+
block_given? ? (each(&work) and object) : self
|
97
66
|
end
|
98
67
|
|
99
|
-
def map
|
100
|
-
raise_error(
|
68
|
+
def map
|
69
|
+
raise_error(:map) unless block_given?
|
101
70
|
|
102
|
-
|
103
|
-
outs = []
|
71
|
+
[].tap do |outs|
|
104
72
|
with_index do |item, index|
|
105
|
-
outs[index] =
|
73
|
+
outs[index] = yield(item)
|
106
74
|
end
|
107
|
-
outs
|
108
|
-
else
|
109
|
-
unlimited_threads(&work).map(&:value)
|
110
75
|
end
|
111
76
|
end
|
112
77
|
|
113
78
|
private
|
114
|
-
|
115
|
-
def evaluate(*args, &work)
|
116
|
-
@lockset.instance_exec(*args, &work)
|
117
|
-
end
|
118
|
-
|
119
|
-
def unlimited_threads(&work)
|
120
|
-
@enum.map do |*args|
|
121
|
-
Thread.new{ evaluate(*args, &work) }
|
122
|
-
end
|
123
|
-
end
|
124
79
|
|
125
80
|
def raise_error(method)
|
126
81
|
raise ArgumentError, "Tried to call async #{method} without a block"
|
@@ -87,15 +87,5 @@ class EnumeratorAsyncTest < Test
|
|
87
87
|
strs = @enum.map{ |i| sleep rand; i.to_s }
|
88
88
|
assert_equal '1 2 3 4 5', strs.join(' ')
|
89
89
|
end
|
90
|
-
|
91
|
-
test 'lock in block' do
|
92
|
-
count = 0
|
93
|
-
1000.times.async(5).each do
|
94
|
-
lock :count do
|
95
|
-
count += 1
|
96
|
-
end
|
97
|
-
end
|
98
|
-
assert_equal 1000, count
|
99
|
-
end
|
100
90
|
|
101
91
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async_enum
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- aj0strow
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-01-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|