thread 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
- thread - various extensions to the thread stdlib
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
+ ```
@@ -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))
@@ -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
- class Thread::Future < Thread::Promise
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
- deliver block.call
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 = pool
22
- @arguments = args
23
- @block = 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
- def auto_trim?; @auto_trim; end
114
- def auto_trim!; @auto_trim = true; end
115
- def no_auto_trim!; @auto_trim = false; end
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, timeout|
333
+ timeout = @timeouts.map {|task, time|
285
334
  next unless task.started_at
286
335
 
287
336
  now - task.started_at + task.timeout
@@ -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
- return @value
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.2'
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 stdlib.'
9
- s.description = 'Includes a thread pool, message passing capabilities, a recursive mutex, promise and future.'
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.2
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 future.
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 stdlib.
52
+ summary: Various extensions to the base thread library.
52
53
  test_files: []
54
+ has_rdoc: