Skip to content

Commit edc1e1d

Browse files
committed
MVar: initial implementation.
1 parent 725314f commit edc1e1d

File tree

3 files changed

+174
-0
lines changed

3 files changed

+174
-0
lines changed

lib/concurrent.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
require 'concurrent/dereferenceable'
1414
require 'concurrent/event'
1515
require 'concurrent/future'
16+
require 'concurrent/mvar'
1617
require 'concurrent/obligation'
1718
require 'concurrent/postable'
1819
require 'concurrent/promise'

lib/concurrent/mvar.rb

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
require 'concurrent/event'
2+
3+
module Concurrent
4+
5+
class MVar
6+
7+
EMPTY = Object.new
8+
TIMEOUT = Object.new
9+
10+
def initialize(value=EMPTY)
11+
@value = value
12+
@mutex = Mutex.new
13+
@empty_condition = ConditionVariable.new
14+
@full_condition = ConditionVariable.new
15+
end
16+
17+
def take(timeout = nil)
18+
@mutex.synchronize do
19+
# If the value isn't empty, wait for full to be signalled
20+
@full_condition.wait(@mutex, timeout) if empty?
21+
22+
# If we timed out we'll still be empty
23+
if not empty?
24+
value = @value
25+
@value = EMPTY
26+
@empty_condition.signal
27+
value
28+
else
29+
TIMEOUT
30+
end
31+
end
32+
33+
34+
end
35+
36+
def put(value, timeout = nil)
37+
@mutex.synchronize do
38+
# Unless the value is empty, wait for empty to be signalled
39+
@empty_condition.wait(@mutex, timeout) unless empty?
40+
41+
# If we timed out we won't be empty
42+
if empty?
43+
@value = value
44+
@full_condition.signal
45+
value
46+
else
47+
TIMEOUT
48+
end
49+
end
50+
end
51+
52+
def empty?
53+
@value == EMPTY
54+
end
55+
56+
end
57+
58+
end

spec/concurrent/mvar_spec.rb

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
require 'spec_helper'
2+
3+
module Concurrent
4+
5+
describe MVar do
6+
7+
context '#initialize' do
8+
9+
it 'accepts no initial value' do
10+
m = MVar.new
11+
m.should be_empty
12+
end
13+
14+
it 'accepts an empty initial value' do
15+
m = MVar.new(MVar::EMPTY)
16+
m.should be_empty
17+
end
18+
19+
it 'accepts an initial value' do
20+
m = MVar.new(14)
21+
m.should_not be_empty
22+
end
23+
24+
it 'accepts a nil initial value' do
25+
m = MVar.new(nil)
26+
m.should_not be_empty
27+
end
28+
29+
end
30+
31+
context '#take' do
32+
33+
it 'sets the MVar to empty' do
34+
m = MVar.new(14)
35+
m.take
36+
m.should be_empty
37+
end
38+
39+
it 'returns the value on a full MVar' do
40+
m = MVar.new(14)
41+
m.take.should eq 14
42+
end
43+
44+
it 'waits for another thread to #put' do
45+
m = MVar.new
46+
47+
putter = Thread.new {
48+
sleep(0.5)
49+
m.put 14
50+
}
51+
52+
m.take.should eq 14
53+
end
54+
55+
it 'returns TIMEOUT on timeout on an empty MVar' do
56+
m = MVar.new
57+
m.take(0.5).should eq MVar::TIMEOUT
58+
end
59+
60+
end
61+
62+
context '#put' do
63+
64+
it 'sets the MVar to be empty' do
65+
m = MVar.new(14)
66+
m.take
67+
m.should be_empty
68+
end
69+
70+
it 'sets a new value on an empty MVar' do
71+
m = MVar.new
72+
m.put 14
73+
m.take.should eq 14
74+
end
75+
76+
it 'waits for another thread to #take' do
77+
m = MVar.new(14)
78+
79+
putter = Thread.new {
80+
sleep(0.5)
81+
m.take
82+
}
83+
84+
m.put(14).should eq 14
85+
end
86+
87+
it 'returns TIMEOUT on timeout on a full MVar' do
88+
m = MVar.new(14)
89+
m.put(14, 0.5).should eq MVar::TIMEOUT
90+
end
91+
92+
it 'returns the value' do
93+
m = MVar.new
94+
m.put(14).should eq 14
95+
end
96+
97+
end
98+
99+
context '#empty' do
100+
101+
it 'returns true on an empty MVar' do
102+
m = MVar.new
103+
m.should be_empty
104+
end
105+
106+
it 'returns false on a full MVar' do
107+
m = MVar.new(14)
108+
m.should_not be_empty
109+
end
110+
111+
end
112+
113+
end
114+
115+
end

0 commit comments

Comments
 (0)