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 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: