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

com.sleepycat.je.dbi.DbEnvPool Maven / Gradle / Ivy

The newest version!
/*-
 * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle Berkeley
 * DB Java Edition made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle Berkeley DB Java Edition for a copy of the
 * license and additional information.
 */

package com.sleepycat.je.dbi;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DbInternal;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.EnvironmentLockedException;
import com.sleepycat.je.EnvironmentMutableConfig;
import com.sleepycat.je.EnvironmentNotFoundException;
import com.sleepycat.je.latch.LatchSupport;
import com.sleepycat.je.utilint.TestHook;
import com.sleepycat.je.utilint.TestHookExecute;

/**
 * Singleton collection of environments.  Responsible for environment open and
 * close, supporting this from multiple threads by synchronizing on the pool.
 *
 * To avoid multiple environment openings from being blocked by recovery in
 * getEnvironment(), the EnvironmentImpl constructor is broken into two parts,
 * with the second part (EnvironmentImpl.finishInit) doing the recovery.
 *
 * When synchronizing on two or more of the following objects the
 * synchronization order must be as follows.  Synchronization is not performed
 * in constructors, of course, because no other thread can access the object.
 *
 * Synchronization order:  Environment, DbEnvPool, EnvironmentImpl, Evictor
 */
public class DbEnvPool {
    /* Singleton instance. */
    private static DbEnvPool pool = new DbEnvPool();

    /*
     * Collection of environment handles, mapped by canonical directory
     * name->EnvironmentImpl object.
     */
    private final Map envs;

    /* Environments (subset of envs) that share the global cache. */
    private final Set sharedCacheEnvs;

    /* Test hook, during Environment creation. */
    private TestHook beforeFinishInitHook;

    /**
     * Enforce singleton behavior.
     */
    private DbEnvPool() {
        envs = new HashMap();
        sharedCacheEnvs = new HashSet();
    }

    /**
     * Access the singleton instance.
     */
    public static DbEnvPool getInstance() {
        return pool;
    }

    public void setBeforeFinishInitHook(TestHook hook) {
        beforeFinishInitHook = hook;
    }

    public synchronized int getNSharedCacheEnvironments() {
        return sharedCacheEnvs.size();
    }

    private EnvironmentImpl getAnySharedCacheEnv() {
        Iterator iter = sharedCacheEnvs.iterator();
        return iter.hasNext() ? iter.next() : null;
    }

    /**
     * Find a single environment, used by Environment handles and by command
     * line utilities.
     *
     * @return a non-null EnvironmentImpl.
     */
    public EnvironmentImpl getEnvironment(File envHome,
                                          EnvironmentConfig config,
                                          boolean checkImmutableParams,
                                          RepConfigProxy repConfigProxy)
        throws EnvironmentNotFoundException, EnvironmentLockedException {

        String environmentKey = null;
        EnvironmentImpl envImpl = null;
        synchronized (this) {
            environmentKey = getEnvironmentMapKey(envHome);
            envImpl = envs.get(environmentKey);

            if (envImpl != null) {

                /*
                 * If the envImpl intance returned is standalone, but users are
                 * actually creating a replicated environment, throw out an
                 * UnsupportedOperationException. We needn't worry about the
                 * read only property, since a replicated environment can't be
                 * read only.
                 */
                if (!envImpl.isReplicated() && (repConfigProxy != null)) {
                    throw new UnsupportedOperationException
                       ("This environment was previously opened as a " +
                        "standalone environment. It cannot be re-opened for " +
                        "replication.");
                }

                /*
                 * If the envImpl instance returned is replicated, but users
                 * are actually creating a standalone environment and it is not
                 * read only, then throw out an UnsupportedOperationException.
                 */
                if (envImpl.isReplicated() && (repConfigProxy == null) &&
                    !config.getReadOnly()) {
                    throw new UnsupportedOperationException
                        ("This environment was previously opened for " +
                         "replication. It cannot be re-opened in read/write " +
                         "mode for standalone operation.");
                }

                /*
                 * If envImpl is used for an Arbiter.
                 */
                if (envImpl.isArbiter()) {
                    throw new UnsupportedOperationException(
                        "An Arbiter is currently using " +
                        "this directory. " + envHome.getAbsolutePath());
                }

                envImpl.checkIfInvalid();

                if (checkImmutableParams) {

                    /*
                     * If a non-null configuration parameter was passed to the
                     * Environment ctor and the underlying EnvironmentImpl
                     * already exists, check that the configuration parameters
                     * specified match those of the currently open environment.
                     * An exception is thrown if the check fails.
                     *
                     * Don't do this check if we create the environment here
                     * because the creation might modify the parameters, which
                     * would create a Catch-22 in terms of validation.  For
                     * example, je.maxMemory will be overridden if the JVM's
                     * -mx flag is less than that setting, so the new resolved
                     * config parameters won't be the same as the passed
                     * in config.
                     */
                    envImpl.checkImmutablePropsForEquality
                        (DbInternal.getProps(config));
                }
                /* Successful, increment reference count */
                envImpl.incOpenCount();
            } else {

                /*
                 * If a shared cache is used, get another (any other,
                 * doesn't matter which) environment that is sharing the
                 * global cache.
                 */
                EnvironmentImpl sharedCacheEnv = config.getSharedCache() ?
                    getAnySharedCacheEnv() : null;

                /*
                 * Environment must be instantiated. If it can be created,
                 * the configuration must have allowCreate set.  Note that
                 * the environment is added to the SharedEvictor before the
                 * EnvironmentImpl ctor returns, by
                 * RecoveryManager.buildTree.
                 */
                envImpl =
                    (repConfigProxy == null) ?
                     new EnvironmentImpl(envHome, config, sharedCacheEnv) :
                     loadRepImpl(envHome, config, sharedCacheEnv,
                                 repConfigProxy);
                assert config.getSharedCache() == envImpl.getSharedCache();

                envImpl.incOpenCount();
                envs.put(environmentKey, envImpl);
                addToSharedCacheEnvs(envImpl);
            }
        }

        /*
         * An new EnvironmentImpl was created. Call finishInit outside the
         * synchronized block to support concurrent recovery for more than one
         * environment.
         *
         * Note that finishInit must be called even if an existing envImpl was
         * found, because initialization (recovery) for that envImpl may not be
         * complete.  finishInit is synchronized and this ensures that recovery
         * will be complete when it returns.
         *
         * If this environment finishInit() fails in any way, make sure it
         * is removed from the envs map. If it isn't, it will block all
         * future attempts to create the environment.
         */
        TestHookExecute.doHookIfSet(beforeFinishInitHook, envImpl);
        boolean success = false;
        try {
            if (envImpl.finishInit(config)) {
                /* Initialization (recovery) was performed. */
                synchronized(this) {
                    finishAdditionOfSharedCacheEnv(envImpl);
                }
            }
            success = true;
        } finally {
            if (!success) {
                synchronized(this) {
                    envs.remove(environmentKey);
                    sharedCacheEnvs.remove(envImpl);
                }
            }
        }

        return envImpl;
    }

    /**
     * Use reflection to create a RepImpl, to avoid introducing HA compilation
     * dependencies to non-replication code.
     */
     private EnvironmentImpl loadRepImpl(File envHome,
                                         EnvironmentConfig config,
                                         EnvironmentImpl sharedCacheEnv,
                                         RepConfigProxy repConfigProxy)
        throws DatabaseException {

        final String repClassName = "com.sleepycat.je.rep.impl.RepImpl";
        final String envImplName = "com.sleepycat.je.dbi.EnvironmentImpl";
        final String repProxy = "com.sleepycat.je.dbi.RepConfigProxy";
        try {
            final Class repClass = Class.forName(repClassName);
            return (EnvironmentImpl)
                repClass.getConstructor(envHome.getClass(),
                                        config.getClass(),
                                        Class.forName(envImplName),
                                        Class.forName(repProxy)).
                newInstance(envHome, config, sharedCacheEnv, repConfigProxy);
        } catch (InvocationTargetException e) {
            if (e.getCause() instanceof RuntimeException) {
                /* Propate runtime exceptions thrown by the ctor. */
                throw (RuntimeException)e.getCause();
            }
            throw EnvironmentFailureException.unexpectedException(e);
        } catch (Exception e) {

            /*
             * This intentially violates our guideline for not catching
             * Exception in order to avoid many catches for all the checked
             * exceptions thrown by Class.forName and Class.getConstructor.  No
             * other methods in the try block throw checked exceptions.
             */
            throw EnvironmentFailureException.unexpectedException(e);
        }
    }

    /* Add this environment into sharedCache environments list. */
    private void addToSharedCacheEnvs(EnvironmentImpl envImpl)
        throws DatabaseException {

        if (envImpl.getSharedCache()) {
            if (sharedCacheEnvs.contains(envImpl)) {
                throw EnvironmentFailureException.unexpectedState();
            }
            sharedCacheEnvs.add(envImpl);
        }
    }

    /* Post-processing of SharedCacheEnv addition, after recovery is done. */
    private void finishAdditionOfSharedCacheEnv(EnvironmentImpl envImpl)
        throws DatabaseException {

        if (envImpl.getSharedCache()) {
            if (!sharedCacheEnvs.contains(envImpl)) {
                throw EnvironmentFailureException.unexpectedState();
            }
            assert envImpl.getEvictor().checkEnv(envImpl);
            resetSharedCache(-1, envImpl);
        }
    }

    /**
     * Called by EnvironmentImpl.setMutableConfig to perform the
     * setMutableConfig operation while synchronized on the DbEnvPool.
     *
     * In theory we shouldn't need to synchronize here when
     * envImpl.getSharedCache() is false; however, we synchronize
     * unconditionally to standardize the synchronization order and avoid
     * accidental deadlocks.
     */
    synchronized void setMutableConfig(EnvironmentImpl envImpl,
                                       EnvironmentMutableConfig mutableConfig)
        throws DatabaseException {

        envImpl.doSetMutableConfig(mutableConfig);
        if (envImpl.getSharedCache()) {
            resetSharedCache(envImpl.getMemoryBudget().getMaxMemory(),
                             envImpl);
        }
    }

    /**
     * Called by EnvironmentImpl.close to perform the close operation while
     * synchronized on the DbEnvPool.
     *
     * Synchronization on this DbEnvPool during the close is used to protect
     * its data structures.  Unfortunately, this means that a long checkpoint
     * during a close will block other closes and opens.  We may want to
     * improve this in the future.  However, at least there is a user
     * workaround: perform a full checkpoint before closing the environment.
     */
    synchronized void closeEnvironment(EnvironmentImpl envImpl,
                                       boolean doCheckpoint,
                                       boolean isAbnormalClose) {
        synchronized (envImpl) {
            /* Hold the reference count stable. */
            if (envImpl.decOpenCount()) {
                try {
                    envImpl.doClose(doCheckpoint, isAbnormalClose);
                } finally {
                    removeEnvironment(envImpl);
                }
            }
        }
    }

    /**
     * Called by EnvironmentImpl.closeAfterInvalid to perform the close
     * operation while synchronized on the DbEnvPool.
     */
    synchronized void closeEnvironmentAfterInvalid(EnvironmentImpl envImpl)
        throws DatabaseException {

        try {
            envImpl.doCloseAfterInvalid();
        } finally {
            removeEnvironment(envImpl);
        }
    }

    /**
     * Removes an EnvironmentImpl from the pool after it has been closed.  This
     * method is called while synchronized.  Note that the environment was
     * removed from the SharedEvictor by EnvironmentImpl.shutdownEvictor.
     */
    private void removeEnvironment(EnvironmentImpl envImpl) {

        final String environmentKey =
            getEnvironmentMapKey(envImpl.getEnvironmentHome());

        final boolean found = envs.remove(environmentKey) != null;

        if (sharedCacheEnvs.remove(envImpl)) {

            assert found && envImpl.getSharedCache();
            assert !envImpl.getEvictor().checkEnv(envImpl);

            if (sharedCacheEnvs.isEmpty()) {
                envImpl.getEvictor().shutdown();
                envImpl.getOffHeapCache().shutdown();
            } else {
                envImpl.getMemoryBudget().subtractCacheUsage();
                resetSharedCache(-1, null);
            }
        } else {
            assert !found || !envImpl.getSharedCache();
        }

        /*
         * Latch notes may only be cleared when there is no possibility that
         * any environment is open.
         */
        if (envs.isEmpty()) {
            LatchSupport.clear();
        }
    }

    /**
     * For unit testing only.
     */
    public synchronized void clear() {
        envs.clear();
    }

    public synchronized Collection getEnvImpls() {
        return envs.values();
    }

    public synchronized boolean isOpen(final File home) {
        return envs.containsKey(getEnvironmentMapKey(home));
    }

    /* Use the canonical path name for a normalized environment key. */
    String getEnvironmentMapKey(File file)
        throws DatabaseException {

        try {
            return file.getCanonicalPath();
        } catch (IOException e) {
            /* No env is available, can't throw EnvironmentFailedException. */
            throw EnvironmentFailureException.unexpectedException(e);
        }
    }

    /**
     * Resets the memory budget for all environments with a shared cache.
     *
     * @param newMaxMemory is the new total cache budget or is less than 0 if
     * the total should remain unchanged.  A total greater than zero is given
     * when it has changed via setMutableConfig.
     *
     * @param skipEnv is an environment that should not be reset, or null.
     * Non-null is passed when an environment has already been reset because
     * it was just created or the target of setMutableConfig.
     */
    private void resetSharedCache(long newMaxMemory, EnvironmentImpl skipEnv)
        throws DatabaseException {

        for (EnvironmentImpl envImpl : sharedCacheEnvs) {

            /*
             * To avoid spurious exceptions, don't reset invalid envs that have
             * not yet been removed.  They aren't usable, and we expect them
             * to be closed and removed very soon.
             */
            if (envImpl != skipEnv && envImpl.isValid()) {
                envImpl.getMemoryBudget().reset(newMaxMemory,
                                                false /*newEnv*/,
                                                envImpl.getConfigManager());
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy