package com.lambdaworks.redis.resource; import static com.lambdaworks.redis.resource.Futures.toBooleanPromise; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import com.lambdaworks.redis.EpollProvider; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.util.concurrent.*; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; /** * Default implementation which manages one event loop group instance per type. * * @author Mark Paluch * @since 3.4 */ public class DefaultEventLoopGroupProvider implements EventLoopGroupProvider { protected static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultEventLoopGroupProvider.class); private final Map<Class<? extends EventExecutorGroup>, EventExecutorGroup> eventLoopGroups = new ConcurrentHashMap<>(2); private final Map<ExecutorService, Long> refCounter = new ConcurrentHashMap<>(2); private final int numberOfThreads; private volatile boolean shutdownCalled = false; /** * Creates a new instance of {@link DefaultEventLoopGroupProvider}. * * @param numberOfThreads number of threads (pool size) */ public DefaultEventLoopGroupProvider(int numberOfThreads) { this.numberOfThreads = numberOfThreads; } @Override public <T extends EventLoopGroup> T allocate(Class<T> type) { synchronized (this) { return addReference(getOrCreate(type)); } } private <T extends ExecutorService> T addReference(T reference) { synchronized (refCounter){ long counter = 0; if(refCounter.containsKey(reference)){ counter = refCounter.get(reference); } logger.debug("Adding reference to {}, existing ref count {}", reference, counter); counter++; refCounter.put(reference, counter); } return reference; } private <T extends ExecutorService> T release(T reference) { synchronized (refCounter) { long counter = 0; if (refCounter.containsKey(reference)) { counter = refCounter.get(reference); } if (counter < 1) { logger.debug("Attempting to release {} but ref count is {}", reference, counter); } counter--; if (counter == 0) { refCounter.remove(reference); } else { refCounter.put(reference, counter); } } return reference; } @SuppressWarnings("unchecked") private <T extends EventLoopGroup> T getOrCreate(Class<T> type) { if (shutdownCalled) { throw new IllegalStateException("Provider is shut down and can not longer provide resources"); } if (!eventLoopGroups.containsKey(type)) { eventLoopGroups.put(type, createEventLoopGroup(type, numberOfThreads)); } return (T) eventLoopGroups.get(type); } /** * Create an instance of a {@link EventExecutorGroup}. Supported types are: * <ul> * <li>DefaultEventExecutorGroup</li> * <li>NioEventLoopGroup</li> * <li>EpollEventLoopGroup</li> * </ul> * * @param type the type * @param numberOfThreads the number of threads to use for the {@link EventExecutorGroup} * @param <T> type parameter * @return a new instance of a {@link EventExecutorGroup} * @throws IllegalArgumentException if the {@code type} is not supported. */ public static <T extends EventExecutorGroup> EventExecutorGroup createEventLoopGroup(Class<T> type, int numberOfThreads) { if (DefaultEventExecutorGroup.class.equals(type)) { return new DefaultEventExecutorGroup(numberOfThreads, new DefaultThreadFactory("lettuce-eventExecutorLoop", true)); } if (NioEventLoopGroup.class.equals(type)) { return new NioEventLoopGroup(numberOfThreads, new DefaultThreadFactory("lettuce-nioEventLoop", true)); } if (EpollProvider.epollEventLoopGroupClass != null && EpollProvider.epollEventLoopGroupClass.equals(type)) { return EpollProvider.newEventLoopGroup(numberOfThreads, new DefaultThreadFactory("lettuce-epollEventLoop", true)); } throw new IllegalArgumentException("Type " + type.getName() + " not supported"); } @Override public Promise<Boolean> release(EventExecutorGroup eventLoopGroup, long quietPeriod, long timeout, TimeUnit unit) { Class<?> key = getKey(release(eventLoopGroup)); if ((key == null && eventLoopGroup.isShuttingDown()) || refCounter.containsKey(eventLoopGroup)) { DefaultPromise<Boolean> promise = new DefaultPromise<Boolean>(GlobalEventExecutor.INSTANCE); promise.setSuccess(true); return promise; } if (key != null) { eventLoopGroups.remove(key); } Future<?> shutdownFuture = eventLoopGroup.shutdownGracefully(quietPeriod, timeout, unit); return toBooleanPromise(shutdownFuture); } private Class<?> getKey(EventExecutorGroup eventLoopGroup) { Class<?> key = null; Map<Class<? extends EventExecutorGroup>, EventExecutorGroup> copy = new HashMap<>(eventLoopGroups); for (Map.Entry<Class<? extends EventExecutorGroup>, EventExecutorGroup> entry : copy.entrySet()) { if (entry.getValue() == eventLoopGroup) { key = entry.getKey(); break; } } return key; } @Override public int threadPoolSize() { return numberOfThreads; } @Override @SuppressWarnings("unchecked") public Future<Boolean> shutdown(long quietPeriod, long timeout, TimeUnit timeUnit) { shutdownCalled = true; Map<Class<? extends EventExecutorGroup>, EventExecutorGroup> copy = new HashMap<>(eventLoopGroups); DefaultPromise<Boolean> overall = new DefaultPromise<Boolean>(GlobalEventExecutor.INSTANCE); DefaultPromise<Boolean> lastRelease = new DefaultPromise<Boolean>(GlobalEventExecutor.INSTANCE); Futures.PromiseAggregator<Boolean, Promise<Boolean>> aggregator = new Futures.PromiseAggregator<Boolean, Promise<Boolean>>( overall); aggregator.expectMore(1 + copy.size()); aggregator.arm(); for (EventExecutorGroup executorGroup : copy.values()) { Promise<Boolean> shutdown = toBooleanPromise(release(executorGroup, quietPeriod, timeout, timeUnit)); aggregator.add(shutdown); } aggregator.add(lastRelease); lastRelease.setSuccess(null); return toBooleanPromise(overall); } }