seekingalpha_thread 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,11 @@
1
+ require 'thread/delay'
2
+
3
+ describe Thread::Delay do
4
+ it 'delivers a value properly' do
5
+ d = Thread.delay {
6
+ 42
7
+ }
8
+
9
+ expect(d.value).to eq(42)
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ require 'thread/every'
2
+
3
+ describe Thread::Every do
4
+ it 'delivers a value properly' do
5
+ e = Thread.every(5) { sleep 0.02; 42 }
6
+
7
+ expect(e.value).to eq(42)
8
+ end
9
+ 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