vinted-thread 0.1.1
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 +7 -0
- data/.travis.yml +7 -0
- data/README.md +195 -0
- data/Rakefile +21 -0
- data/lib/thread/channel.rb +105 -0
- data/lib/thread/delay.rb +94 -0
- data/lib/thread/every.rb +197 -0
- data/lib/thread/future.rb +158 -0
- data/lib/thread/pipe.rb +119 -0
- data/lib/thread/pool.rb +428 -0
- data/lib/thread/process.rb +72 -0
- data/lib/thread/promise.rb +83 -0
- data/lib/thread/recursive_mutex.rb +38 -0
- data/tests/channel_spec.rb +41 -0
- data/tests/delay_spec.rb +13 -0
- data/tests/every_spec.rb +11 -0
- data/tests/future_spec.rb +37 -0
- data/tests/pipe_spec.rb +25 -0
- data/tests/promise_spec.rb +37 -0
- data/thread.gemspec +18 -0
- metadata +90 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5135a4b3c6ef3c2396b06b34c7040d39b52fbe3f
|
4
|
+
data.tar.gz: 445e9bec68c67f6597c037844b90b5c41cc29f8c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3822c43a01c8f8d29652b46917be0827a4c83f061e7b159222d202c16fe86d293468991e7342cd092f8ba0065fddbc7d62796d9c31ac6b0a49c83adfc3a58942
|
7
|
+
data.tar.gz: f257a9d93c9e63b8e8708b5cfffb37be3a7abd929342d9f16cfbbde5b6ed2b493cfe63212f8f8dd37e14c8710a6e7a3321b024717090007c7bdad9dd700b99dd
|
data/.travis.yml
ADDED
data/README.md
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
thread - various extensions to the thread library
|
2
|
+
=================================================
|
3
|
+
|
4
|
+
Pool
|
5
|
+
====
|
6
|
+
All the implementations I looked at were either buggy or wasted CPU resources
|
7
|
+
for no apparent reason, for example used a sleep of 0.01 seconds to then check for
|
8
|
+
readiness and stuff like this.
|
9
|
+
|
10
|
+
This implementation uses standard locking functions to work properly across multiple Ruby
|
11
|
+
implementations.
|
12
|
+
|
13
|
+
Example
|
14
|
+
-------
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
require 'thread/pool'
|
18
|
+
|
19
|
+
pool = Thread.pool(4)
|
20
|
+
|
21
|
+
10.times {
|
22
|
+
pool.process {
|
23
|
+
sleep 2
|
24
|
+
|
25
|
+
puts 'lol'
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
pool.shutdown
|
30
|
+
```
|
31
|
+
|
32
|
+
You should get 4 lols every 2 seconds and it should exit after 10 of them.
|
33
|
+
|
34
|
+
Channel
|
35
|
+
=======
|
36
|
+
This implements a channel where you can write messages and receive messages.
|
37
|
+
|
38
|
+
Example
|
39
|
+
-------
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
require 'thread/channel'
|
43
|
+
|
44
|
+
channel = Thread.channel
|
45
|
+
channel.send 'wat'
|
46
|
+
channel.receive # => 'wat'
|
47
|
+
|
48
|
+
channel = Thread.channel { |o| o.is_a?(Integer) }
|
49
|
+
channel.send 'wat' # => ArgumentError: guard mismatch
|
50
|
+
|
51
|
+
Thread.new {
|
52
|
+
while num = channel.receive(&:even?)
|
53
|
+
puts 'Aye!'
|
54
|
+
end
|
55
|
+
}
|
56
|
+
|
57
|
+
Thread.new {
|
58
|
+
while num = channel.receive(&:odd?)
|
59
|
+
puts 'Arrr!'
|
60
|
+
end
|
61
|
+
}
|
62
|
+
|
63
|
+
loop {
|
64
|
+
channel.send rand(1_000_000_000)
|
65
|
+
|
66
|
+
sleep 0.5
|
67
|
+
}
|
68
|
+
```
|
69
|
+
|
70
|
+
Pipe
|
71
|
+
====
|
72
|
+
A pipe allows you to execute various tasks on a set of data in parallel,
|
73
|
+
each datum inserted in the pipe is passed along through queues to the various
|
74
|
+
functions composing the pipe, the final result is inserted in the final queue.
|
75
|
+
|
76
|
+
Example
|
77
|
+
-------
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
require 'thread/pipe'
|
81
|
+
|
82
|
+
p = Thread |-> d { d * 2 } |-> d { d * 4 }
|
83
|
+
p << 2
|
84
|
+
|
85
|
+
puts ~p # => 16
|
86
|
+
```
|
87
|
+
|
88
|
+
Process
|
89
|
+
=======
|
90
|
+
A process helps reducing programming errors coming from race conditions and the
|
91
|
+
like, the only way to interact with a process is through messages.
|
92
|
+
|
93
|
+
Multiple processes should talk with eachother through messages.
|
94
|
+
|
95
|
+
Example
|
96
|
+
-------
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
require 'thread/process'
|
100
|
+
|
101
|
+
p = Thread.process {
|
102
|
+
loop {
|
103
|
+
puts receive.inspect
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
p << 42
|
108
|
+
p << 23
|
109
|
+
```
|
110
|
+
|
111
|
+
Promise
|
112
|
+
=======
|
113
|
+
This implements the promise pattern, allowing you to pass around an object
|
114
|
+
where you can send a value and extract a value, in a thread-safe way, accessing
|
115
|
+
the value will wait for the value to be delivered.
|
116
|
+
|
117
|
+
Example
|
118
|
+
-------
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
require 'thread/promise'
|
122
|
+
|
123
|
+
p = Thread.promise
|
124
|
+
|
125
|
+
Thread.new {
|
126
|
+
sleep 5
|
127
|
+
p << 42
|
128
|
+
}
|
129
|
+
|
130
|
+
puts ~p # => 42
|
131
|
+
```
|
132
|
+
|
133
|
+
Future
|
134
|
+
======
|
135
|
+
A future is somewhat a promise, except you pass it a block to execute in
|
136
|
+
another thread.
|
137
|
+
|
138
|
+
The value returned by the block will be the value of the promise.
|
139
|
+
|
140
|
+
Example
|
141
|
+
-------
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
require 'thread/future'
|
145
|
+
|
146
|
+
f = Thread.future {
|
147
|
+
sleep 5
|
148
|
+
|
149
|
+
42
|
150
|
+
}
|
151
|
+
|
152
|
+
puts ~f # => 42
|
153
|
+
```
|
154
|
+
|
155
|
+
Delay
|
156
|
+
=====
|
157
|
+
A delay is kind of a promise, except the block is called when the value is
|
158
|
+
being accessed and the result is cached.
|
159
|
+
|
160
|
+
Example
|
161
|
+
-------
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
require 'thread/delay'
|
165
|
+
|
166
|
+
d = Thread.delay {
|
167
|
+
42
|
168
|
+
}
|
169
|
+
|
170
|
+
puts ~d # => 42
|
171
|
+
```
|
172
|
+
|
173
|
+
Every
|
174
|
+
=====
|
175
|
+
An every executes the block every given seconds and yields the value to the
|
176
|
+
every object, you can then check if the current value is old or how much time
|
177
|
+
is left until the second call is done.
|
178
|
+
|
179
|
+
Example
|
180
|
+
-------
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
require 'net/http'
|
184
|
+
require 'thread/every'
|
185
|
+
|
186
|
+
e = Thread.every(5) {
|
187
|
+
Net::HTTP.get(URI.parse('http://www.whattimeisit.com/')).match %r{<B>(.*?)<BR>\s+(.*?)</B>}m do |m|
|
188
|
+
{ date: m[1], time: m[2] }
|
189
|
+
end
|
190
|
+
}
|
191
|
+
|
192
|
+
loop do
|
193
|
+
puts ~e
|
194
|
+
end
|
195
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
task :default => [:install, :test]
|
5
|
+
|
6
|
+
task :install do
|
7
|
+
sh 'gem install --no-force rspec'
|
8
|
+
sh 'gem build *.gemspec'
|
9
|
+
sh 'gem install *.gem'
|
10
|
+
end
|
11
|
+
|
12
|
+
task :test do
|
13
|
+
FileUtils.cd 'tests' do
|
14
|
+
sh 'rspec channel_spec.rb --backtrace --color --format doc'
|
15
|
+
sh 'rspec promise_spec.rb --backtrace --color --format doc'
|
16
|
+
sh 'rspec future_spec.rb --backtrace --color --format doc'
|
17
|
+
sh 'rspec delay_spec.rb --backtrace --color --format doc'
|
18
|
+
sh 'rspec pipe_spec.rb --backtrace --color --format doc'
|
19
|
+
sh 'rspec every_spec.rb --backtrace --color --format doc'
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
#--
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
3
|
+
# Version 2, December 2004
|
4
|
+
#
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
7
|
+
#
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
9
|
+
#++
|
10
|
+
|
11
|
+
require 'thread'
|
12
|
+
|
13
|
+
# A channel lets you send and receive various messages in a thread-safe way.
|
14
|
+
#
|
15
|
+
# It also allows for guards upon sending and retrieval, to ensure the passed
|
16
|
+
# messages are safe to be consumed.
|
17
|
+
class Thread::Channel
|
18
|
+
# Create a channel with optional initial messages and optional channel guard.
|
19
|
+
def initialize (messages = [], &block)
|
20
|
+
@messages = []
|
21
|
+
@mutex = Mutex.new
|
22
|
+
@check = block
|
23
|
+
|
24
|
+
messages.each {|o|
|
25
|
+
send o
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
# Send a message to the channel.
|
30
|
+
#
|
31
|
+
# If there's a guard, the value is passed to it, if the guard returns a falsy value
|
32
|
+
# an ArgumentError exception is raised and the message is not sent.
|
33
|
+
def send (what)
|
34
|
+
if @check && !@check.call(what)
|
35
|
+
raise ArgumentError, 'guard mismatch'
|
36
|
+
end
|
37
|
+
|
38
|
+
@mutex.synchronize {
|
39
|
+
@messages << what
|
40
|
+
|
41
|
+
cond.broadcast if cond?
|
42
|
+
}
|
43
|
+
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
# Receive a message, if there are none the call blocks until there's one.
|
48
|
+
#
|
49
|
+
# If a block is passed, it's used as guard to match to a message.
|
50
|
+
def receive (&block)
|
51
|
+
message = nil
|
52
|
+
|
53
|
+
if block
|
54
|
+
found = false
|
55
|
+
|
56
|
+
until found
|
57
|
+
@mutex.synchronize {
|
58
|
+
if index = @messages.find_index(&block)
|
59
|
+
message = @messages.delete_at(index)
|
60
|
+
found = true
|
61
|
+
else
|
62
|
+
cond.wait @mutex
|
63
|
+
end
|
64
|
+
}
|
65
|
+
end
|
66
|
+
else
|
67
|
+
@mutex.synchronize {
|
68
|
+
if @messages.empty?
|
69
|
+
cond.wait @mutex
|
70
|
+
end
|
71
|
+
|
72
|
+
message = @messages.shift
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
message
|
77
|
+
end
|
78
|
+
|
79
|
+
# Receive a message, if there are none the call returns nil.
|
80
|
+
#
|
81
|
+
# If a block is passed, it's used as guard to match to a message.
|
82
|
+
def receive! (&block)
|
83
|
+
if block
|
84
|
+
@messages.delete_at(@messages.find_index(&block))
|
85
|
+
else
|
86
|
+
@messages.shift
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
def cond?
|
92
|
+
instance_variable_defined? :@cond
|
93
|
+
end
|
94
|
+
|
95
|
+
def cond
|
96
|
+
@cond ||= ConditionVariable.new
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class Thread
|
101
|
+
# Helper to create a channel.
|
102
|
+
def self.channel (*args, &block)
|
103
|
+
Thread::Channel.new(*args, &block)
|
104
|
+
end
|
105
|
+
end
|
data/lib/thread/delay.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
#--
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
3
|
+
# Version 2, December 2004
|
4
|
+
#
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
7
|
+
#
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
9
|
+
#++
|
10
|
+
|
11
|
+
require 'thread'
|
12
|
+
|
13
|
+
# A delay is an object that incapsulates a block which is called upon
|
14
|
+
# value retrieval, and its result cached.
|
15
|
+
class Thread::Delay
|
16
|
+
# Create a delay with the passed block.
|
17
|
+
def initialize (&block)
|
18
|
+
raise ArgumentError, 'no block given' unless block
|
19
|
+
|
20
|
+
@mutex = Mutex.new
|
21
|
+
@block = block
|
22
|
+
end
|
23
|
+
|
24
|
+
# Check if an exception has been raised.
|
25
|
+
def exception?
|
26
|
+
@mutex.synchronize {
|
27
|
+
instance_variable_defined? :@exception
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
# Return the raised exception.
|
32
|
+
def exception
|
33
|
+
@mutex.synchronize {
|
34
|
+
@exception
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Check if the delay has been called.
|
39
|
+
def delivered?
|
40
|
+
@mutex.synchronize {
|
41
|
+
instance_variable_defined? :@value
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
alias realized? delivered?
|
46
|
+
|
47
|
+
# Get the value of the delay, if it's already been executed, return the
|
48
|
+
# cached result, otherwise execute the block and return the value.
|
49
|
+
#
|
50
|
+
# In case the block raises an exception, it will be raised, the exception is
|
51
|
+
# cached and will be raised every time you access the value.
|
52
|
+
def value
|
53
|
+
@mutex.synchronize {
|
54
|
+
raise @exception if instance_variable_defined? :@exception
|
55
|
+
|
56
|
+
return @value if instance_variable_defined? :@value
|
57
|
+
|
58
|
+
begin
|
59
|
+
@value = @block.call
|
60
|
+
rescue Exception => e
|
61
|
+
@exception = e
|
62
|
+
|
63
|
+
raise
|
64
|
+
end
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
alias ~ value
|
69
|
+
|
70
|
+
# Do the same as {#value}, but return nil in case of exception.
|
71
|
+
def value!
|
72
|
+
begin
|
73
|
+
value
|
74
|
+
rescue Exception
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
alias ! value!
|
80
|
+
end
|
81
|
+
|
82
|
+
class Thread
|
83
|
+
# Helper to create Thread::Delay
|
84
|
+
def self.delay (&block)
|
85
|
+
Thread::Delay.new(&block)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
module Kernel
|
90
|
+
# Helper to create a Thread::Delay
|
91
|
+
def delay (&block)
|
92
|
+
Thread::Delay.new(&block)
|
93
|
+
end
|
94
|
+
end
|
data/lib/thread/every.rb
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
#--
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
3
|
+
# Version 2, December 2004
|
4
|
+
#
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
7
|
+
#
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
9
|
+
#++
|
10
|
+
|
11
|
+
require 'thread'
|
12
|
+
|
13
|
+
# An every runs the given block every given seconds, you can then get the
|
14
|
+
# value, check if the value is old and you can check how many seconds
|
15
|
+
# until the next run.
|
16
|
+
class Thread::Every
|
17
|
+
Cancel = Class.new(Exception)
|
18
|
+
Restart = Class.new(Exception)
|
19
|
+
|
20
|
+
# Create an every with the given seconds and block.
|
21
|
+
def initialize (every, &block)
|
22
|
+
raise ArgumentError, 'no block given' unless block
|
23
|
+
|
24
|
+
@every = every
|
25
|
+
@old = true
|
26
|
+
@mutex = Mutex.new
|
27
|
+
@thread = Thread.new {
|
28
|
+
loop do
|
29
|
+
begin
|
30
|
+
value = block.call
|
31
|
+
|
32
|
+
@mutex.synchronize {
|
33
|
+
@at = Time.now
|
34
|
+
@value = value
|
35
|
+
@old = false
|
36
|
+
@exception = nil
|
37
|
+
}
|
38
|
+
rescue Restart
|
39
|
+
next
|
40
|
+
rescue Exception => e
|
41
|
+
@mutex.synchronize {
|
42
|
+
@at = Time.now
|
43
|
+
@exception = e
|
44
|
+
}
|
45
|
+
|
46
|
+
break if e.is_a? Cancel
|
47
|
+
end
|
48
|
+
|
49
|
+
cond.broadcast if cond?
|
50
|
+
|
51
|
+
begin
|
52
|
+
sleep @every
|
53
|
+
rescue Restart
|
54
|
+
next
|
55
|
+
rescue Cancel => e
|
56
|
+
@mutex.synchronize {
|
57
|
+
@at = Time.now
|
58
|
+
@exception = e
|
59
|
+
}
|
60
|
+
|
61
|
+
break
|
62
|
+
end
|
63
|
+
end
|
64
|
+
}
|
65
|
+
|
66
|
+
ObjectSpace.define_finalizer self, self.class.finalizer(@thread)
|
67
|
+
end
|
68
|
+
|
69
|
+
# @private
|
70
|
+
def self.finalizer (thread)
|
71
|
+
proc {
|
72
|
+
thread.raise Cancel.new
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
# Change the number of seconds between each call.
|
77
|
+
def every (seconds)
|
78
|
+
@every = seconds
|
79
|
+
|
80
|
+
restart
|
81
|
+
end
|
82
|
+
|
83
|
+
# Cancel the every, {#value} will yield a Cancel exception.
|
84
|
+
def cancel
|
85
|
+
@mutex.synchronize {
|
86
|
+
@thread.raise Cancel.new('every cancelled')
|
87
|
+
}
|
88
|
+
|
89
|
+
self
|
90
|
+
end
|
91
|
+
|
92
|
+
# Check if the every has been cancelled.
|
93
|
+
def cancelled?
|
94
|
+
@mutex.synchronize {
|
95
|
+
@exception.is_a? Cancel
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
# Checks when the every was cancelled.
|
100
|
+
def cancelled_at
|
101
|
+
if cancelled?
|
102
|
+
@mutex.synchronize {
|
103
|
+
@at
|
104
|
+
}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Restart the every.
|
109
|
+
def restart
|
110
|
+
@mutex.synchronize {
|
111
|
+
@thread.raise Restart.new
|
112
|
+
}
|
113
|
+
|
114
|
+
self
|
115
|
+
end
|
116
|
+
|
117
|
+
# Check if the every is running.
|
118
|
+
def running?
|
119
|
+
!cancelled?
|
120
|
+
end
|
121
|
+
|
122
|
+
# Check if the every is old, after the first #value call it becomes old,
|
123
|
+
# until another run of the block is gone)
|
124
|
+
def old?
|
125
|
+
@mutex.synchronize {
|
126
|
+
@old
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
# Gets the Time when the block was called.
|
131
|
+
def called_at
|
132
|
+
@mutex.synchronize {
|
133
|
+
@at
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
# Gets how many seconds are missing before another call.
|
138
|
+
def next_in
|
139
|
+
return if cancelled?
|
140
|
+
|
141
|
+
@mutex.synchronize {
|
142
|
+
@every - (Time.now - @at)
|
143
|
+
}
|
144
|
+
end
|
145
|
+
|
146
|
+
# Gets the current every value.
|
147
|
+
def value (timeout = nil)
|
148
|
+
@mutex.synchronize {
|
149
|
+
if @old
|
150
|
+
cond.wait(@mutex, *timeout)
|
151
|
+
end
|
152
|
+
|
153
|
+
@old = true
|
154
|
+
|
155
|
+
if @exception
|
156
|
+
raise @exception
|
157
|
+
else
|
158
|
+
@value
|
159
|
+
end
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
163
|
+
alias ~ value
|
164
|
+
|
165
|
+
# Gets the current every value, without blocking and waiting for the next
|
166
|
+
# call.
|
167
|
+
def value!
|
168
|
+
@mutex.synchronize {
|
169
|
+
@old = true
|
170
|
+
|
171
|
+
@value unless @exception
|
172
|
+
}
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
def cond?
|
177
|
+
instance_variable_defined? :@cond
|
178
|
+
end
|
179
|
+
|
180
|
+
def cond
|
181
|
+
@cond ||= ConditionVariable.new
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
class Thread
|
186
|
+
# Helper to create an every
|
187
|
+
def self.every (every, &block)
|
188
|
+
Thread::Every.new(every, &block)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
module Kernel
|
193
|
+
# Helper to create an every
|
194
|
+
def every (every, &block)
|
195
|
+
Thread::Every.new(every, &block)
|
196
|
+
end
|
197
|
+
end
|