Skip to content

Commit f198391

Browse files
committed
Update build plugins, fix caching, fix maven plugin
1 parent db64c97 commit f198391

File tree

9 files changed

+381
-95
lines changed

9 files changed

+381
-95
lines changed

net.tascalate.async.runtime/src/main/java/net/tascalate/async/core/SchedulerResolvers.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import net.tascalate.async.Scheduler;
3434
import net.tascalate.async.spi.SchedulerResolver;
3535
import net.tascalate.async.util.Cache;
36+
import net.tascalate.async.util.ReferenceType;
3637

3738
class SchedulerResolvers {
3839
private SchedulerResolvers() {}
@@ -91,7 +92,8 @@ private static ClassLoader classLoaderOfClass(Class<?> clazz) {
9192

9293
private static final Comparator<SchedulerResolver> SCHEDULER_RESOLVER_BY_PRIORITY =
9394
Comparator.comparing(SchedulerResolver::priority).reversed();
95+
9496
private static final Cache<ClassLoader, ServiceLoader<SchedulerResolver>> SERVICE_LOADER_BY_CLASS_LOADER =
95-
new Cache<>();
97+
new Cache<>(ReferenceType.WEAK, ReferenceType.SOFT);
9698

9799
}

net.tascalate.async.runtime/src/main/java/net/tascalate/async/sequence/OrderedSequence.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public void close() {
4646

4747
@Override
4848
public String toString() {
49-
return "<empty-async-sequence>";
49+
return "<empty-sequence>";
5050
}
5151

5252
};

net.tascalate.async.runtime/src/main/java/net/tascalate/async/spi/SchedulerProviderLookup.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import net.tascalate.async.Scheduler;
4444
import net.tascalate.async.SchedulerProvider;
4545
import net.tascalate.async.util.Cache;
46+
import net.tascalate.async.util.ReferenceType;
4647

4748
public class SchedulerProviderLookup {
4849

@@ -242,8 +243,8 @@ protected static boolean isStatic(Member target) {
242243
}
243244
}
244245

245-
private final Cache<Class<?>, Accessor> instanceAccessorsCache = new Cache<>();
246-
private final Cache<Class<?>, Accessor> classAccessorsCache = new Cache<>();
246+
private final Cache<Class<?>, Accessor> instanceAccessorsCache = new Cache<>(ReferenceType.WEAK, ReferenceType.SOFT);
247+
private final Cache<Class<?>, Accessor> classAccessorsCache = new Cache<>(ReferenceType.WEAK, ReferenceType.SOFT);
247248

248249
private final boolean inspectSuperclasses;
249250
private final boolean inspectInterfaces;

net.tascalate.async.runtime/src/main/java/net/tascalate/async/util/Cache.java

+52-80
Original file line numberDiff line numberDiff line change
@@ -26,111 +26,83 @@
2626

2727
import java.lang.ref.Reference;
2828
import java.lang.ref.ReferenceQueue;
29-
import java.lang.ref.WeakReference;
3029
import java.util.concurrent.ConcurrentHashMap;
3130
import java.util.concurrent.ConcurrentMap;
3231
import java.util.function.Function;
3332

3433
public class Cache<K, V> {
35-
private final ConcurrentMap<Reference<K>, Object> producerMutexes = new ConcurrentHashMap<>();
36-
private final ConcurrentMap<Reference<K>, V> valueMap = new ConcurrentHashMap<>();
37-
private final ReferenceQueue<K> queue = new ReferenceQueue<K>();
38-
34+
private final KeyedLocks<K> producerMutexes = new KeyedLocks<>();
35+
private final ConcurrentMap<Object, Object> valueMap = new ConcurrentHashMap<>();
36+
37+
private final ReferenceType keyRefType;
38+
private final ReferenceType valueRefType;
39+
private final ReferenceQueue<K> queue;
40+
41+
public Cache() {
42+
this(ReferenceType.WEAK, ReferenceType.SOFT);
43+
}
44+
45+
public Cache(ReferenceType keyRefType, ReferenceType valueRefType) {
46+
this.keyRefType = keyRefType;
47+
this.valueRefType = valueRefType;
48+
this.queue = keyRefType.createKeyReferenceQueue();
49+
}
50+
3951
public V get(K key, Function<? super K, ? extends V> producer) {
4052
expungeStaleEntries();
4153

42-
Reference<K> lookupKeyRef = new KeyReference<K>(key);
43-
V value;
54+
Object lookupKeyRef = keyRefType.createLookupKey(key);
55+
Object valueRef;
4456

4557
// Try to get a cached value.
46-
value = valueMap.get(lookupKeyRef);
47-
48-
if (value != null) {
49-
// A cached value was found.
50-
return value;
58+
valueRef = valueMap.get(lookupKeyRef);
59+
V value;
60+
61+
if (valueRef != null) {
62+
value = valueRefType.dereference(valueRef);
63+
if (value != null) {
64+
// A cached value was found.
65+
return value;
66+
}
5167
}
5268

53-
Object mutex = getOrCreateMutex(lookupKeyRef);
54-
synchronized (mutex) {
55-
try {
56-
// Double-check after getting mutex
57-
value = valueMap.get(lookupKeyRef);
58-
if (value == null) {
59-
value = producer.apply(key);
60-
final Reference<K> actualKeyRef = new KeyReference<K>(key, queue);
61-
valueMap.put(actualKeyRef, value);
62-
}
63-
} finally {
64-
producerMutexes.remove(lookupKeyRef, mutex);
69+
try (KeyedLocks.Lock lock = producerMutexes.acquire(key)) {
70+
// Double-check after getting mutex
71+
valueRef = valueMap.get(lookupKeyRef);
72+
value = valueRef == null ? null : valueRefType.dereference(valueRef);
73+
if (value == null) {
74+
value = producer.apply(key);
75+
valueMap.put(
76+
keyRefType.createKeyReference(key, queue),
77+
valueRefType.createValueReference(value)
78+
);
6579
}
80+
} catch (InterruptedException ex) {
81+
throw new RuntimeException(ex);
6682
}
67-
6883
return value;
6984
}
7085

86+
7187
public V remove(K key) {
72-
Reference<K> lookupKeyRef = new KeyReference<K>(key);
73-
Object mutex = getOrCreateMutex(lookupKeyRef);
74-
synchronized (mutex) {
75-
try {
76-
final V value = valueMap.remove(lookupKeyRef);
77-
return value;
78-
} finally {
79-
producerMutexes.remove(lookupKeyRef, mutex);
80-
}
88+
try (KeyedLocks.Lock lock = producerMutexes.acquire(key)) {
89+
Object valueRef = valueMap.remove(keyRefType.createLookupKey(key));
90+
return valueRef == null ? null : valueRefType.dereference(valueRef);
91+
} catch (InterruptedException ex) {
92+
throw new RuntimeException(ex);
8193
}
8294
}
83-
84-
protected Object getOrCreateMutex(final Reference<K> keyRef) {
85-
return producerMutexes.computeIfAbsent(keyRef, newProducerLock());
86-
}
87-
95+
8896
private void expungeStaleEntries() {
97+
if (null == queue) {
98+
return;
99+
}
89100
for (Reference<? extends K> ref; (ref = queue.poll()) != null;) {
90101
@SuppressWarnings("unchecked")
91102
Reference<K> keyRef = (Reference<K>) ref;
92-
// keyRef now is equal only to itself while referent is cleared
93-
// already
94-
// so it's safe to remove it without ceremony (like
95-
// getOrCreateMutex(keyRef) usage)
103+
// keyRef now is equal only to itself while referent is cleared already
104+
// so it's safe to remove it without ceremony (like getOrCreateMutex(keyRef) usage)
96105
valueMap.remove(keyRef);
97106
}
98107
}
99-
100-
101-
@SuppressWarnings("unchecked")
102-
private static <K> Function<K, Object> newProducerLock() {
103-
return (Function<K, Object>)NEW_PRODUCER_LOCK;
104-
}
105-
106-
static class KeyReference<K> extends WeakReference<K> {
107-
private final int referentHashCode;
108-
109-
KeyReference(K key) {
110-
this(key, null);
111-
}
112-
113-
KeyReference(K key, ReferenceQueue<K> queue) {
114-
super(key, queue);
115-
referentHashCode = key == null ? 0 : key.hashCode();
116-
}
117-
118-
public int hashCode() {
119-
return referentHashCode;
120-
}
121-
122-
public boolean equals(Object other) {
123-
if (this == other)
124-
return true;
125-
if (null == other || other.getClass() != KeyReference.class)
126-
return false;
127-
Object r1 = this.get();
128-
Object r2 = ((KeyReference<?>) other).get();
129-
return null == r1 ? null == r2 : r1.equals(r2);
130-
}
131-
}
132-
133-
134-
135-
private static final Function<Object, Object> NEW_PRODUCER_LOCK = k -> new Object();
136108
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/**
2+
* Copyright 2015-2025 Valery Silaev (http://vsilaev.com)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package net.tascalate.async.util;
17+
18+
import java.util.concurrent.ConcurrentHashMap;
19+
import java.util.concurrent.ConcurrentMap;
20+
import java.util.concurrent.CountDownLatch;
21+
22+
final class KeyedLocks<K> {
23+
private final ConcurrentMap<K, Lock> locksByKey = new ConcurrentHashMap<>();
24+
25+
public Lock acquire(K key) throws InterruptedException {
26+
Lock ourLock = Lock.acquire(() -> locksByKey.remove(key));
27+
while (true) {
28+
Lock theirLock = locksByKey.putIfAbsent(key, ourLock);
29+
if (theirLock == null) {
30+
// No other locks, we are the owner
31+
return ourLock;
32+
}
33+
if (theirLock.tryAcquire(false)) {
34+
// Reentrant call
35+
return theirLock;
36+
}
37+
// Wait for other lock release and re-try
38+
theirLock.await();
39+
}
40+
}
41+
42+
final static class Lock implements AutoCloseable {
43+
private final CountDownLatch mutex;
44+
private final long threadId;
45+
private final Runnable cleanup;
46+
private int lockedCount = 1;
47+
48+
private Lock(long threadId, Runnable cleanup) {
49+
this.threadId = threadId;
50+
this.cleanup = cleanup;
51+
this.mutex = new CountDownLatch(1);
52+
}
53+
54+
static Lock acquire(Runnable cleanup) {
55+
return new Lock(currentThreadId(), cleanup);
56+
}
57+
58+
boolean sameThread(long currentThreadId, boolean throwError) {
59+
if (currentThreadId != threadId) {
60+
if (throwError) {
61+
return invalidThreadContext("The lock modified from the thread " + currentThreadId + " but was accuried in the thread " + threadId);
62+
} else {
63+
return false;
64+
}
65+
} else {
66+
return true;
67+
}
68+
69+
}
70+
71+
void await() throws InterruptedException {
72+
mutex.await();
73+
}
74+
75+
boolean tryAcquire(boolean throwError) {
76+
long currentThreadId = currentThreadId();
77+
if (threadId != currentThreadId) {
78+
if (throwError) {
79+
return invalidThreadContext("Trying to re-acquire lock from the thread " + currentThreadId + " but it was accuried in the thread " + threadId);
80+
} else {
81+
return false;
82+
}
83+
}
84+
lockedCount++;
85+
return true;
86+
}
87+
88+
boolean tryRelease(boolean throwError) {
89+
long currentThreadId = currentThreadId();
90+
if (threadId != currentThreadId) {
91+
if (throwError) {
92+
return invalidThreadContext("Trying to release lock from the thread " + currentThreadId + " but it was accuried in the thread " + threadId);
93+
} else {
94+
return false;
95+
}
96+
}
97+
if (lockedCount < 1) {
98+
return false;
99+
} else if (--lockedCount == 0) {
100+
cleanup.run();
101+
mutex.countDown();
102+
return true;
103+
} else {
104+
return true;
105+
}
106+
}
107+
108+
public boolean release() {
109+
return tryRelease(false);
110+
}
111+
112+
@Override
113+
public void close() {
114+
tryRelease(true);
115+
}
116+
117+
private static long currentThreadId() {
118+
return Thread.currentThread().getId();
119+
}
120+
121+
private static boolean invalidThreadContext(String message) {
122+
throw new IllegalStateException(message);
123+
}
124+
}
125+
}

0 commit comments

Comments
 (0)