Skip to content

Commit 9866df2

Browse files
authored
Merge pull request #554 from ivoanjo/allow-jruby-semaphore-start-at-zero
Fix JRuby Semaphore not allowing #new and #reduce_permits with zero
2 parents da45461 + 4a4728e commit 9866df2

File tree

2 files changed

+71
-11
lines changed

2 files changed

+71
-11
lines changed

ext/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@ public JavaSemaphore(Ruby runtime, RubyClass metaClass) {
4040

4141
@JRubyMethod
4242
public IRubyObject initialize(ThreadContext context, IRubyObject value) {
43-
this.semaphore = new JRubySemaphore(rubyFixnumToInt(value, "count"));
43+
this.semaphore = new JRubySemaphore(rubyFixnumToNonNegativeInt(value, "count"));
4444
return context.nil;
4545
}
4646

4747
@JRubyMethod
4848
public IRubyObject acquire(ThreadContext context, IRubyObject value) throws InterruptedException {
49-
this.semaphore.acquire(rubyFixnumToInt(value, "permits"));
49+
this.semaphore.acquire(rubyFixnumToPositiveInt(value, "permits"));
5050
return context.nil;
5151
}
5252

@@ -73,14 +73,14 @@ public IRubyObject tryAcquire(ThreadContext context) throws InterruptedException
7373

7474
@JRubyMethod(name = "try_acquire")
7575
public IRubyObject tryAcquire(ThreadContext context, IRubyObject permits) throws InterruptedException {
76-
return getRuntime().newBoolean(semaphore.tryAcquire(rubyFixnumToInt(permits, "permits")));
76+
return getRuntime().newBoolean(semaphore.tryAcquire(rubyFixnumToPositiveInt(permits, "permits")));
7777
}
7878

7979
@JRubyMethod(name = "try_acquire")
8080
public IRubyObject tryAcquire(ThreadContext context, IRubyObject permits, IRubyObject timeout) throws InterruptedException {
8181
return getRuntime().newBoolean(
8282
semaphore.tryAcquire(
83-
rubyFixnumToInt(permits, "permits"),
83+
rubyFixnumToPositiveInt(permits, "permits"),
8484
rubyNumericToLong(timeout, "timeout"),
8585
java.util.concurrent.TimeUnit.SECONDS)
8686
);
@@ -94,22 +94,31 @@ public IRubyObject release(ThreadContext context) {
9494

9595
@JRubyMethod
9696
public IRubyObject release(ThreadContext context, IRubyObject value) {
97-
this.semaphore.release(rubyFixnumToInt(value, "permits"));
97+
this.semaphore.release(rubyFixnumToPositiveInt(value, "permits"));
9898
return getRuntime().newBoolean(true);
9999
}
100100

101101
@JRubyMethod(name = "reduce_permits")
102102
public IRubyObject reducePermits(ThreadContext context, IRubyObject reduction) throws InterruptedException {
103-
this.semaphore.publicReducePermits(rubyFixnumToInt(reduction, "reduction"));
103+
this.semaphore.publicReducePermits(rubyFixnumToNonNegativeInt(reduction, "reduction"));
104104
return context.nil;
105105
}
106106

107-
private int rubyFixnumToInt(IRubyObject value, String paramName) {
107+
private int rubyFixnumToNonNegativeInt(IRubyObject value, String paramName) {
108+
if (value instanceof RubyFixnum && ((RubyFixnum) value).getLongValue() >= 0) {
109+
RubyFixnum fixNum = (RubyFixnum) value;
110+
return (int) fixNum.getLongValue();
111+
} else {
112+
throw getRuntime().newArgumentError(paramName + " must be a non-negative integer");
113+
}
114+
}
115+
116+
private int rubyFixnumToPositiveInt(IRubyObject value, String paramName) {
108117
if (value instanceof RubyFixnum && ((RubyFixnum) value).getLongValue() > 0) {
109118
RubyFixnum fixNum = (RubyFixnum) value;
110119
return (int) fixNum.getLongValue();
111120
} else {
112-
throw getRuntime().newArgumentError(paramName + " must be in integer greater than zero");
121+
throw getRuntime().newArgumentError(paramName + " must be an integer greater than zero");
113122
}
114123
}
115124

@@ -118,7 +127,7 @@ private long rubyNumericToLong(IRubyObject value, String paramName) {
118127
RubyNumeric fixNum = (RubyNumeric) value;
119128
return fixNum.getLongValue();
120129
} else {
121-
throw getRuntime().newArgumentError(paramName + " must be in float greater than zero");
130+
throw getRuntime().newArgumentError(paramName + " must be a float greater than zero");
122131
}
123132
}
124133

spec/concurrent/atomic/semaphore_spec.rb

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
shared_examples :semaphore do
22
let(:semaphore) { described_class.new(3) }
33

4-
context '#initialize' do
4+
describe '#initialize' do
55
it 'raises an exception if the initial count is not an integer' do
66
expect {
77
described_class.new('foo')
88
}.to raise_error(ArgumentError)
99
end
10+
11+
context 'when initializing with 0' do
12+
let(:semaphore) { described_class.new(0) }
13+
14+
it do
15+
expect(semaphore).to_not be nil
16+
end
17+
end
1018
end
1119

1220
describe '#acquire' do
@@ -27,6 +35,14 @@
2735
expect(semaphore.available_permits).to eq 0
2836
end
2937
end
38+
39+
context 'when acquiring zero permits' do
40+
it do
41+
expect {
42+
semaphore.acquire(0)
43+
}.to raise_error(ArgumentError)
44+
end
45+
end
3046
end
3147

3248
describe '#drain_permits' do
@@ -54,6 +70,14 @@
5470
result = semaphore.try_acquire(20)
5571
expect(result).to be_falsey
5672
end
73+
74+
context 'when trying to acquire zero permits' do
75+
it do
76+
expect {
77+
semaphore.try_acquire(0)
78+
}.to raise_error(ArgumentError)
79+
end
80+
end
5781
end
5882

5983
context 'with timeout' do
@@ -86,7 +110,7 @@
86110

87111
it 'reduces permits below zero' do
88112
semaphore.reduce_permits 1003
89-
expect(semaphore.available_permits).to eq -1000
113+
expect(semaphore.available_permits).to eq(-1000)
90114
end
91115

92116
it 'reduces permits' do
@@ -95,6 +119,33 @@
95119
semaphore.reduce_permits 2
96120
expect(semaphore.available_permits).to eq 0
97121
end
122+
123+
it 'reduces zero permits' do
124+
semaphore.reduce_permits 0
125+
expect(semaphore.available_permits).to eq 3
126+
end
127+
end
128+
129+
describe '#release' do
130+
it 'increases the number of available permits by one' do
131+
semaphore.release
132+
expect(semaphore.available_permits).to eq 4
133+
end
134+
135+
context 'when a number of permits is specified' do
136+
it 'increases the number of available permits by the specified value' do
137+
semaphore.release(2)
138+
expect(semaphore.available_permits).to eq 5
139+
end
140+
141+
context 'when permits is set to zero' do
142+
it do
143+
expect {
144+
semaphore.release(0)
145+
}.to raise_error(ArgumentError)
146+
end
147+
end
148+
end
98149
end
99150
end
100151

0 commit comments

Comments
 (0)