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

com.mongodb.internal.TimeoutContext Maven / Gradle / Ivy

/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.mongodb.internal;

import com.mongodb.MongoClientException;
import com.mongodb.MongoOperationTimeoutException;
import com.mongodb.internal.connection.CommandMessage;
import com.mongodb.internal.time.StartTime;
import com.mongodb.internal.time.Timeout;
import com.mongodb.lang.Nullable;
import com.mongodb.session.ClientSession;

import java.util.Objects;
import java.util.function.LongConsumer;

import static com.mongodb.assertions.Assertions.assertNull;
import static com.mongodb.assertions.Assertions.isTrue;
import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE;
import static com.mongodb.internal.time.Timeout.ZeroSemantics.ZERO_DURATION_MEANS_INFINITE;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;

/**
 * Timeout Context.
 *
 * 

The context for handling timeouts in relation to the Client Side Operation Timeout specification.

*/ public class TimeoutContext { private final boolean isMaintenanceContext; private final TimeoutSettings timeoutSettings; @Nullable private Timeout timeout; @Nullable private Timeout computedServerSelectionTimeout; private long minRoundTripTimeMS = 0; @Nullable private MaxTimeSupplier maxTimeSupplier = null; public static MongoOperationTimeoutException createMongoRoundTripTimeoutException() { return createMongoTimeoutException("Remaining timeoutMS is less than or equal to the server's minimum round trip time."); } public static MongoOperationTimeoutException createMongoTimeoutException(final String message) { return new MongoOperationTimeoutException(message); } public static T throwMongoTimeoutException(final String message) { throw new MongoOperationTimeoutException(message); } public static MongoOperationTimeoutException createMongoTimeoutException(final Throwable cause) { return createMongoTimeoutException("Operation exceeded the timeout limit: " + cause.getMessage(), cause); } public static MongoOperationTimeoutException createMongoTimeoutException(final String message, final Throwable cause) { if (cause instanceof MongoOperationTimeoutException) { return (MongoOperationTimeoutException) cause; } return new MongoOperationTimeoutException(message, cause); } public static TimeoutContext createMaintenanceTimeoutContext(final TimeoutSettings timeoutSettings) { return new TimeoutContext(true, timeoutSettings, startTimeout(timeoutSettings.getTimeoutMS())); } public static TimeoutContext createTimeoutContext(final ClientSession session, final TimeoutSettings timeoutSettings) { TimeoutContext sessionTimeoutContext = session.getTimeoutContext(); if (sessionTimeoutContext != null) { TimeoutSettings sessionTimeoutSettings = sessionTimeoutContext.timeoutSettings; if (timeoutSettings.getGenerationId() > sessionTimeoutSettings.getGenerationId()) { throw new MongoClientException("Cannot change the timeoutMS during a transaction."); } // Check for any legacy operation timeouts if (sessionTimeoutSettings.getTimeoutMS() == null) { if (timeoutSettings.getMaxTimeMS() != 0) { sessionTimeoutSettings = sessionTimeoutSettings.withMaxTimeMS(timeoutSettings.getMaxTimeMS()); } if (timeoutSettings.getMaxAwaitTimeMS() != 0) { sessionTimeoutSettings = sessionTimeoutSettings.withMaxAwaitTimeMS(timeoutSettings.getMaxAwaitTimeMS()); } if (timeoutSettings.getMaxCommitTimeMS() != null) { sessionTimeoutSettings = sessionTimeoutSettings.withMaxCommitMS(timeoutSettings.getMaxCommitTimeMS()); } return new TimeoutContext(sessionTimeoutSettings); } return sessionTimeoutContext; } return new TimeoutContext(timeoutSettings); } // Creates a copy of the timeout context that can be reset without resetting the original. public TimeoutContext copyTimeoutContext() { return new TimeoutContext(getTimeoutSettings(), getTimeout()); } public TimeoutContext(final TimeoutSettings timeoutSettings) { this(false, timeoutSettings, startTimeout(timeoutSettings.getTimeoutMS())); } private TimeoutContext(final TimeoutSettings timeoutSettings, @Nullable final Timeout timeout) { this(false, timeoutSettings, timeout); } private TimeoutContext(final boolean isMaintenanceContext, final TimeoutSettings timeoutSettings, @Nullable final Timeout timeout) { this.isMaintenanceContext = isMaintenanceContext; this.timeoutSettings = timeoutSettings; this.timeout = timeout; } /** * Allows for the differentiation between users explicitly setting a global operation timeout via {@code timeoutMS}. * * @return true if a timeout has been set. */ public boolean hasTimeoutMS() { return timeoutSettings.getTimeoutMS() != null; } /** * Runs the runnable if the timeout is expired. * @param onExpired the runnable to run */ public void onExpired(final Runnable onExpired) { Timeout.nullAsInfinite(timeout).onExpired(onExpired); } /** * Sets the recent min round trip time * @param minRoundTripTimeMS the min round trip time * @return this */ public TimeoutContext minRoundTripTimeMS(final long minRoundTripTimeMS) { isTrue("'minRoundTripTimeMS' must be a positive number", minRoundTripTimeMS >= 0); this.minRoundTripTimeMS = minRoundTripTimeMS; return this; } @Nullable public Timeout timeoutIncludingRoundTrip() { return timeout == null ? null : timeout.shortenBy(minRoundTripTimeMS, MILLISECONDS); } /** * Returns the remaining {@code timeoutMS} if set or the {@code alternativeTimeoutMS}. * * @param alternativeTimeoutMS the alternative timeout. * @return timeout to use. */ public long timeoutOrAlternative(final long alternativeTimeoutMS) { if (timeout == null) { return alternativeTimeoutMS; } else { return timeout.call(MILLISECONDS, () -> 0L, (ms) -> ms, () -> throwMongoTimeoutException("The operation exceeded the timeout limit.")); } } public TimeoutSettings getTimeoutSettings() { return timeoutSettings; } public long getMaxAwaitTimeMS() { return timeoutSettings.getMaxAwaitTimeMS(); } public void runMaxTimeMS(final LongConsumer onRemaining) { if (maxTimeSupplier != null) { runWithFixedTimeout(maxTimeSupplier.get(), onRemaining); return; } if (timeout == null) { runWithFixedTimeout(timeoutSettings.getMaxTimeMS(), onRemaining); return; } timeout.shortenBy(minRoundTripTimeMS, MILLISECONDS) .run(MILLISECONDS, () -> {}, onRemaining, () -> { throw createMongoRoundTripTimeoutException(); }); } private static void runWithFixedTimeout(final long ms, final LongConsumer onRemaining) { if (ms != 0) { onRemaining.accept(ms); } } public void resetToDefaultMaxTime() { this.maxTimeSupplier = null; } /** * The override will be provided as the remaining value in * {@link #runMaxTimeMS}, where 0 is ignored. This is useful for setting timeout * in {@link CommandMessage} as an extra element before we send it to the server. * *

* NOTE: Suitable for static user-defined values only (i.e MaxAwaitTimeMS), * not for running timeouts that adjust dynamically (CSOT). */ public void setMaxTimeOverride(final long maxTimeMS) { this.maxTimeSupplier = () -> maxTimeMS; } /** * Disable the maxTimeMS override. This way the maxTimeMS will not * be appended to the command in the {@link CommandMessage}. */ public void disableMaxTimeOverride() { this.maxTimeSupplier = () -> 0; } /** * The override will be provided as the remaining value in * {@link #runMaxTimeMS}, where 0 is ignored. */ public void setMaxTimeOverrideToMaxCommitTime() { this.maxTimeSupplier = () -> getMaxCommitTimeMS(); } @VisibleForTesting(otherwise = PRIVATE) public long getMaxCommitTimeMS() { Long maxCommitTimeMS = timeoutSettings.getMaxCommitTimeMS(); return timeoutOrAlternative(maxCommitTimeMS != null ? maxCommitTimeMS : 0); } public long getReadTimeoutMS() { return timeoutOrAlternative(timeoutSettings.getReadTimeoutMS()); } public long getWriteTimeoutMS() { return timeoutOrAlternative(0); } public int getConnectTimeoutMs() { final long connectTimeoutMS = getTimeoutSettings().getConnectTimeoutMS(); return Math.toIntExact(Timeout.nullAsInfinite(timeout).call(MILLISECONDS, () -> connectTimeoutMS, (ms) -> connectTimeoutMS == 0 ? ms : Math.min(ms, connectTimeoutMS), () -> throwMongoTimeoutException("The operation exceeded the timeout limit."))); } public void resetTimeoutIfPresent() { if (hasTimeoutMS()) { timeout = startTimeout(timeoutSettings.getTimeoutMS()); } } /** * Resets the timeout if this timeout context is being used by pool maintenance */ public void resetMaintenanceTimeout() { if (!isMaintenanceContext) { return; } timeout = Timeout.nullAsInfinite(timeout).call(NANOSECONDS, () -> timeout, (ms) -> startTimeout(timeoutSettings.getTimeoutMS()), () -> startTimeout(timeoutSettings.getTimeoutMS())); } public TimeoutContext withAdditionalReadTimeout(final int additionalReadTimeout) { // Only used outside timeoutMS usage assertNull(timeout); // Check existing read timeout is infinite if (timeoutSettings.getReadTimeoutMS() == 0) { return this; } long newReadTimeout = getReadTimeoutMS() + additionalReadTimeout; return new TimeoutContext(timeoutSettings.withReadTimeoutMS(newReadTimeout > 0 ? newReadTimeout : Long.MAX_VALUE)); } @Override public String toString() { return "TimeoutContext{" + "isMaintenanceContext=" + isMaintenanceContext + ", timeoutSettings=" + timeoutSettings + ", timeout=" + timeout + ", minRoundTripTimeMS=" + minRoundTripTimeMS + '}'; } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final TimeoutContext that = (TimeoutContext) o; return isMaintenanceContext == that.isMaintenanceContext && minRoundTripTimeMS == that.minRoundTripTimeMS && Objects.equals(timeoutSettings, that.timeoutSettings) && Objects.equals(timeout, that.timeout); } @Override public int hashCode() { return Objects.hash(isMaintenanceContext, timeoutSettings, timeout, minRoundTripTimeMS); } @Nullable public static Timeout startTimeout(@Nullable final Long timeoutMS) { if (timeoutMS != null) { return Timeout.expiresIn(timeoutMS, MILLISECONDS, ZERO_DURATION_MEANS_INFINITE); } return null; } /** * Returns the computed server selection timeout * *

Caches the computed server selection timeout if: *

    *
  • not in a maintenance context
  • *
  • there is a timeoutMS, so to keep the same legacy behavior.
  • *
  • the server selection timeout is less than the remaining overall timeout.
  • *
* * @return the timeout context */ public Timeout computeServerSelectionTimeout() { Timeout serverSelectionTimeout = StartTime.now() .timeoutAfterOrInfiniteIfNegative(getTimeoutSettings().getServerSelectionTimeoutMS(), MILLISECONDS); if (isMaintenanceContext || !hasTimeoutMS()) { return serverSelectionTimeout; } if (timeout != null && Timeout.earliest(serverSelectionTimeout, timeout) == timeout) { return timeout; } computedServerSelectionTimeout = serverSelectionTimeout; return computedServerSelectionTimeout; } /** * Returns the timeout context to use for the handshake process * * @return a new timeout context with the cached computed server selection timeout if available or this */ public TimeoutContext withComputedServerSelectionTimeoutContext() { if (this.hasTimeoutMS() && computedServerSelectionTimeout != null) { return new TimeoutContext(false, timeoutSettings, computedServerSelectionTimeout); } return this; } public Timeout startWaitQueueTimeout(final StartTime checkoutStart) { final long ms = getTimeoutSettings().getMaxWaitTimeMS(); return checkoutStart.timeoutAfterOrInfiniteIfNegative(ms, MILLISECONDS); } @Nullable public Timeout getTimeout() { return timeout; } public interface MaxTimeSupplier { long get(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy