mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Valery Kharseko
15 hours ago 2b8612f7fa2f5e00dff4ff2d8ac912c833e1f7ab
opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/CachedConnection.java
@@ -37,6 +37,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.LongFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -235,6 +236,54 @@
    private static volatile Executor closer = DIRECT_EXECUTOR;
    /**
     * Returns the bound of one attempt to establish a connection, as configured by the {@value
     * #CONNECT_TIMEOUT_PROPERTY} system property; 0 for the operator asking for no bound of its own.
     * A value beyond what a millisecond bound can carry is taken down to it: three of the four
     * dialects state their properties in milliseconds, and a value that saturates the conversion
     * bounds nothing.
     * <p>
     * Read here rather than at each connect so that every connection this backend establishes is
     * bounded by the same configured value - the borrows of this pool and the connection {@code
     * JDBCStorage} opens outside it for the tree catalog of a backend (#888) alike. A connect
     * bounded tighter than the login of the deployment takes is a backend that stops opening, and
     * one place to read the property is what keeps the two from drifting apart.
     */
    static long getConnectTimeoutSeconds() {
        return Math.min(getNonNegativeProperty(CONNECT_TIMEOUT_PROPERTY, DEFAULT_CONNECT_TIMEOUT_SECONDS, "s"),
            Integer.MAX_VALUE / 1000);
    }
    /**
     * Returns the deadline of a whole borrow, as configured by the {@value #POOL_TIMEOUT_PROPERTY}
     * system property; 0 for the operator asking for no deadline at all.
     * <p>
     * Read here for the reason the bound of a connect is: it is what a database taking no
     * connection for the moment is waited out for, and the connection {@code JDBCStorage} opens
     * outside this pool for the tree catalog of a backend (#888) waits it out for exactly as long -
     * one property, one meaning, whichever of the two is asking.
     */
    static long getPoolTimeoutSeconds() {
        return getNonNegativeProperty(POOL_TIMEOUT_PROPERTY, DEFAULT_POOL_TIMEOUT_SECONDS, "s");
    }
    /**
     * The moment a borrow of this length gives up, or {@link Long#MAX_VALUE} where it gives up
     * never - a property of 0, and a value so large that the milliseconds of it would overflow.
     * <p>
     * The sum is guarded and not only the product: a value under the clamp above but large enough
     * that the moment it names is past the end of the epoch would wrap to a deadline already behind
     * us, and a borrow configured to wait practically forever would give up on its first retryable
     * failure - the opposite of what was asked for.
     */
    static long deadlineOf(long startedAt, long poolTimeoutSeconds) {
        if (poolTimeoutSeconds == 0 || poolTimeoutSeconds >= Long.MAX_VALUE / 1000) {
            return Long.MAX_VALUE;
        }
        final long deadline = startedAt + poolTimeoutSeconds * 1000;
        return deadline < startedAt ? Long.MAX_VALUE : deadline;
    }
    /**
     * Returns the time after which an idle pooled connection is closed, as configured by the
     * {@value #TTL_PROPERTY} system property. An invalid value is ignored in favor of the default.
     * <p>
@@ -399,7 +448,10 @@
        if (borrowers <= pool.max()) {
            return;
        }
        final long poolTimeoutSeconds = getNonNegativeProperty(POOL_TIMEOUT_PROPERTY, DEFAULT_POOL_TIMEOUT_SECONDS, "s");
        // through the helper the borrows and the catalog connect both read it by: the same property
        // has to mean the same thing wherever it is asked, and a clamp that helper grows the day the
        // deadline needs one - getConnectTimeoutSeconds() already has one - must not be missed here
        final long poolTimeoutSeconds = getPoolTimeoutSeconds();
        final String wait = poolTimeoutSeconds == 0
            ? "waits for one to be returned for as long as that takes"
            : "waits up to " + poolTimeoutSeconds + "s for one to be returned and fails if none is";
@@ -1079,14 +1131,11 @@
        final Pool pool = poolOf(connectionString);
        final ConnectDialect dialect = ConnectDialect.of(connectionString);
        reportUnknownDialect(connectionString, dialect);
        final long connectTimeoutSeconds = Math.min(
            getNonNegativeProperty(CONNECT_TIMEOUT_PROPERTY, DEFAULT_CONNECT_TIMEOUT_SECONDS, "s"),
            Integer.MAX_VALUE / 1000);
        final long poolTimeoutSeconds = getNonNegativeProperty(POOL_TIMEOUT_PROPERTY, DEFAULT_POOL_TIMEOUT_SECONDS, "s");
        final long connectTimeoutSeconds = getConnectTimeoutSeconds();
        final long poolTimeoutSeconds = getPoolTimeoutSeconds();
        final long ttlMillis = getCacheTtlMillis();
        final long startedAt = System.currentTimeMillis();
        final long deadline = (poolTimeoutSeconds == 0 || poolTimeoutSeconds >= Long.MAX_VALUE / 1000)
            ? Long.MAX_VALUE : startedAt + poolTimeoutSeconds * 1000;
        final long deadline = deadlineOf(startedAt, poolTimeoutSeconds);
        // A thread already holding a connection is not made to wait for one: the two are held at
        // the same time, so waiting for the first to come back would wait for itself.
        final boolean reentrant = pool.heldByCurrentThread();
@@ -1493,16 +1542,71 @@
    // is indistinguishable from a hang. Throttled, since every operation of the backend borrows
    // through here and would otherwise log a copy of its own.
    private static void warnStall(String connectionString, int attempts, long startedAt, SQLException cause) {
        warnStall(connectionString, "", startedAt,
            waitedMs -> stallMessage(connectionString, attempts, waitedMs, cause));
    }
    /**
     * The same for a connect this class makes for somebody outside the pool - the connection the
     * tree catalog of a backend is written on (#888) - which waits for no pooled connection and
     * must not be described as one.
     * <p>
     * Throttled apart from the borrows of the same url as well as worded apart from them: the two
     * stall on the same database for the same reason, so a borrow that warned a moment ago would
     * otherwise silence the connect that is about to fail - the one of the two an operator has no
     * other line about.
     */
    static void warnStallOutsidePool(String connectionString, String what, int attempts, long startedAt,
            SQLException cause) {
        warnStall(connectionString, "|" + what, startedAt,
            waitedMs -> outsidePoolStallMessage(connectionString, what, attempts, waitedMs, cause));
    }
    private static void warnStall(String connectionString, String throttleKeySuffix, long startedAt,
            LongFunction<String> message) {
        final long now = System.currentTimeMillis();
        if (stallWarningDue(connectionString, throttleKeySuffix, startedAt, now)) {
            logger.warn(LocalizableMessage.raw("%s", message.apply(now - startedAt)));
        }
    }
    /**
     * Whether a stall of this wait is to be reported now: a wait shorter than
     * {@link #STALL_WARNING_AFTER_MS} is no stall yet, and one already reported for this url within
     * {@link #STALL_WARNING_INTERVAL_MS} is not reported again - every worker thread of a server
     * meets a database taking no connection at the same moment, and one line an interval is what
     * an operator can read.
     * <p>
     * Built apart from the logging of it for the reason {@link #stallMessage} is: what it has to
     * keep is a rule a test can hold it to, and the shipped path reaches the throttle only where a
     * connect really has stalled for a second. The suffix is what keeps the two waits apart - a
     * borrow of the pool and the connect the tree catalog of a backend is made on (#888) stall on
     * the same database for the same reason, and a borrow that reported a moment ago must not
     * silence the connect that is about to fail, which has no other line about it at all.
     * <p>
     * Filing the moment is part of deciding it, so that two threads asking at once report once.
     */
    static boolean stallWarningDue(String connectionString, String throttleKeySuffix, long startedAt, long now) {
        if (now - startedAt < STALL_WARNING_AFTER_MS) {
            return;
            return false;
        }
        final AtomicLong lastOfThisUrl =
            lastStallWarning.computeIfAbsent(safeUrl(connectionString), url -> new AtomicLong());
            lastStallWarning.computeIfAbsent(safeUrl(connectionString) + throttleKeySuffix, url -> new AtomicLong());
        final long last = lastOfThisUrl.get();
        if (now - last >= STALL_WARNING_INTERVAL_MS && lastOfThisUrl.compareAndSet(last, now)) {
            logger.warn(LocalizableMessage.raw("%s", stallMessage(connectionString, attempts, now - startedAt, cause)));
        }
        return now - last >= STALL_WARNING_INTERVAL_MS && lastOfThisUrl.compareAndSet(last, now);
    }
    /**
     * The stall of a connect made outside the pool, as it reaches the log. Built apart from the
     * logging of it for the reason {@link #stallMessage} is: the rule it has to keep - neither the
     * connection string nor the message of the driver reaches a log as it stands - is a rule a test
     * can hold it to.
     */
    static String outsidePoolStallMessage(String connectionString, String what, int attempts, long waitedMs,
            SQLException cause) {
        return String.format("%s takes no further connection: the %s connection of this backend is opened outside the"
            + " pool and has been retrying for %d ms (%d attempts), last error: %s", safeUrl(connectionString), what,
            waitedMs, attempts, redact(cause.getMessage(), connectionString));
    }
    /**