thread 0.0.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.
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ thread - various extensions to the thread stdlib
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.new(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.new
45
+ channel.send 'wat'
46
+ channel.receive # => 'wat'
47
+
48
+ channel = Thread::Channel.new { |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
+ ```
@@ -0,0 +1,74 @@
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
+ class Thread::Channel
14
+ def initialize (messages = [], &block)
15
+ @messages = []
16
+ @mutex = Mutex.new
17
+ @cond = ConditionVariable.new
18
+ @check = block
19
+
20
+ messages.each {|o|
21
+ send o
22
+ }
23
+ end
24
+
25
+ def send (what)
26
+ if @check && !@check.call(what)
27
+ raise ArgumentError, 'guard mismatch'
28
+ end
29
+
30
+ @mutex.synchronize {
31
+ @messages << what
32
+ @cond.broadcast
33
+ }
34
+
35
+ self
36
+ end
37
+
38
+ def receive (&block)
39
+ message = nil
40
+
41
+ if block
42
+ found = false
43
+
44
+ until found
45
+ @mutex.synchronize {
46
+ if index = @messages.find_index(&block)
47
+ message = @messages.delete_at(index)
48
+ found = true
49
+ else
50
+ @cond.wait @mutex
51
+ end
52
+ }
53
+ end
54
+ else
55
+ @mutex.synchronize {
56
+ if @messages.empty?
57
+ @cond.wait @mutex
58
+ end
59
+
60
+ message = @messages.shift
61
+ }
62
+ end
63
+
64
+ message
65
+ end
66
+
67
+ def receive! (&block)
68
+ if block
69
+ @messages.delete_at(@messages.find_index(&block))
70
+ else
71
+ @messages.shift
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,309 @@
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
+ class Thread::Pool
14
+ class Task
15
+ Timeout = Class.new(Exception)
16
+ Asked = Class.new(Exception)
17
+
18
+ attr_reader :pool, :timeout, :exception, :thread, :started_at
19
+
20
+ def initialize (pool, *args, &block)
21
+ @pool = pool
22
+ @arguments = args
23
+ @block = block
24
+ end
25
+
26
+ def running?; @running; end
27
+ def finished?; @finished; end
28
+ def timeout?; @timedout; end
29
+ def terminated?; @terminated; end
30
+
31
+ def execute (thread)
32
+ return if terminated? || running? || finished?
33
+
34
+ @thread = thread
35
+ @running = true
36
+ @started_at = Time.now
37
+
38
+ pool.wake_up_timeout
39
+
40
+ begin
41
+ @block.call(*@arguments)
42
+ rescue Exception => e
43
+ if e.is_a? Timeout
44
+ @timedout = true
45
+ elsif e.is_a? Asked
46
+ return
47
+ else
48
+ @exception = reason
49
+ end
50
+ end
51
+
52
+ @running = false
53
+ @finished = true
54
+ @thread = nil
55
+ end
56
+
57
+ def terminate! (exception = Asked)
58
+ return if terminated? || finished? || timeout?
59
+
60
+ @terminated = true
61
+
62
+ return unless running?
63
+
64
+ @thread.raise exception
65
+ end
66
+
67
+ def timeout!
68
+ terminate! Timeout
69
+ end
70
+
71
+ def timeout_after (time)
72
+ @timeout = time
73
+
74
+ pool.timeout_for self, time
75
+
76
+ self
77
+ end
78
+ end
79
+
80
+ attr_reader :min, :max, :spawned
81
+
82
+ def initialize (min, max = nil, &block)
83
+ @min = min
84
+ @max = max || min
85
+ @block = block
86
+
87
+ @cond = ConditionVariable.new
88
+ @mutex = Mutex.new
89
+
90
+ @todo = []
91
+ @workers = []
92
+ @timeouts = {}
93
+
94
+ @spawned = 0
95
+ @waiting = 0
96
+ @shutdown = false
97
+ @trim_requests = 0
98
+ @auto_trim = false
99
+
100
+ @mutex.synchronize {
101
+ min.times {
102
+ spawn_thread
103
+ }
104
+ }
105
+ end
106
+
107
+ def shutdown?; !!@shutdown; end
108
+
109
+ def auto_trim?; @auto_trim; end
110
+ def auto_trim!; @auto_trim = true; end
111
+ def no_auto_trim!; @auto_trim = false; end
112
+
113
+ def resize (min, max = nil)
114
+ @min = min
115
+ @max = max || min
116
+
117
+ trim!
118
+ end
119
+
120
+ def backlog
121
+ @mutex.synchronize {
122
+ @todo.length
123
+ }
124
+ end
125
+
126
+ def process (*args, &block)
127
+ unless block || @block
128
+ raise ArgumentError, 'you must pass a block'
129
+ end
130
+
131
+ task = Task.new(self, *args, &(block || @block))
132
+
133
+ @mutex.synchronize {
134
+ raise 'unable to add work while shutting down' if shutdown?
135
+
136
+ @todo << task
137
+
138
+ if @waiting == 0 && @spawned < @max
139
+ spawn_thread
140
+ end
141
+
142
+ @cond.signal
143
+ }
144
+
145
+ task
146
+ end
147
+
148
+ alias << process
149
+
150
+ def trim (force = false)
151
+ @mutex.synchronize {
152
+ if (force || @waiting > 0) && @spawned - @trim_requests > @min
153
+ @trim_requests -= 1
154
+ @cond.signal
155
+ end
156
+ }
157
+
158
+ self
159
+ end
160
+
161
+ def trim!
162
+ trim true
163
+ end
164
+
165
+ def shutdown!
166
+ @mutex.synchronize {
167
+ @shutdown = :now
168
+ @cond.broadcast
169
+ }
170
+
171
+ wake_up_timeout
172
+
173
+ self
174
+ end
175
+
176
+ def shutdown
177
+ @mutex.synchronize {
178
+ @shutdown = :nicely
179
+ @cond.broadcast
180
+ }
181
+
182
+ join
183
+
184
+ if @timeout
185
+ @shutdown = :now
186
+
187
+ wake_up_timeout
188
+
189
+ @timeout.join
190
+ end
191
+
192
+ self
193
+ end
194
+
195
+ def join
196
+ @workers.first.join until @workers.empty?
197
+
198
+ self
199
+ end
200
+
201
+ def timeout_for (task, timeout)
202
+ unless @timeout
203
+ spawn_timeout_thread
204
+ end
205
+
206
+ @mutex.synchronize {
207
+ @timeouts[task] = timeout
208
+
209
+ wake_up_timeout
210
+ }
211
+ end
212
+
213
+ def shutdown_after (timeout)
214
+ Thread.new {
215
+ sleep timeout
216
+
217
+ shutdown
218
+ }
219
+
220
+ self
221
+ end
222
+
223
+ def wake_up_timeout
224
+ if @pipes
225
+ @pipes.last.write_nonblock 'x' rescue nil
226
+ end
227
+ end
228
+
229
+ private
230
+ def spawn_thread
231
+ @spawned += 1
232
+
233
+ thread = Thread.new {
234
+ loop do
235
+ task = @mutex.synchronize {
236
+ if @todo.empty?
237
+ while @todo.empty?
238
+ if @trim_requests > 0
239
+ @trim_requests -= 1
240
+
241
+ break
242
+ end
243
+
244
+ break if shutdown?
245
+
246
+ @waiting += 1
247
+ @cond.wait @mutex
248
+ @waiting -= 1
249
+
250
+ break !shutdown?
251
+ end or break
252
+ end
253
+
254
+ @todo.shift
255
+ } or break
256
+
257
+ task.execute(thread)
258
+
259
+ break if @shutdown == :now
260
+
261
+ trim if auto_trim? && @spawned > @min
262
+ end
263
+
264
+ @mutex.synchronize {
265
+ @spawned -= 1
266
+ @workers.delete thread
267
+ }
268
+ }
269
+
270
+ @workers << thread
271
+
272
+ thread
273
+ end
274
+
275
+ def spawn_timeout_thread
276
+ @pipes = IO.pipe
277
+ @timeout = Thread.new {
278
+ loop do
279
+ now = Time.now
280
+ timeout = @timeouts.map {|task, timeout|
281
+ next unless task.started_at
282
+
283
+ now - task.started_at + task.timeout
284
+ }.compact.min unless @timeouts.empty?
285
+
286
+ readable, = IO.select([@pipes.first], nil, nil, timeout)
287
+
288
+ break if @shutdown == :now
289
+
290
+ if readable && !readable.empty?
291
+ readable.first.read_nonblock 1024
292
+ end
293
+
294
+ now = Time.now
295
+ @timeouts.each {|task, time|
296
+ next if !task.started_at || task.terminated? || task.finished?
297
+
298
+ if now > task.started_at + task.timeout
299
+ task.timeout!
300
+ end
301
+ }
302
+
303
+ @timeouts.reject! { |task, _| task.terminated? || task.finished? }
304
+
305
+ break if @shutdown == :now
306
+ end
307
+ }
308
+ end
309
+ end
@@ -0,0 +1,37 @@
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
+ class RecursiveMutex < Mutex
14
+ def initialize
15
+ @threads = Hash.new { |h, k| h[k] = 0 }
16
+
17
+ super
18
+ end
19
+
20
+ def lock
21
+ @thread[Thread.current] += 1
22
+
23
+ if @thread[Thread.current] == 1
24
+ super
25
+ end
26
+ end
27
+
28
+ def unlock
29
+ @thread[Thread.current] -= 1
30
+
31
+ if @thread[Thread.current] == 0
32
+ @thread.delete(Thread.current)
33
+
34
+ super
35
+ end
36
+ end
37
+ end
data/thread.gemspec ADDED
@@ -0,0 +1,14 @@
1
+ Gem::Specification.new {|s|
2
+ s.name = 'thread'
3
+ s.version = '0.0.1'
4
+ s.author = 'meh.'
5
+ s.email = 'meh@schizofreni.co'
6
+ s.homepage = 'http://github.com/meh/ruby-thread'
7
+ s.platform = Gem::Platform::RUBY
8
+ s.summary = 'Various extensions to the base thread stdlib.'
9
+
10
+ s.files = `git ls-files`.split("\n")
11
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
12
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
13
+ s.require_paths = ['lib']
14
+ }
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: thread
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - meh.
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-22 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description:
15
+ email: meh@schizofreni.co
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - README.md
21
+ - lib/thread/channel.rb
22
+ - lib/thread/pool.rb
23
+ - lib/thread/recursive_mutex.rb
24
+ - thread.gemspec
25
+ homepage: http://github.com/meh/ruby-thread
26
+ licenses: []
27
+ post_install_message:
28
+ rdoc_options: []
29
+ require_paths:
30
+ - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ none: false
33
+ requirements:
34
+ - - ! '>='
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ required_rubygems_version: !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ! '>='
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubyforge_project:
45
+ rubygems_version: 1.8.24
46
+ signing_key:
47
+ specification_version: 3
48
+ summary: Various extensions to the base thread stdlib.
49
+ test_files: []