thread 0.0.2 → 0.0.3
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.
- data/README.md +18 -2
- data/lib/thread/channel.rb +15 -0
- data/lib/thread/delay.rb +73 -0
- data/lib/thread/future.rb +88 -2
- data/lib/thread/pool.rb +58 -9
- data/lib/thread/promise.rb +8 -1
- data/lib/thread/recursive_mutex.rb +6 -0
- data/thread.gemspec +3 -3
- metadata +5 -3
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
thread - various extensions to the thread
|
2
|
-
|
1
|
+
thread - various extensions to the thread library
|
2
|
+
=================================================
|
3
3
|
|
4
4
|
Pool
|
5
5
|
====
|
@@ -108,3 +108,19 @@ puts ~future {
|
|
108
108
|
42
|
109
109
|
} # => 42
|
110
110
|
```
|
111
|
+
|
112
|
+
Delay
|
113
|
+
=====
|
114
|
+
A delay is kind of a promise, except the block is called when the value is
|
115
|
+
being accessed and the result is cached.
|
116
|
+
|
117
|
+
Example
|
118
|
+
-------
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
require 'thread/delay'
|
122
|
+
|
123
|
+
puts ~delay {
|
124
|
+
42
|
125
|
+
}
|
126
|
+
```
|
data/lib/thread/channel.rb
CHANGED
@@ -10,7 +10,12 @@
|
|
10
10
|
|
11
11
|
require 'thread'
|
12
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.
|
13
17
|
class Thread::Channel
|
18
|
+
# Create a channel with optional initial messages and optional channel guard.
|
14
19
|
def initialize (messages = [], &block)
|
15
20
|
@messages = []
|
16
21
|
@mutex = Mutex.new
|
@@ -22,6 +27,10 @@ class Thread::Channel
|
|
22
27
|
}
|
23
28
|
end
|
24
29
|
|
30
|
+
# Send a message to the channel.
|
31
|
+
#
|
32
|
+
# If there's a guard, the value is passed to it, if the guard returns a falsy value
|
33
|
+
# an ArgumentError exception is raised and the message is not sent.
|
25
34
|
def send (what)
|
26
35
|
if @check && !@check.call(what)
|
27
36
|
raise ArgumentError, 'guard mismatch'
|
@@ -35,6 +44,9 @@ class Thread::Channel
|
|
35
44
|
self
|
36
45
|
end
|
37
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.
|
38
50
|
def receive (&block)
|
39
51
|
message = nil
|
40
52
|
|
@@ -64,6 +76,9 @@ class Thread::Channel
|
|
64
76
|
message
|
65
77
|
end
|
66
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.
|
67
82
|
def receive! (&block)
|
68
83
|
if block
|
69
84
|
@messages.delete_at(@messages.find_index(&block))
|
data/lib/thread/delay.rb
ADDED
@@ -0,0 +1,73 @@
|
|
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
|
+
# A delay is an object that incapsulates a block which is called upon
|
12
|
+
# value retrieval, and its result cached.
|
13
|
+
class Thread::Delay
|
14
|
+
def initialize (&block)
|
15
|
+
@block = block
|
16
|
+
end
|
17
|
+
|
18
|
+
# Check if an exception has been raised.
|
19
|
+
def exception?
|
20
|
+
instance_variable_defined? :@exception
|
21
|
+
end
|
22
|
+
|
23
|
+
# Return the raised exception.
|
24
|
+
def exception
|
25
|
+
@exception
|
26
|
+
end
|
27
|
+
|
28
|
+
# Check if the delay has been called.
|
29
|
+
def delivered?
|
30
|
+
instance_variable_defined? :@value
|
31
|
+
end
|
32
|
+
|
33
|
+
alias realized? delivered?
|
34
|
+
|
35
|
+
# Get the value of the delay, if it's already been executed, return the
|
36
|
+
# cached result, otherwise execute the block and return the value.
|
37
|
+
#
|
38
|
+
# In case the block raises an exception, it will be raised, the exception is
|
39
|
+
# cached and will be raised every time you access the value.
|
40
|
+
def value
|
41
|
+
raise @exception if exception?
|
42
|
+
|
43
|
+
return @value if realized?
|
44
|
+
|
45
|
+
begin
|
46
|
+
@value = @block.call
|
47
|
+
rescue Exception => e
|
48
|
+
@exception = e
|
49
|
+
|
50
|
+
raise
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
alias ~ value
|
55
|
+
|
56
|
+
# Do the same as {#value}, but return nil in case of exception.
|
57
|
+
def value!
|
58
|
+
begin
|
59
|
+
value
|
60
|
+
rescue Exception
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
alias ! value!
|
66
|
+
end
|
67
|
+
|
68
|
+
module Kernel
|
69
|
+
# Helper to create a Thread::Delay
|
70
|
+
def delay (&block)
|
71
|
+
Thread::Delay.new(&block)
|
72
|
+
end
|
73
|
+
end
|
data/lib/thread/future.rb
CHANGED
@@ -10,12 +10,98 @@
|
|
10
10
|
|
11
11
|
require 'thread/promise'
|
12
12
|
|
13
|
-
|
13
|
+
# A future is an object that incapsulates a block which is called in a
|
14
|
+
# different thread, upon retrieval the caller gets blocked until the block has
|
15
|
+
# finished running, and its result is returned and cached.
|
16
|
+
class Thread::Future
|
14
17
|
def initialize (&block)
|
15
18
|
Thread.new {
|
16
|
-
|
19
|
+
begin
|
20
|
+
deliver block.call
|
21
|
+
rescue Exception => e
|
22
|
+
@exception = e
|
23
|
+
|
24
|
+
deliver nil
|
25
|
+
end
|
17
26
|
}
|
18
27
|
end
|
28
|
+
|
29
|
+
# Check if an exception has been raised.
|
30
|
+
def exception?
|
31
|
+
instance_variable_defined? :@exception
|
32
|
+
end
|
33
|
+
|
34
|
+
# Return the raised exception.
|
35
|
+
def exception
|
36
|
+
@exception
|
37
|
+
end
|
38
|
+
|
39
|
+
# Check if the future has been called.
|
40
|
+
def delivered?
|
41
|
+
instance_variable_defined? :@value
|
42
|
+
end
|
43
|
+
|
44
|
+
alias realized? delivered?
|
45
|
+
|
46
|
+
# Get the value of the future, if it's not finished running this call will block.
|
47
|
+
#
|
48
|
+
# In case the block raises an exception, it will be raised, the exception is cached
|
49
|
+
# and will be raised every time you access the value.
|
50
|
+
def value
|
51
|
+
raise @exception if exception?
|
52
|
+
|
53
|
+
return @value if delivered?
|
54
|
+
|
55
|
+
mutex.synchronize {
|
56
|
+
cond.wait(mutex)
|
57
|
+
}
|
58
|
+
|
59
|
+
if exception?
|
60
|
+
raise @exception
|
61
|
+
else
|
62
|
+
@value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
alias ~ value
|
67
|
+
|
68
|
+
# Do the same as {#value}, but return nil in case of exception.
|
69
|
+
def value!
|
70
|
+
begin
|
71
|
+
value
|
72
|
+
rescue Exception
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
alias ! value!
|
78
|
+
|
79
|
+
private
|
80
|
+
def deliver (value)
|
81
|
+
return if delivered?
|
82
|
+
|
83
|
+
@value = value
|
84
|
+
|
85
|
+
if cond?
|
86
|
+
mutex.synchronize {
|
87
|
+
cond.broadcast
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
def cond?
|
95
|
+
instance_variable_defined? :@cond
|
96
|
+
end
|
97
|
+
|
98
|
+
def cond
|
99
|
+
@cond ||= ConditionVariable.new
|
100
|
+
end
|
101
|
+
|
102
|
+
def mutex
|
103
|
+
@mutex ||= Mutex.new
|
104
|
+
end
|
19
105
|
end
|
20
106
|
|
21
107
|
module Kernel
|
data/lib/thread/pool.rb
CHANGED
@@ -10,17 +10,27 @@
|
|
10
10
|
|
11
11
|
require 'thread'
|
12
12
|
|
13
|
+
# A pool is a container of a limited amount of threads to which you can add
|
14
|
+
# tasks to run.
|
15
|
+
#
|
16
|
+
# This is usually more performant and less memory intensive than creating a
|
17
|
+
# new thread for every task.
|
13
18
|
class Thread::Pool
|
19
|
+
# A task incapsulates a block being ran by the pool and the arguments to pass
|
20
|
+
# to it.
|
14
21
|
class Task
|
15
22
|
Timeout = Class.new(Exception)
|
16
23
|
Asked = Class.new(Exception)
|
17
24
|
|
18
25
|
attr_reader :pool, :timeout, :exception, :thread, :started_at
|
19
26
|
|
27
|
+
# Create a task in the given pool which will pass the arguments to the
|
28
|
+
# block.
|
20
29
|
def initialize (pool, *args, &block)
|
21
|
-
@pool
|
22
|
-
@arguments
|
23
|
-
@block
|
30
|
+
@pool = pool
|
31
|
+
@arguments = args
|
32
|
+
@block = block
|
33
|
+
|
24
34
|
@running = false
|
25
35
|
@finished = false
|
26
36
|
@timedout = false
|
@@ -32,6 +42,7 @@ class Thread::Pool
|
|
32
42
|
def timeout?; @timedout; end
|
33
43
|
def terminated?; @terminated; end
|
34
44
|
|
45
|
+
# Execute the task in the given thread.
|
35
46
|
def execute (thread)
|
36
47
|
return if terminated? || running? || finished?
|
37
48
|
|
@@ -39,7 +50,7 @@ class Thread::Pool
|
|
39
50
|
@running = true
|
40
51
|
@started_at = Time.now
|
41
52
|
|
42
|
-
pool.wake_up_timeout
|
53
|
+
pool.__send__.wake_up_timeout
|
43
54
|
|
44
55
|
begin
|
45
56
|
@block.call(*@arguments)
|
@@ -58,6 +69,7 @@ class Thread::Pool
|
|
58
69
|
@thread = nil
|
59
70
|
end
|
60
71
|
|
72
|
+
# Terminate the exception with an optionally given exception.
|
61
73
|
def terminate! (exception = Asked)
|
62
74
|
return if terminated? || finished? || timeout?
|
63
75
|
|
@@ -68,10 +80,12 @@ class Thread::Pool
|
|
68
80
|
@thread.raise exception
|
69
81
|
end
|
70
82
|
|
83
|
+
# Force the task to timeout.
|
71
84
|
def timeout!
|
72
85
|
terminate! Timeout
|
73
86
|
end
|
74
87
|
|
88
|
+
# Timeout the task after the given time.
|
75
89
|
def timeout_after (time)
|
76
90
|
@timeout = time
|
77
91
|
|
@@ -83,6 +97,13 @@ class Thread::Pool
|
|
83
97
|
|
84
98
|
attr_reader :min, :max, :spawned
|
85
99
|
|
100
|
+
# Create the pool with minimum and maximum threads.
|
101
|
+
#
|
102
|
+
# The pool will start with the minimum amount of threads created and will
|
103
|
+
# spawn new threads until the max is reached in case of need.
|
104
|
+
#
|
105
|
+
# A default block can be passed, which will be used to {#process} the passed
|
106
|
+
# data.
|
86
107
|
def initialize (min, max = nil, &block)
|
87
108
|
@min = min
|
88
109
|
@max = max || min
|
@@ -108,12 +129,26 @@ class Thread::Pool
|
|
108
129
|
}
|
109
130
|
end
|
110
131
|
|
132
|
+
# Check if the pool has been shut down.
|
111
133
|
def shutdown?; !!@shutdown; end
|
112
134
|
|
113
|
-
|
114
|
-
def auto_trim
|
115
|
-
|
135
|
+
# Check if auto trimming is enabled.
|
136
|
+
def auto_trim?
|
137
|
+
@auto_trim
|
138
|
+
end
|
139
|
+
|
140
|
+
# Enable auto trimming, unneeded threads will be deleted until the minimum
|
141
|
+
# is reached.
|
142
|
+
def auto_trim!
|
143
|
+
@auto_trim = true
|
144
|
+
end
|
116
145
|
|
146
|
+
# Disable auto trimming.
|
147
|
+
def no_auto_trim!
|
148
|
+
@auto_trim = false
|
149
|
+
end
|
150
|
+
|
151
|
+
# Resize the pool with the passed arguments.
|
117
152
|
def resize (min, max = nil)
|
118
153
|
@min = min
|
119
154
|
@max = max || min
|
@@ -121,12 +156,18 @@ class Thread::Pool
|
|
121
156
|
trim!
|
122
157
|
end
|
123
158
|
|
159
|
+
# Get the amount of tasks that still have to be run.
|
124
160
|
def backlog
|
125
161
|
@mutex.synchronize {
|
126
162
|
@todo.length
|
127
163
|
}
|
128
164
|
end
|
129
165
|
|
166
|
+
# Add a task to the pool which will execute the block with the given
|
167
|
+
# argument.
|
168
|
+
#
|
169
|
+
# If no block is passed the default block will be used if present, an
|
170
|
+
# ArgumentError will be raised otherwise.
|
130
171
|
def process (*args, &block)
|
131
172
|
unless block || @block
|
132
173
|
raise ArgumentError, 'you must pass a block'
|
@@ -151,6 +192,8 @@ class Thread::Pool
|
|
151
192
|
|
152
193
|
alias << process
|
153
194
|
|
195
|
+
# Trim the unused threads, if forced threads will be trimmed even if there
|
196
|
+
# are tasks waiting.
|
154
197
|
def trim (force = false)
|
155
198
|
@mutex.synchronize {
|
156
199
|
if (force || @waiting > 0) && @spawned - @trim_requests > @min
|
@@ -162,10 +205,12 @@ class Thread::Pool
|
|
162
205
|
self
|
163
206
|
end
|
164
207
|
|
208
|
+
# Force #{trim}.
|
165
209
|
def trim!
|
166
210
|
trim true
|
167
211
|
end
|
168
212
|
|
213
|
+
# Shut down the pool instantly without finishing to execute tasks.
|
169
214
|
def shutdown!
|
170
215
|
@mutex.synchronize {
|
171
216
|
@shutdown = :now
|
@@ -177,6 +222,7 @@ class Thread::Pool
|
|
177
222
|
self
|
178
223
|
end
|
179
224
|
|
225
|
+
# Shut down the pool, it will block until all tasks have finished running.
|
180
226
|
def shutdown
|
181
227
|
@mutex.synchronize {
|
182
228
|
@shutdown = :nicely
|
@@ -196,12 +242,14 @@ class Thread::Pool
|
|
196
242
|
self
|
197
243
|
end
|
198
244
|
|
245
|
+
# Join on all threads in the pool.
|
199
246
|
def join
|
200
247
|
@workers.first.join until @workers.empty?
|
201
248
|
|
202
249
|
self
|
203
250
|
end
|
204
251
|
|
252
|
+
# Define a timeout for a task.
|
205
253
|
def timeout_for (task, timeout)
|
206
254
|
unless @timeout
|
207
255
|
spawn_timeout_thread
|
@@ -214,6 +262,7 @@ class Thread::Pool
|
|
214
262
|
}
|
215
263
|
end
|
216
264
|
|
265
|
+
# Shutdown the pool after a given amount of time.
|
217
266
|
def shutdown_after (timeout)
|
218
267
|
Thread.new {
|
219
268
|
sleep timeout
|
@@ -224,13 +273,13 @@ class Thread::Pool
|
|
224
273
|
self
|
225
274
|
end
|
226
275
|
|
276
|
+
private
|
227
277
|
def wake_up_timeout
|
228
278
|
if defined? @pipes
|
229
279
|
@pipes.last.write_nonblock 'x' rescue nil
|
230
280
|
end
|
231
281
|
end
|
232
282
|
|
233
|
-
private
|
234
283
|
def spawn_thread
|
235
284
|
@spawned += 1
|
236
285
|
|
@@ -281,7 +330,7 @@ private
|
|
281
330
|
@timeout = Thread.new {
|
282
331
|
loop do
|
283
332
|
now = Time.now
|
284
|
-
timeout = @timeouts.map {|task,
|
333
|
+
timeout = @timeouts.map {|task, time|
|
285
334
|
next unless task.started_at
|
286
335
|
|
287
336
|
now - task.started_at + task.timeout
|
data/lib/thread/promise.rb
CHANGED
@@ -10,11 +10,16 @@
|
|
10
10
|
|
11
11
|
require 'thread'
|
12
12
|
|
13
|
+
# A promise is an object that lets you wait for a value to be delivered to it.
|
13
14
|
class Thread::Promise
|
15
|
+
# Check if a value has been delivered.
|
14
16
|
def delivered?
|
15
17
|
instance_variable_defined? :@value
|
16
18
|
end
|
17
19
|
|
20
|
+
alias realized? delivered?
|
21
|
+
|
22
|
+
# Deliver a value.
|
18
23
|
def deliver (value)
|
19
24
|
return if delivered?
|
20
25
|
|
@@ -31,6 +36,8 @@ class Thread::Promise
|
|
31
36
|
|
32
37
|
alias << deliver
|
33
38
|
|
39
|
+
# Get the value that's been delivered, if none has been delivered yet the call
|
40
|
+
# will block until one is delivered.
|
34
41
|
def value
|
35
42
|
return @value if delivered?
|
36
43
|
|
@@ -38,7 +45,7 @@ class Thread::Promise
|
|
38
45
|
cond.wait(mutex)
|
39
46
|
}
|
40
47
|
|
41
|
-
|
48
|
+
@value
|
42
49
|
end
|
43
50
|
|
44
51
|
alias ~ value
|
@@ -10,6 +10,10 @@
|
|
10
10
|
|
11
11
|
require 'thread'
|
12
12
|
|
13
|
+
# A recursive mutex lets you lock in various threads recursively, allowing
|
14
|
+
# you to do multiple locks one inside another.
|
15
|
+
#
|
16
|
+
# You really shouldn't use this, but in some cases it makes your life easier.
|
13
17
|
class RecursiveMutex < Mutex
|
14
18
|
def initialize
|
15
19
|
@threads = Hash.new { |h, k| h[k] = 0 }
|
@@ -17,6 +21,7 @@ class RecursiveMutex < Mutex
|
|
17
21
|
super
|
18
22
|
end
|
19
23
|
|
24
|
+
# Lock the mutex.
|
20
25
|
def lock
|
21
26
|
@thread[Thread.current] += 1
|
22
27
|
|
@@ -25,6 +30,7 @@ class RecursiveMutex < Mutex
|
|
25
30
|
end
|
26
31
|
end
|
27
32
|
|
33
|
+
# Unlock the mutex.
|
28
34
|
def unlock
|
29
35
|
@thread[Thread.current] -= 1
|
30
36
|
|
data/thread.gemspec
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
Gem::Specification.new {|s|
|
2
2
|
s.name = 'thread'
|
3
|
-
s.version = '0.0.
|
3
|
+
s.version = '0.0.3'
|
4
4
|
s.author = 'meh.'
|
5
5
|
s.email = 'meh@schizofreni.co'
|
6
6
|
s.homepage = 'http://github.com/meh/ruby-thread'
|
7
7
|
s.platform = Gem::Platform::RUBY
|
8
|
-
s.summary = 'Various extensions to the base thread
|
9
|
-
s.description = 'Includes a thread pool, message passing capabilities, a recursive mutex, promise and
|
8
|
+
s.summary = 'Various extensions to the base thread library.'
|
9
|
+
s.description = 'Includes a thread pool, message passing capabilities, a recursive mutex, promise, future and delay.'
|
10
10
|
|
11
11
|
s.files = `git ls-files`.split("\n")
|
12
12
|
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: thread
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -12,7 +12,7 @@ cert_chain: []
|
|
12
12
|
date: 2013-02-25 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Includes a thread pool, message passing capabilities, a recursive mutex,
|
15
|
-
promise and
|
15
|
+
promise, future and delay.
|
16
16
|
email: meh@schizofreni.co
|
17
17
|
executables: []
|
18
18
|
extensions: []
|
@@ -20,6 +20,7 @@ extra_rdoc_files: []
|
|
20
20
|
files:
|
21
21
|
- README.md
|
22
22
|
- lib/thread/channel.rb
|
23
|
+
- lib/thread/delay.rb
|
23
24
|
- lib/thread/future.rb
|
24
25
|
- lib/thread/pool.rb
|
25
26
|
- lib/thread/promise.rb
|
@@ -48,5 +49,6 @@ rubyforge_project:
|
|
48
49
|
rubygems_version: 1.8.23
|
49
50
|
signing_key:
|
50
51
|
specification_version: 3
|
51
|
-
summary: Various extensions to the base thread
|
52
|
+
summary: Various extensions to the base thread library.
|
52
53
|
test_files: []
|
54
|
+
has_rdoc:
|