Skip to content
This repository was archived by the owner on Mar 15, 2022. It is now read-only.

Commit 4cf3d46

Browse files
committed
Merge pull request #37 from kares/cache-key
cache.key support
2 parents 7c746cd + f373d37 commit 4cf3d46

File tree

8 files changed

+77
-19
lines changed

8 files changed

+77
-19
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ test/version_tmp
1919
tmp
2020
.DS_Store
2121
*.swp
22+
test/package.jar

README.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,22 @@ sh = ThreadSafe::Hash.new # supports standard Hash.new forms
2828
```
2929

3030
`ThreadSafe::Cache` also exists, as a hash-like object, and should have
31-
much better performance characteristics under concurrency than
31+
much better performance characteristics esp. under high concurrency than
3232
`ThreadSafe::Hash`. However, `ThreadSafe::Cache` is not strictly semantically
33-
equivalent to ruby Hash -- for instance, it does not neccesarily ordered by
34-
insertion time as Hash is. For most uses it should do fine though, and we
35-
recommend you consider `ThreadSafe::Cache` instead of `ThreadSafe::Hash` for your
36-
concurrency-safe hash needs.
33+
equivalent to a ruby `Hash` -- for instance, it does not necessarily retain
34+
ordering by insertion time as `Hash` does. For most uses it should do fine
35+
though, and we recommend you consider `ThreadSafe::Cache` instead of
36+
`ThreadSafe::Hash` for your concurrency-safe hash needs. It understands some
37+
options when created (depending on your ruby platform) that control some of the
38+
internals - when unsure just leave them out:
3739

3840

41+
```ruby
42+
require 'thread_safe'
43+
44+
cache = ThreadSafe::Cache.new
45+
```
46+
3947
## Contributing
4048

4149
1. Fork it

ext/org/jruby/ext/thread_safe/JRubyCacheBackendLibrary.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,17 @@ public RubyBoolean replace_pair(IRubyObject key, IRubyObject oldValue, IRubyObje
178178
return getRuntime().newBoolean(map.replace(key, oldValue, newValue));
179179
}
180180

181-
@JRubyMethod(name = {"key?"}, required = 1)
181+
@JRubyMethod(name = "key?", required = 1)
182182
public RubyBoolean has_key_p(IRubyObject key) {
183183
return map.containsKey(key) ? getRuntime().getTrue() : getRuntime().getFalse();
184184
}
185185

186+
@JRubyMethod
187+
public IRubyObject key(IRubyObject value) {
188+
final IRubyObject key = map.findKey(value);
189+
return key == null ? getRuntime().getNil() : key;
190+
}
191+
186192
@JRubyMethod
187193
public IRubyObject replace_if_exists(IRubyObject key, IRubyObject value) {
188194
IRubyObject result = map.replace(key, value);

ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMap.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,7 @@ public interface BiFun<A,B,T> { T apply(A a, B b); }
2525
public Set<Map.Entry<K,V>> entrySet();
2626
public int size();
2727
public V getValueOrDefault(Object key, V defaultValue);
28+
29+
public boolean containsValue(V value);
30+
public K findKey(V value);
2831
}

ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMapV8.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2430,8 +2430,8 @@ else if (f.casHash(fh, fh | LOCKED)) {
24302430
@SuppressWarnings("serial") static class Traverser<K,V,R> {
24312431
final ConcurrentHashMapV8<K, V> map;
24322432
Node next; // the next entry to use
2433-
Object nextKey; // cached key field of next
2434-
Object nextVal; // cached val field of next
2433+
K nextKey; // cached key field of next
2434+
V nextVal; // cached val field of next
24352435
Node[] tab; // current table; updated if resized
24362436
int index; // index of bin to use next
24372437
int baseIndex; // current index of initial table
@@ -2461,9 +2461,9 @@ else if ((t = it.tab) == null && // force parent tab initialization
24612461
* Advances next; returns nextVal or null if terminated.
24622462
* See above for explanation.
24632463
*/
2464-
final Object advance() {
2464+
final V advance() {
24652465
Node e = next;
2466-
Object ev = null;
2466+
V ev = null;
24672467
outer: do {
24682468
if (e != null) // advance past used/skipped node
24692469
e = e.next;
@@ -2489,8 +2489,8 @@ else if ((m = map) != null && (t = tab = m.table) != null)
24892489
} // visit upper slots if present
24902490
index = (i += baseSize) < n ? i : (baseIndex = b + 1);
24912491
}
2492-
nextKey = e.key;
2493-
} while ((ev = e.val) == null); // skip deleted or special nodes
2492+
nextKey = (K) e.key;
2493+
} while ((ev = (V) e.val) == null); // skip deleted or special nodes
24942494
next = e;
24952495
return nextVal = ev;
24962496
}
@@ -2730,6 +2730,18 @@ public boolean containsValue(Object value) {
27302730
return false;
27312731
}
27322732

2733+
public K findKey(Object value) {
2734+
if (value == null)
2735+
throw new NullPointerException();
2736+
Object v;
2737+
Traverser<K,V,Object> it = new Traverser<K,V,Object>(this);
2738+
while ((v = it.advance()) != null) {
2739+
if (v == value || value.equals(v))
2740+
return it.nextKey;
2741+
}
2742+
return null;
2743+
}
2744+
27332745
/**
27342746
* Legacy method testing if some key maps into the specified value
27352747
* in this table. This method is identical in functionality to

ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/ConcurrentHashMapV8.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2422,8 +2422,8 @@ else if (f.casHash(fh, fh | LOCKED)) {
24222422
@SuppressWarnings("serial") static class Traverser<K,V,R> {
24232423
final ConcurrentHashMapV8<K, V> map;
24242424
Node next; // the next entry to use
2425-
Object nextKey; // cached key field of next
2426-
Object nextVal; // cached val field of next
2425+
K nextKey; // cached key field of next
2426+
V nextVal; // cached val field of next
24272427
AtomicReferenceArray<Node> tab; // current table; updated if resized
24282428
int index; // index of bin to use next
24292429
int baseIndex; // current index of initial table
@@ -2453,9 +2453,9 @@ else if ((t = it.tab) == null && // force parent tab initialization
24532453
* Advances next; returns nextVal or null if terminated.
24542454
* See above for explanation.
24552455
*/
2456-
final Object advance() {
2456+
final V advance() {
24572457
Node e = next;
2458-
Object ev = null;
2458+
V ev = null;
24592459
outer: do {
24602460
if (e != null) // advance past used/skipped node
24612461
e = e.next;
@@ -2481,8 +2481,8 @@ else if ((m = map) != null && (t = tab = m.table) != null)
24812481
} // visit upper slots if present
24822482
index = (i += baseSize) < n ? i : (baseIndex = b + 1);
24832483
}
2484-
nextKey = e.key;
2485-
} while ((ev = e.val) == null); // skip deleted or special nodes
2484+
nextKey = (K) e.key;
2485+
} while ((ev = (V) e.val) == null); // skip deleted or special nodes
24862486
next = e;
24872487
return nextVal = ev;
24882488
}
@@ -2722,6 +2722,18 @@ public boolean containsValue(Object value) {
27222722
return false;
27232723
}
27242724

2725+
public K findKey(Object value) {
2726+
if (value == null)
2727+
throw new NullPointerException();
2728+
Object v;
2729+
Traverser<K,V,Object> it = new Traverser<K,V,Object>(this);
2730+
while ((v = it.advance()) != null) {
2731+
if (v == value || value.equals(v))
2732+
return it.nextKey;
2733+
}
2734+
return null;
2735+
}
2736+
27252737
/**
27262738
* Legacy method testing if some key maps into the specified value
27272739
* in this table. This method is identical in functionality to

lib/thread_safe/cache.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ def each_value
9595
each_pair {|k, v| yield v}
9696
end unless method_defined?(:each_value)
9797

98+
def key(value)
99+
each_pair {|k, v| return k if v == value}
100+
nil
101+
end unless method_defined?(:key)
102+
alias_method :index, :key if RUBY_VERSION < '1.9'
103+
98104
def empty?
99105
each_pair {|k, v| return false}
100106
true

test/test_cache.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,14 +371,24 @@ def test_get_and_set
371371
end
372372

373373
def test_key
374+
with_or_without_default_proc do
375+
assert_equal nil, @cache.key(1)
376+
@cache[:a] = 1
377+
assert_equal :a, @cache.key(1)
378+
assert_equal nil, @cache.key(0)
379+
assert_equal :a, @cache.index(1) if RUBY_VERSION =~ /1\.8/
380+
end
381+
end
382+
383+
def test_key?
374384
with_or_without_default_proc do
375385
assert_equal false, @cache.key?(:a)
376386
@cache[:a] = 1
377387
assert_equal true, @cache.key?(:a)
378388
end
379389
end
380390

381-
def test_value
391+
def test_value?
382392
with_or_without_default_proc do
383393
assert_equal false, @cache.value?(1)
384394
@cache[:a] = 1

0 commit comments

Comments
 (0)