thread 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []