vinted-thread 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|