ch.cyberduck.core.pool.DefaultSessionPool.java Source code

Java tutorial

Introduction

Here is the source code for ch.cyberduck.core.pool.DefaultSessionPool.java

Source

package ch.cyberduck.core.pool;

/*
 * Copyright (c) 2002-2017 iterate GmbH. All rights reserved.
 * https://cyberduck.io/
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

import ch.cyberduck.core.ConnectionService;
import ch.cyberduck.core.Host;
import ch.cyberduck.core.PathCache;
import ch.cyberduck.core.Session;
import ch.cyberduck.core.SessionFactory;
import ch.cyberduck.core.TranscriptListener;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.exception.ConnectionCanceledException;
import ch.cyberduck.core.ssl.DefaultX509KeyManager;
import ch.cyberduck.core.ssl.DisabledX509TrustManager;
import ch.cyberduck.core.ssl.X509KeyManager;
import ch.cyberduck.core.ssl.X509TrustManager;
import ch.cyberduck.core.threading.BackgroundActionState;
import ch.cyberduck.core.threading.DefaultFailureDiagnostics;
import ch.cyberduck.core.threading.FailureDiagnostics;
import ch.cyberduck.core.vault.VaultRegistry;
import ch.cyberduck.core.worker.DefaultExceptionMappingService;

import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.AbandonedConfig;
import org.apache.commons.pool2.impl.EvictionConfig;
import org.apache.commons.pool2.impl.EvictionPolicy;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.log4j.Logger;

import java.util.NoSuchElementException;

public class DefaultSessionPool implements SessionPool {
    private static final Logger log = Logger.getLogger(DefaultSessionPool.class);

    private static final long BORROW_MAX_WAIT_INTERVAL = 1000L;
    private static final int POOL_WARNING_THRESHOLD = 5;

    private final FailureDiagnostics<BackgroundException> diagnostics = new DefaultFailureDiagnostics();

    private final ConnectionService connect;
    private final TranscriptListener transcript;
    private final PathCache cache;
    private final Host bookmark;

    private final VaultRegistry registry;

    private final GenericObjectPool<Session> pool;

    private SessionPool features = SessionPool.DISCONNECTED;

    public DefaultSessionPool(final ConnectionService connect, final X509TrustManager trust,
            final X509KeyManager key, final VaultRegistry registry, final PathCache cache,
            final TranscriptListener transcript, final Host bookmark) {
        this.connect = connect;
        this.registry = registry;
        this.cache = cache;
        this.bookmark = bookmark;
        this.transcript = transcript;
        final GenericObjectPoolConfig configuration = new GenericObjectPoolConfig();
        configuration.setJmxEnabled(false);
        configuration.setEvictionPolicyClassName(CustomPoolEvictionPolicy.class.getName());
        configuration.setBlockWhenExhausted(true);
        configuration.setMaxWaitMillis(BORROW_MAX_WAIT_INTERVAL);
        this.pool = new GenericObjectPool<Session>(
                new PooledSessionFactory(connect, trust, key, cache, bookmark, registry), configuration);
        final AbandonedConfig abandon = new AbandonedConfig();
        abandon.setUseUsageTracking(true);
        this.pool.setAbandonedConfig(abandon);
    }

    public DefaultSessionPool(final ConnectionService connect, final VaultRegistry registry, final PathCache cache,
            final TranscriptListener transcript, final Host bookmark, final GenericObjectPool<Session> pool) {
        this.connect = connect;
        this.transcript = transcript;
        this.cache = cache;
        this.bookmark = bookmark;
        this.registry = registry;
        this.pool = pool;
    }

    public static final class CustomPoolEvictionPolicy implements EvictionPolicy<Session<?>> {
        public CustomPoolEvictionPolicy() {
            //
        }

        @Override
        public boolean evict(final EvictionConfig config, final PooledObject<Session<?>> underTest,
                final int idleCount) {
            log.warn(String.format("Evict idle session %s from pool", underTest));
            return true;
        }
    }

    public DefaultSessionPool withMinIdle(final int count) {
        if (log.isDebugEnabled()) {
            log.debug(String.format("Configure with min idle %d", count));
        }
        pool.setMinIdle(count);
        return this;
    }

    public DefaultSessionPool withMaxIdle(final int count) {
        if (log.isDebugEnabled()) {
            log.debug(String.format("Configure with max idle %d", count));
        }
        pool.setMaxIdle(count);
        return this;
    }

    public DefaultSessionPool withMaxTotal(final int count) {
        if (log.isDebugEnabled()) {
            log.debug(String.format("Configure with max total %d", count));
        }
        pool.setMaxTotal(count);
        return this;
    }

    @Override
    public Session<?> borrow(final BackgroundActionState callback) throws BackgroundException {
        final Integer numActive = pool.getNumActive();
        if (numActive > POOL_WARNING_THRESHOLD) {
            log.warn(String.format("Possibly large number of open connections (%d) in pool %s", numActive, this));
        }
        try {
            /*
             * The number of times this action has been run
             */
            int repeat = 0;
            while (!callback.isCanceled()) {
                try {
                    if (log.isInfoEnabled()) {
                        log.info(String.format("Borrow session from pool %s", this));
                    }
                    final Session<?> session = pool.borrowObject();
                    if (log.isInfoEnabled()) {
                        log.info(String.format("Borrowed session %s from pool %s", session, this));
                    }
                    if (DISCONNECTED == features) {
                        features = new StatelessSessionPool(connect, session, cache, transcript, registry);
                    }
                    return session.withListener(transcript);
                } catch (IllegalStateException e) {
                    throw new ConnectionCanceledException(e);
                } catch (NoSuchElementException e) {
                    if (pool.isClosed()) {
                        throw new ConnectionCanceledException(e);
                    }
                    final Throwable cause = e.getCause();
                    if (null == cause) {
                        log.warn(String.format("Timeout borrowing session from pool %s. Wait for another %dms",
                                this, BORROW_MAX_WAIT_INTERVAL));
                        // Timeout
                        continue;
                    }
                    if (cause instanceof BackgroundException) {
                        final BackgroundException failure = (BackgroundException) cause;
                        log.warn(String.format("Failure %s obtaining connection for %s", failure, this));
                        if (diagnostics.determine(failure) == FailureDiagnostics.Type.network) {
                            final int max = Math.max(1, pool.getMaxIdle() - 1);
                            log.warn(String.format("Lower maximum idle pool size to %d connections.", max));
                            pool.setMaxIdle(max);
                            // Clear pool from idle connections
                            pool.clear();
                        }
                        throw failure;
                    }
                    log.error(String.format("Borrowing session from pool %s failed with %s", this, e));
                    throw new DefaultExceptionMappingService().map(cause);
                }
            }
            throw new ConnectionCanceledException();
        } catch (BackgroundException e) {
            throw e;
        } catch (Exception e) {
            if (e.getCause() instanceof BackgroundException) {
                throw ((BackgroundException) e.getCause());
            }
            throw new BackgroundException(e.getMessage(), e);
        }
    }

    @Override
    public void release(final Session<?> session, final BackgroundException failure) {
        if (log.isInfoEnabled()) {
            log.info(String.format("Release session %s to pool", session));
        }
        try {
            if (failure != null && diagnostics.determine(failure) == FailureDiagnostics.Type.network) {
                try {
                    session.interrupt();
                } catch (BackgroundException e) {
                    log.warn(String.format("Failure interrupting session %s prior releasing to pool. %s", session,
                            e.getDetail()));
                } finally {
                    try {
                        // Activation of this method decrements the active count and attempts to destroy the instance
                        pool.invalidateObject(session.removeListener(transcript));
                    } catch (Exception e) {
                        log.warn(String.format("Failure invalidating session %s in pool. %s", session,
                                e.getMessage()));
                    }
                }
            } else {
                pool.returnObject(session);
            }
        } catch (IllegalStateException e) {
            log.warn(String.format("Failed to release session %s. %s", session, e.getMessage()));
        }
    }

    @Override
    public void evict() {
        if (log.isInfoEnabled()) {
            log.info(String.format("Clear idle connections in pool %s", this));
        }
        pool.clear();
    }

    @Override
    public void shutdown() {
        try {
            if (log.isInfoEnabled()) {
                log.info(String.format("Close connection pool %s", this));
            }
            this.evict();
            pool.close();
        } catch (Exception e) {
            log.warn(String.format("Failure closing connection pool %s", e.getMessage()));
        } finally {
            registry.clear();
        }
    }

    @Override
    public Host getHost() {
        return bookmark;
    }

    @Override
    public PathCache getCache() {
        return cache;
    }

    @Override
    public VaultRegistry getVault() {
        return registry;
    }

    public int getNumActive() {
        return pool.getNumActive();
    }

    public int getNumIdle() {
        return pool.getNumIdle();
    }

    @Override
    public Session.State getState() {
        if (pool.isClosed()) {
            return Session.State.closed;
        }
        if (cache.isEmpty()) {
            return Session.State.opening;
        }
        if (0 == pool.getNumIdle()) {
            return Session.State.opening;
        }
        return Session.State.open;
    }

    @Override
    public <T> T getFeature(final Class<T> type) {
        if (DISCONNECTED == features) {
            return SessionFactory.create(bookmark, new DisabledX509TrustManager(), new DefaultX509KeyManager())
                    .getFeature(type);
        }
        return features.getFeature(type);
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("DefaultSessionPool{");
        sb.append("bookmark=").append(bookmark);
        sb.append('}');
        return sb.toString();
    }
}