All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tech.ydb.table.impl.pool.WaitingQueue Maven / Gradle / Ivy

package tech.ydb.table.impl.pool;

import java.util.Iterator;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;

import javax.annotation.concurrent.ThreadSafe;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import tech.ydb.core.Status;
import tech.ydb.core.StatusCode;
import tech.ydb.core.UnexpectedResultException;

/**
 *
 * @author Aleksandr Gorshenin
 * @param  type of objects in queue
*/
@ThreadSafe
public class WaitingQueue implements AutoCloseable {
    private static final Logger logger = LoggerFactory.getLogger(WaitingQueue.class);

    public interface Handler {
        CompletableFuture create();
        void destroy(T object);
    }

    /** Limit of waiting requests = maxSize * constant */
    @VisibleForTesting
    static final int WAITINGS_LIMIT_FACTOR = 10;

    private final Handler handler;
    private volatile Limits limits;
    private volatile boolean stopped;

    /** Deque of idle objects */
    private final ConcurrentLinkedDeque idle = new ConcurrentLinkedDeque<>();
    /** Non idle objects managed by WaitingQueue */
    private final Map used = new ConcurrentHashMap<>();
    /** Set of pending object creations */
    private final Map, CompletableFuture> pendingRequests = new ConcurrentHashMap<>();

    /** Summary size of queue = idle.size() + used.size() + pendingRequests.size() */
    private final AtomicInteger queueSize = new AtomicInteger();

    /** Queue of waiting acquire requests */
    private final Queue> waitingAcquires = new ConcurrentLinkedQueue<>();
    /** Size of waiting acquires queue */
    private final AtomicInteger waitingAcqueireCount = new AtomicInteger();

    @VisibleForTesting
    WaitingQueue(Handler handler, int maxSize, int waitingsLimit) {
        Preconditions.checkArgument(maxSize > 0, "WaitingQueue max size (%d) must be positive", maxSize);
        Preconditions.checkArgument(handler != null, "WaitingQueue handler must be not null");

        this.handler = handler;
        this.limits = new Limits(maxSize, waitingsLimit);
    }

    public WaitingQueue(Handler handler, int maxSize) {
        this(handler, maxSize, maxSize * WAITINGS_LIMIT_FACTOR);
    }

    public void updateLimits(int maxSize) {
        updateLimits(maxSize, maxSize * WAITINGS_LIMIT_FACTOR);
    }

    public void updateLimits(int maxSize, int waitingsLimit) {
        this.limits = new Limits(maxSize, waitingsLimit);
        checkNextWaitingAcquire();
    }

    public void acquire(CompletableFuture acquire) {
        if (stopped) {
             acquire.completeExceptionally(new IllegalStateException("Queue is already closed"));
             return;
        }

        boolean ok = tryToPollIdle(acquire)
                || tryToCreateNewPending(acquire)
                || tryToCreateNewWaiting(acquire);

        if (!ok) {
            acquire.completeExceptionally(new UnexpectedResultException(
                    "Objects limit exceeded", Status.of(StatusCode.CLIENT_RESOURCE_EXHAUSTED)
            ));
        }
    }

    public void release(T object) {
        if (!used.remove(object, object)) {
            if (!logger.isTraceEnabled()) {
                logger.warn("obj {} double release, possible pool leaks!!", object);
            } else {
                Exception stackTrace = new RuntimeException("Double release");
                logger.warn("obj {} double release, possible pool leaks!!", object, stackTrace);
            }
            return;
        }

        // Try to complete waiting request
        if (!tryToCompleteWaiting(object)) {
            // if queue is overflowed
            if (queueSize.get() > limits.maxSize) {
                queueSize.decrementAndGet();
                handler.destroy(object);
                return;
            }

            // Put object to idle deque as hottest object
            idle.offerFirst(object); // ConcurrentLinkedDeque always return true
            if (stopped) {
                clear();
            }
        }
    }

    public void delete(T object) {
        if (!used.remove(object, object)) {
            if (!logger.isTraceEnabled()) {
                logger.warn("obj {} double delete, possible pool leaks!!", object);
            } else {
                Exception stackTrace = new RuntimeException("Double delete");
                logger.warn("obj {} double delete, possible pool leaks!!", object, stackTrace);
            }
            return;
        }
        queueSize.decrementAndGet();
        handler.destroy(object);

        // After deleting one object we can try to create new pending if it needed
        checkNextWaitingAcquire();
    }

    @Override
    public void close() {
        stopped = true;
        clear();
    }

    public Iterator coldIterator() {
        return new ColdIterator(idle.descendingIterator());
    }

    public int getIdleCount() {
        return idle.size();
    }

    public int getUsedCount() {
        return used.size();
    }

    public int getTotalCount() {
        return queueSize.get();
    }

    public int getPendingCount() {
        return pendingRequests.size();
    }

    public int getWaitingCount() {
        return waitingAcqueireCount.get();
    }

    public int getTotalLimit() {
        return limits.maxSize;
    }

    public int getWaitingLimit() {
        return limits.waitingsLimit;
    }

    private boolean safeAcquireObject(CompletableFuture acquire, T object) {
        used.put(object, object);
        if (stopped) {
            acquire.completeExceptionally(new CancellationException("Queue is already closed"));
            if (used.remove(object, object)) {
                queueSize.decrementAndGet();
                handler.destroy(object);
            }
            return true;
        }

        if (!acquire.complete(object)) {
            used.remove(object, object);
            return false;
        }

        return true;
    }

    private boolean tryToPollIdle(CompletableFuture acquire) {
        // Try to poll hottest element
        T next = idle.pollFirst();
        if (next == null) {
            return false;
        }

        if (!safeAcquireObject(acquire, next)) {
            idle.offerFirst(next);
            return false;
        }

        return true;
    }

    private boolean tryToCreateNewPending(CompletableFuture acquire) {
        int count = queueSize.get();
        while (count < limits.maxSize) {
            if (!queueSize.compareAndSet(count, count + 1)) {
                count = queueSize.get();
                continue;
            }

            CompletableFuture pending = handler.create();
            pendingRequests.put(pending, pending);
            pending.whenComplete(new PendingHandler(acquire, pending));
            return true;
        }

        return false;
    }

    private boolean tryToCreateNewWaiting(CompletableFuture acquire) {
        int waitingsCount = waitingAcqueireCount.get();
        while (waitingsCount < limits.waitingsLimit) {
            if (!waitingAcqueireCount.compareAndSet(waitingsCount, waitingsCount + 1)) {
                waitingsCount = waitingAcqueireCount.get();
                continue;
            }

            waitingAcquires.offer(acquire); // ConcurrentLinkedQueue always return true
            return true;
        }

        return false;
    }

    private boolean tryToCompleteWaiting(T object) {
        if (stopped) {
            return false;
        }

        CompletableFuture next = waitingAcquires.poll();
        while (next != null) {
            waitingAcqueireCount.decrementAndGet();

            if (safeAcquireObject(next, object)) {
                return true;
            }

            next = waitingAcquires.poll();
        }

        return false;
    }

    private void checkNextWaitingAcquire() {
        if (stopped || waitingAcquires.isEmpty()) {
            return;
        }

        // Try to create new pending request
        CompletableFuture pending = new CompletableFuture<>();
        if (tryToCreateNewPending(pending)) {
            pending.whenComplete((object, th) -> {
                if (th != null) {
                    checkNextWaitingAcquire();
                }
                if (object != null) {
                    if (!tryToCompleteWaiting(object)) {
                        idle.offerFirst(object);
                    }
                }
            });
        }
    }

    private void clear() {
        for (CompletableFuture key : pendingRequests.keySet()) {
            if (pendingRequests.remove(key, key)) {
                queueSize.decrementAndGet();
            }
        }

        CompletableFuture waiting = waitingAcquires.poll();
        while (waiting != null) {
            waiting.completeExceptionally(new CancellationException("Queue is already closed"));
            waiting = waitingAcquires.poll();
        }

        T nextIdle = idle.poll();
        while (nextIdle != null) {
            queueSize.decrementAndGet();
            handler.destroy(nextIdle);
            nextIdle = idle.poll();
        }
    }

    private static class Limits {
        private final int maxSize;
        private final int waitingsLimit;

        Limits(int queueSize, int waitingsSize) {
            this.maxSize = queueSize;
            this.waitingsLimit = waitingsSize;
        }
    }

    private class PendingHandler implements BiConsumer {
        private final CompletableFuture acquire;
        private final CompletableFuture pending;

        PendingHandler(CompletableFuture acquire, CompletableFuture pending) {
            this.acquire = acquire;
            this.pending = pending;
        }

        @Override
        public void accept(T object, Throwable th) {
            // If pool is already closed and clean
            if (!pendingRequests.remove(pending, pending)) {
                acquire.completeExceptionally(new CancellationException("Queue is already closed"));
                if (object != null) {
                    handler.destroy(object);
                }
                return;
            }

            // The implementation of CompletableFuture
            // guarantees that if object is null then th is not null
            if (th != null) {
                queueSize.decrementAndGet();
                acquire.completeExceptionally(th);
                return;
            }

            if (!acquire.isDone() && safeAcquireObject(acquire, object)) {
                return;
            }

            // If acquire future is already canceled, put new object to hot queue
            idle.offerFirst(object); // ConcurrentLinkedQueue always return true
            if (stopped) {
                clear();
            }
        }
    }

    /** Iterator with custom remove action */
    private class ColdIterator implements Iterator {
        private final Iterator iter;
        private volatile T lastRet;

        ColdIterator(Iterator iter) {
            this.iter = iter;
        }

        @Override
        public boolean hasNext() {
            return this.iter.hasNext();
        }

        @Override
        public void remove() {
            if (lastRet == null) {
                return;
            }
            if (idle.removeLastOccurrence(lastRet)) {
                handler.destroy(lastRet);
                lastRet = null;
                queueSize.decrementAndGet();
                checkNextWaitingAcquire();
            }
        }

        @Override
        public T next() {
            lastRet = iter.next();
            return lastRet;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy