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

com.sleepycat.je.rep.utilint.RepUtils 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.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());
        InetSocketAddress bindAddr = connectOpts.getBindAnyLocalAddr() ? null : localAddr;
        socket.bind(bindAddr);

        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 - 2024 Weber Informatics LLC | Privacy Policy