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

com.infomaximum.platform.querypool.QueryPool Maven / Gradle / Ivy

There is a newer version: 0.1.15p24
Show newest version
package com.infomaximum.platform.querypool;

import com.infomaximum.platform.exception.PlatformException;
import com.infomaximum.platform.querypool.service.DetectHighLoad;
import com.infomaximum.platform.querypool.service.DetectLongQuery;
import com.infomaximum.platform.querypool.service.DetectQueueFilling;
import com.infomaximum.platform.querypool.service.threadcontext.ThreadContext;
import com.infomaximum.platform.querypool.service.threadcontext.ThreadContextImpl;
import com.infomaximum.platform.sdk.component.Component;
import com.infomaximum.platform.sdk.context.ContextTransaction;
import com.infomaximum.platform.sdk.context.ContextTransactionInternal;
import com.infomaximum.platform.sdk.exception.GeneralExceptionBuilder;
import com.infomaximum.platform.utils.DefaultThreadGroup;
import com.infomaximum.platform.utils.DefaultThreadPoolExecutor;
import com.infomaximum.platform.utils.LockGuard;

import java.time.Instant;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;

public class QueryPool {

    public enum LockType {
        SHARED, EXCLUSIVE
    }

    public enum Priority {
        LOW, HIGH
    }

    @FunctionalInterface
    public interface Callback {

        void execute(QueryPool pool);
    }

    public static class QueryWrapper {

        final QueryPool queryPool;
        final Component component;
        final ContextTransaction context;
        final Query query;
        final QueryFuture future;

        final Map resources;

        private volatile Thread thread;
        private volatile Instant timeStart;
        private volatile Instant timeComplete;

        QueryWrapper(QueryPool queryPool, Component component, ContextTransaction context, Query query) throws PlatformException {
            this(new QueryFuture(queryPool, component, context, new CompletableFuture<>()), query);
        }

        QueryWrapper(QueryFuture queryFuture, Query query) throws PlatformException {
            this.queryPool = queryFuture.queryPool;
            this.component = queryFuture.component;
            this.context = queryFuture.context;
            this.query = query;
            this.future = queryFuture;

            try (ResourceProviderImpl provider = new ResourceProviderImpl(component)) {
                query.prepare(provider);
                this.resources = provider.getResources();
            }
        }

        public ContextTransaction getContext() {
            return context;
        }

        public Thread getThread() {
            return thread;
        }

        public Instant getTimeStart() {
            return timeStart;
        }

        public Instant getTimeComplete() {
            return timeComplete;
        }

        public Map getResources() {
            return Collections.unmodifiableMap(resources);
        }

        private void execute() {
            timeStart = Instant.now();
            thread = Thread.currentThread();
            try (QueryTransaction transaction = new QueryTransaction(component.getDomainObjectSource())) {
                try {
                    ((ContextTransactionInternal) context).setTransaction(transaction);
                    queryPool.threadContext.setContext(context);

                    T result = query.execute(transaction);
                    transaction.commit();
                    transaction.fireCommitListeners();
                    future.complete(result);
                } catch (PlatformException e) {
                    transaction.fireRollbackListeners(e);
                    future.completeExceptionally(e);
                }
            } catch (Throwable e) {
                future.completeExceptionally(e);
                throw e;
            } finally {
                timeComplete = Instant.now();
                queryPool.detectQueueFilling.queryComplete(this, timeStart, timeComplete);
                thread = null;
                queryPool.threadContext.clearContext();
            }
        }
    }

    private static class QueryLockType {

        final QueryWrapper query;
        final LockType lockType;

        QueryLockType(QueryWrapper query, LockType lockType) {
            this.query = query;
            this.lockType = lockType;
        }
    }

    private static class ResourceMap extends HashMap> {
    }

    //TODO Удалить костыль
    public static final int MAX_THREAD_COUNT = Runtime.getRuntime().availableProcessors() * 80;
    public static final int MAX_WORKED_QUERY_COUNT = MAX_THREAD_COUNT * 5;

    /**
     * Пользовательские запросы более приоритетны, потоэму лучше их положить в очередь, чем сразу кинуть ошибку
     */
    public static final int MAX_WAITING_HIGH_QUERY_COUNT = MAX_THREAD_COUNT * 20;
    /**
     * Очередь для низко приоритетных запросов в четыре раза меньше пула потоков данных, сделана для того,
     * что бы сервер не захлебывался в моменты пиковой нагрузки
     */
    public static final int MAX_WAITING_LOW_QUERY_COUNT = MAX_THREAD_COUNT / 4;

    private final ThreadPoolExecutor threadPool;

    private final ReentrantLock lock = new ReentrantLock();
    private final ArrayList maintenanceMarkers = new ArrayList<>();
    private final ResourceMap occupiedResources = new ResourceMap();
    private final ResourceMap waitingResources = new ResourceMap();
    private final ArrayList emptyPoolListners = new ArrayList<>();

    private final DetectLongQuery detectLongQuery;
    private final DetectHighLoad detectHighLoad;
    private final DetectQueueFilling detectQueueFilling;
    private final ThreadContextImpl threadContext;

    private volatile int highPriorityWaitingQueryCount = 0;
    private volatile int lowPriorityWaitingQueryCount = 0;
    private volatile PlatformException hardException = null;

    public QueryPool(Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {
        DefaultThreadGroup defaultThreadGroup = new DefaultThreadGroup("QueryPool", uncaughtExceptionHandler);

        this.threadPool = new DefaultThreadPoolExecutor(
                MAX_THREAD_COUNT,
                MAX_THREAD_COUNT,
                0L,
                TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(MAX_WORKED_QUERY_COUNT),
                defaultThreadGroup
        );
        this.detectLongQuery = new DetectLongQuery(this, uncaughtExceptionHandler);
        this.detectHighLoad = new DetectHighLoad(this, threadPool, uncaughtExceptionHandler);
        this.detectQueueFilling = new DetectQueueFilling();
        this.threadContext = new ThreadContextImpl(defaultThreadGroup);
    }

    public void setHardException(PlatformException e) {
        hardException = e;
    }

    protected  void execute(QueryFuture queryFuture, Query query, boolean failIfPoolBusy) {
        QueryWrapper queryWrapp;
        try {
            queryWrapp = new QueryWrapper<>(queryFuture, query);
        } catch (PlatformException e) {
            queryFuture.completeExceptionally(e);
            return;
        }
        execute(queryWrapp, failIfPoolBusy);
    }

    public  QueryFuture execute(Component component, Query query) {
        return execute(component, null, query, true);
    }

    public  QueryFuture execute(Component component, ContextTransaction context, Query query) {
        return execute(component, context, query, true);
    }

    public  QueryFuture execute(Component component, ContextTransaction context, Query query, boolean failIfPoolBusy) {
        QueryWrapper queryWrapp;
        try {
            queryWrapp = new QueryWrapper<>(this, component, context, query);
        } catch (PlatformException e) {
            CompletableFuture future = new CompletableFuture();
            future.completeExceptionally(e);
            return new QueryFuture<>(this, component, context, future);
        }
        return execute(queryWrapp, failIfPoolBusy);
    }

    protected  QueryFuture execute(QueryWrapper queryWrapp, boolean failIfPoolBusy) {
        if (hardException != null) {
            queryWrapp.future.completeExceptionally(hardException);
            return queryWrapp.future;
        }

        try (LockGuard guard = new LockGuard(lock)) {
            if (failIfPoolBusy && isOverloaded(queryWrapp.query.getPriority())) {
                queryWrapp.future.completeExceptionally(createOverloadedException());
            } else if (isOccupiedResources(queryWrapp.resources)) {
                if (failIfPoolBusy && isMaintenance()) {
                    queryWrapp.future.completeExceptionally(createMaintenanceException());
                } else {
                    captureWaitingResources(queryWrapp);
                }
            } else {
                submitQuery(queryWrapp);
            }
        }

        return queryWrapp.future;
    }

    public boolean isBusyFor(Priority priority) {
        try (LockGuard guard = new LockGuard(lock)) {
            return isMaintenance() || isOverloaded(priority);
        }
    }

    /**
     * @return null if query is not submitted
     */
    public  QueryFuture tryExecuteImmediately(Component component, Query query) throws PlatformException {
        return tryExecuteImmediately(component, null, query);
    }

    /**
     * @return null if query is not submitted
     */
    public  QueryFuture tryExecuteImmediately(Component component, ContextTransaction context, Query query) throws PlatformException {
        if (hardException != null) {
            return null;
        }

        QueryWrapper queryWrapp = new QueryWrapper<>(this, component, context, query);

        try (LockGuard guard = new LockGuard(lock)) {
            if (isOverloaded(queryWrapp.query.getPriority()) || isOccupiedResources(queryWrapp.resources)) {
                return null;
            }
            submitQuery(queryWrapp);
            return queryWrapp.future;
        }
    }

    public void addEmptyReachedListner(Callback callback) {
        try (LockGuard guard = new LockGuard(lock)) {
            emptyPoolListners.add(callback);
        }
    }

    public void removeEmptyReachedListner(Callback callback) {
        try (LockGuard guard = new LockGuard(lock)) {
            emptyPoolListners.remove(callback);
        }
    }

    public void tryFireEmptyReachedListener() {
        Callback[] listeners;
        try (LockGuard guard = new LockGuard(lock)) {
            listeners = getFiringEmptyPoolListners();
        }
        fireEmptyPoolListners(listeners);
    }

    public boolean waitingQueryExists(Priority priority) {
        switch (priority) {
            case LOW:
                return lowPriorityWaitingQueryCount != 0;
            case HIGH:
                return highPriorityWaitingQueryCount != 0;
        }
        return false;
    }

    public Collection getExecuteQueries() {
        final HashSet queries = new HashSet<>();
        try (LockGuard guard = new LockGuard(lock)) {
            occupiedResources.forEach((key, value) -> value.forEach(item -> queries.add(item.query)));
        }
        return queries;
    }

    public ThreadContext getThreadContext(){
        return threadContext;
    }

    public void shutdownAwait() throws InterruptedException {
        detectLongQuery.shutdownAwait();
        detectHighLoad.shutdownAwait();

        threadPool.shutdown();

        final HashSet queries = new HashSet<>();
        try (LockGuard guard = new LockGuard(lock)) {
            waitingResources.forEach((key, value) -> value.forEach(item -> queries.add(item.query)));
            waitingResources.clear();
        }
        queries.forEach((query) -> query.future.completeExceptionally(GeneralExceptionBuilder.buildServerShutsDownException()));

        threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
    }

    public boolean isShutdown() {
        return threadPool.isShutdown();
    }

    public void await() throws InterruptedException {
        while (true) {
            try (LockGuard guard = new LockGuard(lock)) {
                if (waitingResources.isEmpty()) {
                    break;
                }
            }

            Thread.sleep(500L);
        }

        shutdownAwait();
    }

    private void submitQuery(QueryWrapper queryWrapp) {
        captureOccupiedResources(queryWrapp);

        try {
            threadPool.submit(() -> {
                try {
                    queryWrapp.execute();
                } catch (Throwable e) {
                    try (LockGuard guard = new LockGuard(lock)) {
                        releaseOccupiedResources(queryWrapp);
                    } catch (Throwable ignore) {
                        // do nothing
                    }

                    throw e;
                }

                Callback[] emptyListners;
                try (LockGuard guard = new LockGuard(lock)) {
                    releaseOccupiedResources(queryWrapp);
                    emptyListners = getFiringEmptyPoolListners();

                    //COMMENT Миронов В. можно оптимизировать поиск запросов на исполнение если releaseResources будет
                    // возвращать список ресурсов у которых нет активных Query или он был заблокирован на SHARED
                    trySubmitNextAvailableQueryBy(queryWrapp.resources);
                }

                fireEmptyPoolListners(emptyListners);
            });
        } catch (RejectedExecutionException e) {
            releaseOccupiedResources(queryWrapp);
            queryWrapp.future.completeExceptionally(GeneralExceptionBuilder.buildServerShutsDownException());
        } catch (Throwable e) {
            releaseOccupiedResources(queryWrapp);
            throw e;
        }
    }

    private Callback[] getFiringEmptyPoolListners() {
        if (!occupiedResources.isEmpty() || !waitingResources.isEmpty() || emptyPoolListners.isEmpty()) {
            return null;
        }

        Callback[] listners = new Callback[emptyPoolListners.size()];
        emptyPoolListners.toArray(listners);
        return listners;
    }

    private void fireEmptyPoolListners(Callback[] listners) {
        if (listners != null) {
            for (Callback item : listners) {
                item.execute(this);
            }
        }
    }

    private void captureOccupiedResources(QueryWrapper queryWrapp) {
        appendResources(queryWrapp, occupiedResources);
        pushMaintenance(queryWrapp.query.getMaintenanceMarker());
    }

    private void releaseOccupiedResources(QueryWrapper queryWrapp) {
        popMaintenance(queryWrapp.query.getMaintenanceMarker());
        removeResources(queryWrapp, occupiedResources);
    }

    private void captureWaitingResources(QueryWrapper queryWrapp) {
        switch (queryWrapp.query.getPriority()) {
            case LOW:
                ++lowPriorityWaitingQueryCount;
                break;
            case HIGH:
                ++highPriorityWaitingQueryCount;
                break;
        }
        appendResources(queryWrapp, waitingResources);
    }

    private void releaseWaitingResources(QueryWrapper queryWrapp) {
        removeResources(queryWrapp, waitingResources);
        switch (queryWrapp.query.getPriority()) {
            case LOW:
                --lowPriorityWaitingQueryCount;
                break;
            case HIGH:
                --highPriorityWaitingQueryCount;
                break;
        }
    }

    private void trySubmitNextAvailableQueryBy(Map releasedResources) {
        HashSet candidates = new HashSet<>();

        for (Map.Entry res : releasedResources.entrySet()) {
            ArrayList value = waitingResources.get(res.getKey());
            if (value != null) {
                value.forEach(item -> candidates.add(item.query));
            }
        }

        for (QueryWrapper query : candidates) {
            if (isOccupiedResources(query.resources)) {
                continue;
            }

            if (isFilledThreadPool()) {
                break;
            }

            releaseWaitingResources(query);
            submitQuery(query);
        }
    }

    private boolean isOverloaded(Priority newQueryPriority) {
        if (isFilledThreadPool()) {
            return true;
        }

        switch (newQueryPriority) {
            case LOW:
                //В случа низкоприоритетных запросов смотрим так же и на очередь высокоприоритетных - и если там растет нагрузка,
                // то низкоприоритетные мы сразу откидываем
                return (
                        //Аналог highPriorityWaitingQueryCount >= (MAX_WAITING_HIGH_QUERY_COUNT/4)
                        (highPriorityWaitingQueryCount >= (MAX_WAITING_HIGH_QUERY_COUNT >> 2)) ||
                                (lowPriorityWaitingQueryCount >= MAX_WAITING_LOW_QUERY_COUNT)
                );
            case HIGH:
                return highPriorityWaitingQueryCount >= MAX_WAITING_HIGH_QUERY_COUNT;
        }
        return false;
    }

    private boolean isFilledThreadPool() {
        return threadPool.getQueue().size() >= MAX_WORKED_QUERY_COUNT;
    }

    private void pushMaintenance(String marker) {
        if (marker != null) {
            maintenanceMarkers.add(marker);
        }
    }

    private void popMaintenance(String marker) {
        if (marker != null) {
            maintenanceMarkers.remove(maintenanceMarkers.size() - 1);
        }
    }

    private boolean isMaintenance() {
        return !maintenanceMarkers.isEmpty();
    }

    private boolean isOccupiedResources(final Map targetResources) {
        for (HashMap.Entry res : targetResources.entrySet()) {
            ArrayList foundValue = occupiedResources.get(res.getKey());
            if (foundValue == null || foundValue.isEmpty()) {
                continue;
            }

            if (res.getValue() == LockType.EXCLUSIVE || foundValue.get(0).lockType == LockType.EXCLUSIVE) {
                return true;
            }
        }
        return false;
    }

    public int getHighPriorityWaitingQueryCount() {
        return highPriorityWaitingQueryCount;
    }

    public int getLowPriorityWaitingQueryCount() {
        return lowPriorityWaitingQueryCount;
    }

    private static void appendResources(QueryWrapper query, ResourceMap destination) {
        for (Map.Entry entry : query.resources.entrySet()) {
            ArrayList foundValue = destination.get(entry.getKey());
            if (foundValue == null) {
                foundValue = new ArrayList<>();
                destination.put(entry.getKey(), foundValue);
            }

            foundValue.add(new QueryLockType(query, entry.getValue()));
        }
    }

    private static void removeResources(QueryWrapper query, ResourceMap destination) {
        for (Map.Entry entry : query.resources.entrySet()) {
            ArrayList foundValue = destination.get(entry.getKey());
            if (foundValue == null) {
                continue;
            }

            foundValue.removeIf(item -> item.query == query);
            if (foundValue.isEmpty()) {
                destination.remove(entry.getKey());
            }
        }
    }

    private PlatformException createMaintenanceException() {
        return GeneralExceptionBuilder.buildServerBusyException(maintenanceMarkers.get(maintenanceMarkers.size() - 1));
    }

    private PlatformException createOverloadedException() {
        if (isMaintenance()) {
            return createMaintenanceException();
        }

        return GeneralExceptionBuilder.buildServerOverloadedException();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy