Fixed - high contention during connection acquisition from connection pool. #3111

pull/3123/head
Nikita Koksharov 4 years ago
parent 3ab7930af3
commit 55abf9d780

@ -15,11 +15,9 @@
*/
package org.redisson.pubsub;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
@ -30,8 +28,8 @@ public class AsyncSemaphore {
private static class Entry {
private Runnable runnable;
private int permits;
private final Runnable runnable;
private final int permits;
Entry(Runnable runnable, int permits) {
super();
@ -47,131 +45,94 @@ public class AsyncSemaphore {
return runnable;
}
@Override
@SuppressWarnings("AvoidInlineConditionals")
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((runnable == null) ? 0 : runnable.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Entry other = (Entry) obj;
if (runnable == null) {
if (other.runnable != null)
return false;
} else if (!runnable.equals(other.runnable))
return false;
return true;
}
}
private volatile int counter;
private final Set<Entry> listeners = new LinkedHashSet<Entry>();
private final AtomicInteger counter;
private final Queue<Entry> listeners = new ConcurrentLinkedQueue<>();
private final Set<Runnable> removedListeners = Collections.newSetFromMap(new ConcurrentHashMap<>());
public AsyncSemaphore(int permits) {
counter = permits;
counter = new AtomicInteger(permits);
}
public boolean tryAcquire(long timeoutMillis) {
final CountDownLatch latch = new CountDownLatch(1);
final Runnable listener = new Runnable() {
@Override
public void run() {
latch.countDown();
}
};
acquire(listener);
CountDownLatch latch = new CountDownLatch(1);
Runnable runnable = () -> latch.countDown();
acquire(runnable);
try {
boolean res = latch.await(timeoutMillis, TimeUnit.MILLISECONDS);
if (!res) {
if (!remove(listener)) {
release();
}
boolean r = latch.await(timeoutMillis, TimeUnit.MILLISECONDS);
if (!r) {
remove(runnable);
}
return res;
return r;
} catch (InterruptedException e) {
remove(runnable);
Thread.currentThread().interrupt();
if (!remove(listener)) {
release();
}
return false;
}
}
public int queueSize() {
synchronized (this) {
return listeners.size();
}
return listeners.size() - removedListeners.size();
}
public void removeListeners() {
synchronized (this) {
listeners.clear();
}
listeners.clear();
removedListeners.clear();
}
public void acquire(Runnable listener) {
acquire(listener, 1);
}
public void acquire(Runnable listener, int permits) {
boolean run = false;
synchronized (this) {
if (counter < permits) {
listeners.add(new Entry(listener, permits));
if (permits <= 0) {
throw new IllegalArgumentException("permits can't be negative");
}
listeners.add(new Entry(listener, permits));
tryRun();
}
private void tryRun() {
Entry entry;
while (true) {
entry = listeners.peek();
if (entry == null) {
return;
} else {
counter -= permits;
run = true;
}
int value = counter.get();
if (entry.getPermits() > value) {
return;
}
if (listeners.peek() == entry
&& counter.compareAndSet(value, value - entry.getPermits())) {
listeners.poll();
if (removedListeners.remove(entry.getRunnable())) {
counter.addAndGet(entry.getPermits());
} else {
break;
}
}
}
if (run) {
listener.run();
}
entry.runnable.run();
}
public boolean remove(Runnable listener) {
synchronized (this) {
return listeners.remove(new Entry(listener, 0));
}
public void remove(Runnable listener) {
removedListeners.add(listener);
}
public int getCounter() {
return counter;
return counter.get();
}
public void release() {
Entry entryToAcquire = null;
synchronized (this) {
counter++;
Iterator<Entry> iter = listeners.iterator();
if (iter.hasNext()) {
Entry entry = iter.next();
if (entry.getPermits() <= counter) {
iter.remove();
entryToAcquire = entry;
}
}
}
if (entryToAcquire != null) {
acquire(entryToAcquire.getRunnable(), entryToAcquire.getPermits());
}
counter.incrementAndGet();
tryRun();
}
@Override

@ -15,10 +15,6 @@
*/
package org.redisson.pubsub;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
import org.redisson.PubSubEntry;
import org.redisson.api.RFuture;
import org.redisson.client.BaseRedisPubSubListener;
@ -30,6 +26,9 @@ import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonPromise;
import org.redisson.misc.TransferListener;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
*
* @author Nikita Koksharov
@ -37,75 +36,63 @@ import org.redisson.misc.TransferListener;
*/
abstract class PublishSubscribe<E extends PubSubEntry<E>> {
private final ConcurrentMap<String, E> entries = new ConcurrentHashMap<>();
private final PublishSubscribeService service;
PublishSubscribe(PublishSubscribeService service) {
super();
this.service = service;
}
private final ConcurrentMap<String, E> entries = new ConcurrentHashMap<>();
public void unsubscribe(E entry, String entryName, String channelName) {
AsyncSemaphore semaphore = service.getSemaphore(new ChannelName(channelName));
semaphore.acquire(new Runnable() {
@Override
public void run() {
if (entry.release() == 0) {
// just an assertion
boolean removed = entries.remove(entryName) == entry;
if (!removed) {
throw new IllegalStateException();
}
service.unsubscribe(new ChannelName(channelName), semaphore);
} else {
semaphore.release();
semaphore.acquire(() -> {
if (entry.release() == 0) {
// just an assertion
boolean removed = entries.remove(entryName) == entry;
if (!removed) {
throw new IllegalStateException();
}
service.unsubscribe(new ChannelName(channelName), semaphore);
} else {
semaphore.release();
}
});
}
public RFuture<E> subscribe(String entryName, String channelName) {
AtomicReference<Runnable> listenerHolder = new AtomicReference<Runnable>();
AsyncSemaphore semaphore = service.getSemaphore(new ChannelName(channelName));
RPromise<E> newPromise = new RedissonPromise<E>() {
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return semaphore.remove(listenerHolder.get());
RPromise<E> newPromise = new RedissonPromise<>();
semaphore.acquire(() -> {
if (!newPromise.setUncancellable()) {
semaphore.release();
return;
}
};
Runnable listener = new Runnable() {
E entry = entries.get(entryName);
if (entry != null) {
entry.acquire();
semaphore.release();
entry.getPromise().onComplete(new TransferListener<E>(newPromise));
return;
}
@Override
public void run() {
E entry = entries.get(entryName);
if (entry != null) {
entry.acquire();
semaphore.release();
entry.getPromise().onComplete(new TransferListener<E>(newPromise));
return;
}
E value = createEntry(newPromise);
value.acquire();
E oldValue = entries.putIfAbsent(entryName, value);
if (oldValue != null) {
oldValue.acquire();
semaphore.release();
oldValue.getPromise().onComplete(new TransferListener<E>(newPromise));
return;
}
RedisPubSubListener<Object> listener = createListener(channelName, value);
service.subscribe(LongCodec.INSTANCE, channelName, semaphore, listener);
E value = createEntry(newPromise);
value.acquire();
E oldValue = entries.putIfAbsent(entryName, value);
if (oldValue != null) {
oldValue.acquire();
semaphore.release();
oldValue.getPromise().onComplete(new TransferListener<E>(newPromise));
return;
}
};
semaphore.acquire(listener);
listenerHolder.set(listener);
RedisPubSubListener<Object> listener = createListener(channelName, value);
service.subscribe(LongCodec.INSTANCE, channelName, semaphore, listener);
});
return newPromise;
}

Loading…
Cancel
Save