seekingalpha_thread 1.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.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/.travis.yml +11 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +13 -0
- data/README.md +229 -0
- data/Rakefile +6 -0
- data/lib/thread/channel.rb +109 -0
- data/lib/thread/delay.rb +94 -0
- data/lib/thread/every.rb +197 -0
- data/lib/thread/future.rb +164 -0
- data/lib/thread/pipe.rb +119 -0
- data/lib/thread/pool.rb +488 -0
- data/lib/thread/process.rb +71 -0
- data/lib/thread/promise.rb +83 -0
- data/lib/thread/recursive_mutex.rb +38 -0
- data/spec/thread/channel_spec.rb +39 -0
- data/spec/thread/delay_spec.rb +11 -0
- data/spec/thread/every_spec.rb +9 -0
- data/spec/thread/future_spec.rb +34 -0
- data/spec/thread/pipe_spec.rb +23 -0
- data/spec/thread/pool_spec.rb +86 -0
- data/spec/thread/promise_spec.rb +35 -0
- data/thread.gemspec +22 -0
- metadata +105 -0
@@ -0,0 +1,71 @@
|
|
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/channel'
|
12
|
+
|
13
|
+
# A process should only interact with the outside through messages, it still
|
14
|
+
# uses a thread, but it should make it safer to use than sharing and locks.
|
15
|
+
class Thread::Process
|
16
|
+
def self.all
|
17
|
+
@@processes ||= {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.register(name, process)
|
21
|
+
all[name] = process
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.unregister(name)
|
25
|
+
all.delete(name)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.[](name)
|
29
|
+
all[name]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Create a new process executing the block.
|
33
|
+
def initialize(&block)
|
34
|
+
@channel = Thread::Channel.new
|
35
|
+
|
36
|
+
Thread.new {
|
37
|
+
instance_eval(&block)
|
38
|
+
|
39
|
+
@channel = nil
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
# Send a message to the process.
|
44
|
+
def send(what)
|
45
|
+
unless @channel
|
46
|
+
raise RuntimeError, 'the process has terminated'
|
47
|
+
end
|
48
|
+
|
49
|
+
@channel.send(what)
|
50
|
+
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
alias << send
|
55
|
+
|
56
|
+
private
|
57
|
+
def receive
|
58
|
+
@channel.receive
|
59
|
+
end
|
60
|
+
|
61
|
+
def receive!
|
62
|
+
@channel.receive!
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Thread
|
67
|
+
# Helper to create a process.
|
68
|
+
def self.process(&block)
|
69
|
+
Thread::Process.new(&block)
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,83 @@
|
|
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
|
+
# A promise is an object that lets you wait for a value to be delivered to it.
|
14
|
+
class Thread::Promise
|
15
|
+
# Create a promise.
|
16
|
+
def initialize
|
17
|
+
@mutex = Mutex.new
|
18
|
+
end
|
19
|
+
|
20
|
+
# Check if a value has been delivered.
|
21
|
+
def delivered?
|
22
|
+
@mutex.synchronize {
|
23
|
+
instance_variable_defined? :@value
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
alias realized? delivered?
|
28
|
+
|
29
|
+
# Deliver a value.
|
30
|
+
def deliver(value)
|
31
|
+
return self if delivered?
|
32
|
+
|
33
|
+
@mutex.synchronize {
|
34
|
+
@value = value
|
35
|
+
|
36
|
+
cond.broadcast if cond?
|
37
|
+
}
|
38
|
+
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
alias << deliver
|
43
|
+
|
44
|
+
# Get the value that's been delivered, if none has been delivered yet the call
|
45
|
+
# will block until one is delivered.
|
46
|
+
#
|
47
|
+
# An optional timeout can be passed which will return nil if nothing has been
|
48
|
+
# delivered.
|
49
|
+
def value(timeout = nil)
|
50
|
+
return @value if delivered?
|
51
|
+
|
52
|
+
@mutex.synchronize {
|
53
|
+
cond.wait(@mutex, *timeout)
|
54
|
+
}
|
55
|
+
|
56
|
+
return @value if delivered?
|
57
|
+
end
|
58
|
+
|
59
|
+
alias ~ value
|
60
|
+
|
61
|
+
private
|
62
|
+
def cond?
|
63
|
+
instance_variable_defined? :@cond
|
64
|
+
end
|
65
|
+
|
66
|
+
def cond
|
67
|
+
@cond ||= ConditionVariable.new
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class Thread
|
72
|
+
# Helper method to create a promise.
|
73
|
+
def self.promise
|
74
|
+
Thread::Promise.new
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
module Kernel
|
79
|
+
# Helper method to create a promise.
|
80
|
+
def promise
|
81
|
+
Thread::Promise.new
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,38 @@
|
|
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
|
+
# 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.
|
17
|
+
class RecursiveMutex < Mutex
|
18
|
+
def initialize
|
19
|
+
@threads_lock = Mutex.new
|
20
|
+
@threads = Hash.new { |h, k| h[k] = 0 }
|
21
|
+
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
# Lock the mutex.
|
26
|
+
def lock
|
27
|
+
super if @threads_lock.synchronize{ (@threads[Thread.current] += 1) == 1 }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Unlock the mutex.
|
31
|
+
def unlock
|
32
|
+
if @threads_lock.synchronize{ (@threads[Thread.current] -= 1) == 0 }
|
33
|
+
@threads.delete(Thread.current)
|
34
|
+
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'thread/channel'
|
2
|
+
|
3
|
+
describe Thread::Channel do
|
4
|
+
it 'receives in the proper order' do
|
5
|
+
ch = Thread.channel
|
6
|
+
ch.send 'lol'
|
7
|
+
ch.send 'wut'
|
8
|
+
|
9
|
+
expect(ch.receive).to eq('lol')
|
10
|
+
expect(ch.receive).to eq('wut')
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'receives with constraints properly' do
|
14
|
+
ch = Thread.channel
|
15
|
+
ch.send 'lol'
|
16
|
+
ch.send 'wut'
|
17
|
+
|
18
|
+
expect(ch.receive { |v| v == 'wut' }).to eq('wut')
|
19
|
+
expect(ch.receive).to eq('lol')
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'receives nil when using non blocking mode and the channel is empty' do
|
23
|
+
ch = Thread.channel
|
24
|
+
|
25
|
+
expect(ch.receive!).to be_nil
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'guards sending properly' do
|
29
|
+
ch = Thread.channel { |v| v.is_a? Integer }
|
30
|
+
|
31
|
+
expect {
|
32
|
+
ch.send 23
|
33
|
+
}.to_not raise_error
|
34
|
+
|
35
|
+
expect {
|
36
|
+
ch.send 'lol'
|
37
|
+
}.to raise_error
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'thread/future'
|
2
|
+
|
3
|
+
describe Thread::Future do
|
4
|
+
it 'delivers a value properly' do
|
5
|
+
f = Thread.future {
|
6
|
+
sleep 0.02
|
7
|
+
42
|
8
|
+
}
|
9
|
+
|
10
|
+
expect(f.value).to eq(42)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'properly checks if anything has been delivered' do
|
14
|
+
f = Thread.future {
|
15
|
+
sleep 0.02
|
16
|
+
|
17
|
+
42
|
18
|
+
}
|
19
|
+
|
20
|
+
expect(f.delivered?).to eq(false)
|
21
|
+
sleep 0.03
|
22
|
+
expect(f.delivered?).to eq(true)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'does not block when a timeout is passed' do
|
26
|
+
f = Thread.future {
|
27
|
+
sleep 0.02
|
28
|
+
|
29
|
+
42
|
30
|
+
}
|
31
|
+
|
32
|
+
expect(f.value(0)).to be_nil
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'thread/pipe'
|
2
|
+
|
3
|
+
describe Thread::Pipe do
|
4
|
+
it 'handles passing properly' do
|
5
|
+
p = Thread |-> d { d * 2 } |-> d { d * 4 }
|
6
|
+
|
7
|
+
p << 2
|
8
|
+
p << 4
|
9
|
+
|
10
|
+
expect(p.deq).to eq(16)
|
11
|
+
expect(p.deq).to eq(32)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'empty works properly' do
|
15
|
+
p = Thread |-> d { sleep 0.02; d * 2 } |-> d { d * 4 }
|
16
|
+
|
17
|
+
expect(p.empty?).to be(true)
|
18
|
+
p.enq 42
|
19
|
+
expect(p.empty?).to be(false)
|
20
|
+
p.deq
|
21
|
+
expect(p.empty?).to be(true)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'thread/pool'
|
2
|
+
|
3
|
+
describe Thread::Pool do
|
4
|
+
it 'creates a new pool with the given amount of threads' do
|
5
|
+
pool = Thread.pool(4)
|
6
|
+
|
7
|
+
expect(pool.spawned).to eq(4)
|
8
|
+
|
9
|
+
pool.shutdown
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'creates a new pool with the given amount of threads without spawning more than min' do
|
13
|
+
pool = Thread.pool(4, 8)
|
14
|
+
|
15
|
+
expect(pool.spawned).to eq(4)
|
16
|
+
|
17
|
+
pool.shutdown
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'creates a new pool with a huge amount of threads that will handle Thread Error exception' do
|
21
|
+
pool = Thread.pool(3000)
|
22
|
+
|
23
|
+
expect(pool.spawned).to be<=2500
|
24
|
+
|
25
|
+
pool.shutdown
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'properly reports the backlog length' do
|
29
|
+
pool = Thread.pool(2)
|
30
|
+
|
31
|
+
pool.process { sleep 0.5 }
|
32
|
+
pool.process { sleep 0.5 }
|
33
|
+
pool.process { sleep 0.5 }
|
34
|
+
|
35
|
+
sleep 0.25
|
36
|
+
|
37
|
+
expect(pool.backlog).to eq(1)
|
38
|
+
|
39
|
+
pool.shutdown
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'properly reports the pool is done' do
|
43
|
+
pool = Thread.pool(2)
|
44
|
+
|
45
|
+
pool.process { sleep 0.25 }
|
46
|
+
pool.process { sleep 0.25 }
|
47
|
+
pool.process { sleep 0.25 }
|
48
|
+
|
49
|
+
expect(pool.done?).to be(false)
|
50
|
+
|
51
|
+
sleep 0.75
|
52
|
+
|
53
|
+
expect(pool.done?).to be(true)
|
54
|
+
|
55
|
+
pool.shutdown
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'properly reports the pool is idle' do
|
59
|
+
pool = Thread.pool(2)
|
60
|
+
|
61
|
+
pool.process { sleep 0.25 }
|
62
|
+
pool.process { sleep 0.5 }
|
63
|
+
|
64
|
+
expect(pool.idle?).to be(false)
|
65
|
+
|
66
|
+
sleep 0.30
|
67
|
+
|
68
|
+
expect(pool.idle?).to be(true)
|
69
|
+
|
70
|
+
pool.shutdown
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'properly shutdowns the pool' do
|
74
|
+
result = []
|
75
|
+
pool = Thread.pool(4)
|
76
|
+
|
77
|
+
pool.process { sleep 0.1; result << 1 }
|
78
|
+
pool.process { sleep 0.2; result << 2 }
|
79
|
+
pool.process { sleep 0.3; result << 3 }
|
80
|
+
pool.process { sleep 0.4; result << 4 }
|
81
|
+
|
82
|
+
pool.shutdown
|
83
|
+
|
84
|
+
expect(result).to eq([1, 2, 3, 4])
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'thread/promise'
|
2
|
+
|
3
|
+
describe Thread::Promise do
|
4
|
+
it 'delivers a value properly' do
|
5
|
+
p = Thread.promise
|
6
|
+
|
7
|
+
Thread.new {
|
8
|
+
sleep 0.02
|
9
|
+
|
10
|
+
p << 42
|
11
|
+
}
|
12
|
+
|
13
|
+
expect(p.value).to eq(42)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'properly checks if anything has been delivered' do
|
17
|
+
p = Thread.promise
|
18
|
+
|
19
|
+
Thread.new {
|
20
|
+
sleep 0.02
|
21
|
+
|
22
|
+
p << 42
|
23
|
+
}
|
24
|
+
|
25
|
+
expect(p.delivered?).to be(false)
|
26
|
+
sleep 0.03
|
27
|
+
expect(p.delivered?).to be(true)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'does not block when a timeout is passed' do
|
31
|
+
p = Thread.promise
|
32
|
+
|
33
|
+
expect(p.value(0)).to be(nil)
|
34
|
+
end
|
35
|
+
end
|
data/thread.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "seekingalpha_thread"
|
7
|
+
spec.version = "1.0.1"
|
8
|
+
spec.authors = ["Michel Epsztejn"]
|
9
|
+
spec.email = ["michel@seekingalpha.com"]
|
10
|
+
spec.summary = %q{Various extensions to the base thread library.}
|
11
|
+
spec.description = %q{Forked from meh/ruby-thread. Fixes thread limit exception. Includes a thread pool, message passing capabilities, a recursive mutex, promise, future and delay.}
|
12
|
+
spec.homepage = "https://github.com/seekingalpha/ruby-thread"
|
13
|
+
spec.license = "WTFPL"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "rspec"
|
21
|
+
spec.add_development_dependency "rake"
|
22
|
+
end
|