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

com.opentable.concurrent.ThreadPoolBuilder Maven / Gradle / Ivy

There is a newer version: 6.0.0
Show newest version
/**
 * Copyright (C) 2012 Ness Computing, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.opentable.concurrent;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import javax.inject.Inject;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.MetricRegistry;
import com.google.common.base.MoreObjects;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.mogwee.executors.LoggingExecutor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.PropertyResolver;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.export.UnableToRegisterMBeanException;

import com.opentable.concurrent.ThreadPoolConfig.RejectedHandler;
import com.opentable.spring.SpecializedConfigFactory;

/**
 * Builder for a configurable {@link ExecutorService}.
 * @see ThreadPoolConfig Thread pool configuration options
 */
public class ThreadPoolBuilder implements FactoryBean
{
    private static final Logger LOG = LoggerFactory.getLogger(ThreadPoolBuilder.class);
    private final String threadPoolName;

    private ThreadPoolConfig config;
    private int defaultMinThreads = ThreadPoolConfig.DEFAULT_MIN_THREADS;
    private int defaultMaxThreads = ThreadPoolConfig.DEFAULT_MAX_THREADS;
    private Duration defaultTimeout = ThreadPoolConfig.DEFAULT_TIMEOUT;
    private int defaultQueueSize = ThreadPoolConfig.DEFAULT_QUEUE_SIZE;
    private RejectedExecutionHandler defaultRejectedHandler = ThreadPoolConfig.DEFAULT_REJECTED_HANDLER.getHandler();

    private boolean threadDelegatingWrapperEnabled = true;

    private MetricRegistry metricRegistry;
    private MBeanExporter exporter;
    private final List> wrapperSuppliers = new ArrayList<>();

    ThreadPoolBuilder(String threadPoolName)
    {
        this.threadPoolName = threadPoolName;
    }

    /**
     * Create a default thread pool.
     */
    public static ThreadPoolBuilder defaultPool(String threadPoolName)
    {
        return new ThreadPoolBuilder(threadPoolName);
    }

    /**
     * Create a thread pool for long-running tasks.  The default settings will change to not have a
     * run queue.
     */
    public static ThreadPoolBuilder longTaskPool(String threadPoolName, int poolSize)
    {
        return new ThreadPoolBuilder(threadPoolName).withDefaultMaxThreads(poolSize).withDefaultQueueSize(0);
    }

    /**
     * Create a thread pool for short-running tasks.  The default settings will change to have one thread
     * available per core, plus a few to pick up slack.
     */
    public static ThreadPoolBuilder shortTaskPool(String threadPoolName, int queueSize)
    {
        return new ThreadPoolBuilder(threadPoolName).withDefaultMaxThreads(Runtime.getRuntime().availableProcessors() + 2).withDefaultQueueSize(queueSize);
    }

    /**
     * (For Spring) Inject the property resolver to generate the configuration for the builder according to its name.
     */
    @Inject
    public ThreadPoolBuilder withConfig(PropertyResolver pr) {
        return withConfig(SpecializedConfigFactory.create(pr, ThreadPoolConfig.class, "ot.threadPool.${name}")
            .apply(threadPoolName));
    }

    /**
     * Set the configuration.
     */
    public ThreadPoolBuilder withConfig(ThreadPoolConfig config) {
        this.config = config;
        return this;
    }

    /**
     * Set the default pool core thread count.
     */
    public ThreadPoolBuilder withDefaultMinThreads(int newDefaultMinThreads)
    {
        this.defaultMinThreads = newDefaultMinThreads;
        return this;
    }

    /**
     * Set the default pool max thread count.  May be 0, in which case the executor will be a
     * {@link MoreExecutors#newDirectExecutorService()}.
     */
    public ThreadPoolBuilder withDefaultMaxThreads(int newDefaultMaxThreads)
    {
        this.defaultMaxThreads = newDefaultMaxThreads;
        return this;
    }

    /**
     * Set the default worker thread idle timeout.
     */
    public ThreadPoolBuilder withDefaultThreadTimeout(Duration defaultTimeout)
    {
        this.defaultTimeout = defaultTimeout;
        return this;
    }

    /**
     * Set the default queue length.  May be 0, in which case the queue will be a
     * {@link SynchronousQueue}.
     */
    public ThreadPoolBuilder withDefaultQueueSize(int newDefaultQueueSize)
    {
        this.defaultQueueSize = newDefaultQueueSize;
        return this;
    }

    /**
     * Set the default rejected execution handler.
     */
    public ThreadPoolBuilder withDefaultRejectedHandler(RejectedExecutionHandler newDefaultRejectedHandler)
    {
        this.defaultRejectedHandler = newDefaultRejectedHandler;
        return this;
    }

    /**
     * Remove thread delegated wrapper.
     */
    public ThreadPoolBuilder disableThreadDelegation()
    {
        this.threadDelegatingWrapperEnabled = false;
        return this;
    }

    /**
     * Enable timing wrapper by providing metrics registry into which to put timings and other related metrics.
     */
    @Autowired(required=false)
    public ThreadPoolBuilder enableTimingWrapper(MetricRegistry metricRegistry) {
        this.metricRegistry = metricRegistry;
        return this;
    }

    /**
     * Add a callable wrapper.
     */
    public ThreadPoolBuilder addCallableWrapper(Supplier callableWrapperSupplier) {
        wrapperSuppliers.add(callableWrapperSupplier);
        return this;
    }

    /**
     * Expose some management abilities through JMX.
     */
    @Autowired(required=false)
    public ThreadPoolBuilder withJmxManagement(MBeanExporter exporter) {
        this.exporter = exporter;
        return this;
    }

    /**
     * @return Executor service based on thus-configured properties.  Includes management.
     */
    public ExecutorService build() {
        Integer queueSize = MoreObjects.firstNonNull(config == null ? null : config.getQueueSize(), defaultQueueSize);
        Integer minThreads = MoreObjects.firstNonNull(config == null ? null : config.getMinThreads(), defaultMinThreads);
        Integer maxThreads = MoreObjects.firstNonNull(config == null ? null : config.getMaxThreads(), defaultMaxThreads);
        Duration threadTimeout = MoreObjects.firstNonNull(config == null ? null : config.getThreadTimeout(), defaultTimeout);
        RejectedHandler rejectedHandlerEnum = config == null ? null : config.getRejectedHandler();
        RejectedExecutionHandler rejectedHandler = rejectedHandlerEnum != null ? rejectedHandlerEnum.getHandler() : defaultRejectedHandler;

        List wrappers = new ArrayList<>();
        if (metricRegistry != null) {
            wrappers.add(new TimerWrapper(threadPoolName, metricRegistry));
        }
        if (threadDelegatingWrapperEnabled) {
            wrappers.add(ThreadDelegatingDecorator.THREAD_DELEGATING_WRAPPER);
        }
        wrappers.addAll(wrapperSuppliers.stream().map(Supplier::get).collect(Collectors.toList()));

        final BlockingQueue queue;
        final ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat(threadPoolName + "-%d").build();

        if (queueSize == 0) {
            queue = new SynchronousQueue<>();
        } else {
            queue = new LinkedBlockingQueue<>(queueSize);
        }

        final ExecutorService result;
        final ExecutorServiceManagementBean management;

        if (maxThreads <= 0) {
            result = MoreExecutors.newDirectExecutorService();
            management = new GenericExecutorManagementBean(threadPoolName, result, new SynchronousQueue<>());
        } else {
            ThreadPoolExecutor executor = new LoggingExecutor(
                    minThreads,
                    maxThreads,
                    threadTimeout.toMillis(),
                    TimeUnit.MILLISECONDS,
                    queue,
                    threadFactory,
                    rejectedHandler);
            management = new ThreadPoolExecutorManagementBean(threadPoolName, executor);
            result = executor;
        }

        if (metricRegistry != null) {
            metricRegistry.registerAll(management);
        }

        final ObjectName objectName;
        try {
            objectName = new ObjectName(
                    "com.opentable.concurrent:type=executor,name=" + threadPoolName);
        } catch (MalformedObjectNameException e) {
            throw Throwables.propagate(e);
        }

        if (exporter != null) {
            try {
                exporter.registerManagedResource(management, objectName);
                LOG.info("Exported JMX for thread pool {} at '{}'", threadPoolName, objectName);
            } catch (UnableToRegisterMBeanException e) {
                LOG.error("Unable to export thread pool '{}' as '{}'", threadPoolName, objectName, e);
            }
        } else {
            LOG.debug("Not exporting JMX statistics");
        }

        final Runnable cleanup = () -> {
            if (exporter != null) {
                exporter.unregisterManagedResource(objectName);
            }
            if (metricRegistry != null) {
                final Map exportedMetrics = management.getMetrics();
                final MetricFilter exported = (name, metric) -> exportedMetrics.containsKey(name);
                metricRegistry.removeMatching(exported);
            }
        };

        final ExecutorService decorated = DecoratingExecutors.decorate(result, CallableWrappers.combine(wrappers));
        return new CleanupExecutorService(decorated, cleanup);
    }

    @Override
    public ExecutorService getObject() throws Exception {
        return build();
    }

    @Override
    public Class getObjectType() {
        return ExecutorService.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy