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

com.sleepycat.je.rep.utilint.RepUtils Maven / Gradle / Ivy

Go to download

Berkeley DB Java Edition is a open source, transactional storage solution for Java applications. The Direct Persistence Layer (DPL) API is faster and easier to develop, deploy, and manage than serialized object files or ORM-based Java persistence solutions. The Collections API enhances the standard java.util.collections classes allowing them to be persisted to a local file system and accessed concurrently while protected by ACID transactions. Data is stored by serializing objects and managing class and instance data separately so as not to waste space. Berkeley DB Java Edition is the reliable drop-in solution for complex, fast, and scalable storage. Source for this release is in 'je-4.0.92-sources.jar', the Javadoc is located at 'http://download.oracle.com/berkeley-db/docs/je/4.0.92/'.

There is a newer version: 5.0.73
Show 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.rep.utilint;

import static com.sleepycat.je.rep.utilint.BinaryProtocolStatDefinition.N_MESSAGES_WRITTEN;
import static com.sleepycat.je.rep.utilint.BinaryProtocolStatDefinition.N_WRITE_NANOS;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.ReplicaConsistencyPolicy;
import com.sleepycat.je.dbi.DbConfigManager;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.rep.NoConsistencyRequiredPolicy;
import com.sleepycat.je.rep.ReplicationNetworkConfig;
import com.sleepycat.je.rep.TimeConsistencyPolicy;
import com.sleepycat.je.rep.net.DataChannel;
import com.sleepycat.je.rep.net.DataChannelFactory;
import com.sleepycat.je.rep.net.DataChannelFactory.ConnectOptions;
import com.sleepycat.je.rep.utilint.net.SimpleDataChannel;
import com.sleepycat.je.utilint.PropUtil;
import com.sleepycat.je.utilint.StatGroup;
import com.sleepycat.je.utilint.TestHook;
import com.sleepycat.je.utilint.TestHookExecute;

public class RepUtils {

    public static final boolean DEBUG_PRINT_THREAD = true;
    public static final boolean DEBUG_PRINT_TIME = true;

    /**
     * Maps from uppercase ReplicaConsistencyPolicy name to the policy's
     * format.
     */
    private static final Map>
        consistencyPolicyFormats =
            new HashMap<>();
    static {
        addConsistencyPolicyFormat(TimeConsistencyPolicy.NAME,
                                   new TimeConsistencyPolicyFormat());
        addConsistencyPolicyFormat(NoConsistencyRequiredPolicy.NAME,
                                   new NoConsistencyRequiredPolicyFormat());
    }

    /*
     * Canonical channel instance used to indicate that this is the last
     * instance of a channel in a channel queue and that the queue is
     * effectively closed. This value is typically used during a soft shutdown
     * of a thread to cause the thread waiting on the queue to wake up and
     * take notice.
     */
    public final static DataChannel CHANNEL_EOF_MARKER;

    static {
        try {
            CHANNEL_EOF_MARKER = new SimpleDataChannel(SocketChannel.open());
        } catch (IOException e) {
            throw EnvironmentFailureException.unexpectedException(e);
        }
    }

    /**
     * If not null, called by openSocketChannel with the connect options before
     * opening the socket -- for unit testing.
     */
    public static volatile TestHook openSocketChannelHook;

    /**
     * Define a new ConsistencyPolicyFormat.  Should only be called outside of
     * this class to add support custom policies for testing.  Must be called
     * when the system is quiescent, since the map is unsynchronized.
     *
     * @param name must be the first part of the policy string with a
     * non-letter delimiter following it, or must be the entire policy string.
     *
     * @param format to register.
     */
    public static void
        addConsistencyPolicyFormat(final String name,
                                   final ConsistencyPolicyFormat format) {
        consistencyPolicyFormats.put
            (name.toUpperCase(java.util.Locale.ENGLISH), format);
    }

    /**
     * ReplicaConsistencyPolicy must be stored as a String for use with
     * ReplicationConfig and je.properties.  ConsistencyPolicyFormat is an
     * internal handler that formats and parses the string representation of
     * the policy.  Only a fixed number of string-representable policies are
     * supported. Other policies that are not string-representable can only be
     * used in TransactionConfig, not ReplicationConfig.  For testing only, we
     * allow defining new custom policies.
     */
    public interface
        ConsistencyPolicyFormat {

        String policyToString(final T policy);

        T stringToPolicy(final String string);
    }

    private static class TimeConsistencyPolicyFormat
        implements ConsistencyPolicyFormat {

        @Override
        public String policyToString(final TimeConsistencyPolicy policy) {
            return policy.getName() +
                "(" + policy.getPermissibleLag(TimeUnit.MILLISECONDS) +
                " ms," + policy.getTimeout(TimeUnit.MILLISECONDS) +
                " ms)";
        }

        @Override
        public TimeConsistencyPolicy stringToPolicy(final String string) {
            /* Format: (, ) */
            String args =
                string.substring(TimeConsistencyPolicy.NAME.length());
            if (args.charAt(0) != '(' ||
                args.charAt(args.length()-1) != ')') {
                throw new IllegalArgumentException
                    ("Incorrect property value syntax: " + string);
            }
            int arg1 = args.indexOf(',');
            if (arg1 == -1) {
                throw new IllegalArgumentException
                    ("Incorrect property value syntax: " + string);
            }
            int lag = PropUtil.parseDuration(args.substring(1, arg1));
            int arg2 = args.indexOf(')');
            if (arg2 == -1) {
                throw new IllegalArgumentException
                    ("Incorrect property value syntax: " + string);
            }
            int timeout =
                PropUtil.parseDuration(args.substring(arg1 + 1, arg2));
            return new TimeConsistencyPolicy
                (lag, TimeUnit.MILLISECONDS, timeout, TimeUnit.MILLISECONDS);
        }
    }

    private static class NoConsistencyRequiredPolicyFormat
        implements ConsistencyPolicyFormat {

        @Override
        public String
            policyToString(final NoConsistencyRequiredPolicy policy) {
            return NoConsistencyRequiredPolicy.NAME;
        }

        @Override
        public NoConsistencyRequiredPolicy
            stringToPolicy(final String string) {
            return NoConsistencyRequiredPolicy.NO_CONSISTENCY;
        }
    }

    /**
     * Converts a policy into a string suitable for use as a property value
     * in a je.properties file or elsewhere.
     *
     * @param policy the policy being converted.
     *
     * @return the formatted string representing the policy.
     *
     * @throws IllegalArgumentException if the specific policy does not have a
     * property value format, via ReplicationConfig(Properties) ctor and
     * setter.
     *
     * @see #getReplicaConsistencyPolicy(String)
     */
    @SuppressWarnings("unchecked")
    public static String getPropertyString(ReplicaConsistencyPolicy policy)
        throws IllegalArgumentException {

        @SuppressWarnings("rawtypes")
        ConsistencyPolicyFormat format =
            consistencyPolicyFormats.get(policy.getName().toUpperCase());
        if (format == null) {
            throw new IllegalArgumentException
                ("Policy: " + policy + " cannot be used as a property");
        }
        return format.policyToString(policy);
    }

    /**
     * Converts a property string into a policy instance.
     *
     * @param propertyValue the formatted string representing the policy.
     *
     * @return the policy computed from the string
     *
     * @throws IllegalArgumentException via ReplicationConfig(Properties) ctor
     * and setter.
     */
    public static ReplicaConsistencyPolicy
        getReplicaConsistencyPolicy(String propertyValue)
        throws IllegalArgumentException {

        final String upperCasePropertyValue =
            propertyValue.toUpperCase(java.util.Locale.ENGLISH);
        for (final Map.Entry> entry :
                 consistencyPolicyFormats.entrySet()) {
            final String name = entry.getKey();
            if (upperCasePropertyValue.equals(name) ||
                (upperCasePropertyValue.startsWith(name) &&
                 upperCasePropertyValue.length() > name.length() &&
                 !Character.isLetter(
                     upperCasePropertyValue.charAt(name.length())))) {
                ConsistencyPolicyFormat format = entry.getValue();
                return format.stringToPolicy(propertyValue);
            }
        }
        throw new IllegalArgumentException
            ("Invalid consistency policy: " + propertyValue);
    }

    /**
     * Like CountDownLatch, but makes provision in the await for the await, or
     * more specifically the new awaitOrException method to be exited via an
     * exception.
     */
    public static class ExceptionAwareCountDownLatch extends CountDownLatch {
        /* The environment that may need to be invalidated. */
        final EnvironmentImpl envImpl;

        /* The exception (if any) that caused the latch to be released */
        private final AtomicReference terminatingException =
            new AtomicReference<>();

        public ExceptionAwareCountDownLatch(EnvironmentImpl envImpl,
                                            int count) {
            super(count);
            this.envImpl = envImpl;
        }

        /**
         * The method used to free an await, ensuring that it throws an
         * exception at the awaitOrException.
         *
         * @param e the exception to be wrapped in a DatabaseException
         * and thrown.
         */
        public void releaseAwait(Exception e) {
            terminatingException.compareAndSet(
                null, prepareTerminatingException(e, envImpl));
            for (long count = getCount(); count > 0; count--) {
                countDown();
            }
            assert(getCount() == 0);
        }

        /**
         * Blocks, waiting for the latch to count down to zero, or until an
         * {@code Exception} is provided.  The exception is thrown in every
         * thread that is waiting in this method.
         *
         * @see #releaseAwait
         */
        public boolean awaitOrException(long timeout, TimeUnit unit)
            throws InterruptedException,
                   DatabaseException {

            boolean done = super.await(timeout, unit);
            if (!done) {
                return done;
            }
            final DatabaseException e = terminatingException.get();
            if (e != null) {
                throw addLocalStackTrace(e);
            }
            return done;
        }

        public void awaitOrException()
            throws InterruptedException,
                   DatabaseException {
            awaitOrException(Integer.MAX_VALUE, TimeUnit.SECONDS);
        }

        /**
         * DO NOT use this method. Use awaitOrException instead, so that any
         * outstanding exceptions are thrown.
         */
        @Override
        @Deprecated
        public boolean await(long timeout, TimeUnit unit) {
            throw EnvironmentFailureException.unexpectedState
                ("Use awaitOrException() instead of await");
        }
    }

    /**
     * Like {@code LinkedBlockingQueue}, but provides a {@code
     * pollOrException()} method that should be used instead of {@code poll()},
     * so that callers don't have to treat exception cases specially.
     *
     * @see ExceptionAwareCountDownLatch
     */
    @SuppressWarnings("serial")
    public static class ExceptionAwareBlockingQueue
        extends LinkedBlockingQueue {

        final EnvironmentImpl envImpl;
        final T dummyValue;

        private final AtomicReference terminatingException =
            new AtomicReference<>();

        public ExceptionAwareBlockingQueue(EnvironmentImpl envImpl,
                                           T dummyValue) {
            super();
            this.envImpl = envImpl;
            this.dummyValue = dummyValue;
        }

        public void releasePoll(Exception e) {
            terminatingException.compareAndSet(
                null, prepareTerminatingException(e, envImpl));
            add(dummyValue);
        }

        public T pollOrException(long timeout, TimeUnit unit)
            throws InterruptedException,
            DatabaseException {

            T value = super.poll(timeout, unit);
            if (value == null) {
                return value;
            }
            final DatabaseException e = terminatingException.get();
            if (e != null) {
                throw addLocalStackTrace(e);
            }
            return value;
        }

        /**
         * (Use {@link #pollOrException} instead.
         */
        @Override
        @Deprecated
        public T poll(long timeout, TimeUnit unit) {
            throw EnvironmentFailureException.unexpectedState
                ("Use pollOrException() instead of poll()");
        }
    }

    /**
     * The terminating exception is wrapped in an EFE if it is not already a
     * DatabaseException (which is unexpected).  Also text is added to the
     * message indicating it was thrown by an HA thread, since it will often be
     * re-thrown in an app thread and the stack trace may be confusing.
     */
    private static DatabaseException prepareTerminatingException(
        final Exception e,
        final EnvironmentImpl envImpl) {

        if (e == null) {
            return null;
        }

        final DatabaseException de =
            (e instanceof DatabaseException) ?
            ((DatabaseException) e) :
            EnvironmentFailureException.unexpectedException(envImpl, e);

        de.addErrorMessage(
            "Originally thrown by HA thread: " +
            Thread.currentThread().getName());

        return de;
    }

    /**
     * Ideally we should wrap the exception before rethrowing in a different
     * thread, but this confuses exception handlers that call getCause.  So
     * instead we add the per-thread local stack trace to the message.
     */
    private static DatabaseException addLocalStackTrace(DatabaseException e) {
        e.addRethrownStackTrace();
        return e;
    }

    /**
     * Forces a shutdown of a blocking channel ignoring any errors that may be
     * encountered in the process.
     *
     * @param namedChannel the channel to be shutdown
     */
    public static void shutdownChannel(NamedChannel namedChannel) {
        if (namedChannel == null) {
            return;
        }
        shutdownChannel(namedChannel.getChannel());
    }

    public static void shutdownChannel(DataChannel channel) {
        if (channel == null) {
            return;
        }

        if (!channel.isBlocking()) {
            throw new IllegalStateException(
                    "Unexpected non-blocking channel for shutting down.");
        }

        /*
         * For SSL, shutting down the socket before shutting down the
         * channel is a no-no.  That results in SSLExceptions being
         * thrown due to missing close_notify alerts.
         */
        try {
            channel.close();
        } catch (IOException e) {
            /* Ignore */
        }
    }

    /**
     * Create a socket channel with the designated properties.
     *
     * The motivation and use of the localAddr parameter bears some
     * explanation. Explicit specification of the localAddr is not necessary
     * when the two nics are on separate subnets and the routing rules will
     * automatically choose the correct nic. In fact routing rules, could even
     * be used for such communication even when they are on the same subnet,
     * but such configuration can be brittle and hard to maintain.
     *
     * Consider the following illustrative example with two nodes
     * n1, n2, with two nics each labeled 0 and 1. We would like to constrain
     * all HA traffic between n1 and n2 to nics n1(1) and n2(1) leaving the
     * other two nics for application use, e.g. KV RMI requests.
     *
     * If n1 is the master and its ReplicationConfig.NODE_HOST_PORT specified
     * n1(1) it would listen on n1(1) for all HA traffic. n2 (the replica) has
     * two choices for sending traffic over to n2: n2(0) or n2(1), depending on
     * the routing rules that were set up. By binding conections locally (as is
     * done with this change) on the replica to n2(1) we guarantee that the
     * traffic to n1 will go over n2(1), so that connections always have the
     * desired endpoints n2(1) -> n1(1).
     *
     * @param addr the remote endpoint socket address
     * @param localAddr the local address the socket is bound to it may be null
     * @param connectOpts connect options to be applied to the channel
     * @return the connected channel
     *
     */
    public static SocketChannel openSocketChannel(InetSocketAddress addr,
                                                  InetSocketAddress localAddr,
                                                  ConnectOptions connectOpts)
        throws IOException {

        assert TestHookExecute.doHookIfSet(openSocketChannelHook, connectOpts);

        final SocketChannel channel = SocketChannel.open();
        final boolean blocking = connectOpts.getBlocking();
        channel.configureBlocking(blocking);

        final Socket socket = channel.socket();
        if (connectOpts.getReceiveBufferSize() != 0) {
            socket.setReceiveBufferSize(connectOpts.getReceiveBufferSize());
        }
        socket.setTcpNoDelay(connectOpts.getTcpNoDelay());
        socket.setSoTimeout(connectOpts.getReadTimeout());
        socket.setReuseAddress(connectOpts.getReuseAddr());
        socket.bind(localAddr);

        if (blocking) {
            socket.connect(addr, connectOpts.getOpenTimeout());
        } else {
            channel.connect(addr);
        }
        return channel;
    }

    /**
     * Create a Data channel with the designated properties
     *
     * @param addr the remote endpoint socket address
     * @param factory DataChannel factory for channel creation
     * @param connectOpts connect options to be applied to the channel
     * @return the connected channel
     *
     */
    public static DataChannel openBlockingChannel(InetSocketAddress addr,
                                                  DataChannelFactory factory,
                                                  ConnectOptions connectOpts)
        throws IOException {
        return factory.connect(addr, null, connectOpts);
    }

    /**
     * Chains an old outstanding exception to the tail of a new one, so it's
     * not lost.
     *
     * @param newt the new throwable
     * @param oldt the old throwable
     * @return the new throwable extended with the old cause
     */
    public static Throwable chainExceptionCause(Throwable newt,
                                                Throwable oldt) {
        /* Don't lose the original exception */
        Throwable tail = newt;
        while (tail.getCause() != null) {
            tail = tail.getCause();
        }
        tail.initCause(oldt);
        return newt;
    }

    public static String writeTimesString(StatGroup stats) {
        long nMessagesWritten = stats.getLong(N_MESSAGES_WRITTEN);
        long nWriteNanos = stats.getLong(N_WRITE_NANOS);

        long avgWriteNanos =
            (nMessagesWritten <= 0) ? 0 : (nWriteNanos / nMessagesWritten);

        return String.format(" write time: %, dms Avg write time: %,dus",
                             nWriteNanos / 1000000, avgWriteNanos / 1000);
    }

    /**
     * Read replication access properties from a property file.
     *
     * @param props a Properties object into which the properties will be stored
     * @param accessPropsFile an abstract File naming a file containing property
     *        settings.
     * @return the input properties object, updated with the property settings
     *        found in the file.
     * @throws IllegalArgumentException if the accessPropsFile contains
     *        invalid settings.
     */
    public static Properties populateNetProps(Properties props,
                                              File accessPropsFile) {

        Properties rawProps = new Properties();
        DbConfigManager.applyFileConfig(accessPropsFile, rawProps,
                                        true); //forReplication

        /* filter out the properties that are not relevant */
        ReplicationNetworkConfig.applyRepNetProperties(rawProps, props);
        return props;
    }

    /**
     * A simple debugging utility used to obtain information about the
     * execution environment that's only available through some system utility,
     * like netstat, or jps, etc. It's up to the caller to ensure the
     * availability of the utility and ensure that it's on the search path.
     *
     * @param args the arguments to a ProcessBuilder with args[0] being the
     * command and args[1-...] being its args
     *
     * @return a string denoting the output from the command. Or a string
     * prefixed by the word EXCEPTION, if an exception was encountered,
     * followed by the exception class name and exception message.
     */
    public static String exec(String... args) {

        final ByteArrayOutputStream bao = new ByteArrayOutputStream(1024);
        final PrintStream output = new PrintStream(bao);

        try {

            final ProcessBuilder builder = new ProcessBuilder(args);
            builder.redirectErrorStream(true);

            final Process process = builder.start();
            final InputStream is = process.getInputStream();
            final InputStreamReader isr = new InputStreamReader(is);
            final BufferedReader br = new BufferedReader(isr);

            String line;
            while ((line = br.readLine()) != null) {
                output.println(line);
            }

        } catch (Exception e) {
            output.println("EXCEPTION:" + " class:" + e.getClass().getName() +
                           " message:" + e.getMessage());
        }

        return bao.toString();
    }

    /*
     * Used to create deliberate clock skews for testing purposes. Replicator
     * code should use it instead of invoking System.currentTimeMillis()
     * directly.
     */
    public static class Clock {
        private final int skewMs;

        public Clock(int skewMs) {
            this.skewMs = skewMs;
        }

        public long currentTimeMillis() {
            return System.currentTimeMillis() + skewMs;
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy