forked from mongodb/mongo-java-driver
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTimeoutContext.java
322 lines (275 loc) · 11 KB
/
TimeoutContext.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
/*
* 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.MongoOperationTimeoutException;
import com.mongodb.internal.time.StartTime;
import com.mongodb.internal.time.Timeout;
import com.mongodb.lang.Nullable;
import java.util.Objects;
import java.util.Optional;
import static com.mongodb.assertions.Assertions.assertNotNull;
import static com.mongodb.assertions.Assertions.assertNull;
import static com.mongodb.assertions.Assertions.isTrue;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
/**
* Timeout Context.
*
* <p>The context for handling timeouts in relation to the Client Side Operation Timeout specification.</p>
*/
public class TimeoutContext {
private final boolean isMaintenanceContext;
private final TimeoutSettings timeoutSettings;
@Nullable
private Timeout timeout;
@Nullable
private Timeout computedServerSelectionTimeout;
private long minRoundTripTimeMS = 0;
public static MongoOperationTimeoutException createMongoTimeoutException() {
return createMongoTimeoutException("Remaining timeoutMS is less than the servers minimum round trip time.");
}
public static MongoOperationTimeoutException createMongoTimeoutException(final String message) {
return new MongoOperationTimeoutException(message);
}
public static MongoOperationTimeoutException createMongoTimeoutException(final Throwable cause) {
return createMongoTimeoutException("Operation timed out: " + 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, calculateTimeout(timeoutSettings.getTimeoutMS()));
}
public TimeoutContext(final TimeoutSettings timeoutSettings) {
this(false, timeoutSettings, calculateTimeout(timeoutSettings.getTimeoutMS()));
}
public TimeoutContext(final TimeoutSettings timeoutSettings, @Nullable final Timeout timeout) {
this(false, timeoutSettings, timeout);
}
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;
}
/**
* Checks the expiry of the timeout.
*
* @return true if the timeout has been set and it has expired
*/
public boolean hasExpired() {
// Use timeout.remaining instead of timeout.hasExpired that measures in nanoseconds.
return timeout != null && !timeout.isInfinite() && timeout.remaining(MILLISECONDS) <= 0;
}
/**
* 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;
}
public Optional<MongoOperationTimeoutException> validateHasTimedOutForCommandExecution() {
if (hasTimedOutForCommandExecution()) {
return Optional.of(createMongoTimeoutException());
}
return Optional.empty();
}
private boolean hasTimedOutForCommandExecution() {
if (timeout == null || timeout.isInfinite()) {
return false;
}
long remaining = timeout.remaining(MILLISECONDS);
return remaining <= 0 || minRoundTripTimeMS > remaining;
}
/**
* 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) {
Long timeoutMS = timeoutSettings.getTimeoutMS();
if (timeoutMS == null) {
return alternativeTimeoutMS;
} else if (timeoutMS == 0) {
return timeoutMS;
} else {
return timeoutRemainingMS();
}
}
/**
* Calculates the minimum timeout value between two possible timeouts.
*
* @param alternativeTimeoutMS the alternative timeout
* @return the minimum value to use.
*/
public long calculateMin(final long alternativeTimeoutMS) {
Long timeoutMS = timeoutSettings.getTimeoutMS();
if (timeoutMS == null) {
return alternativeTimeoutMS;
} else if (timeoutMS == 0) {
return alternativeTimeoutMS;
} else if (alternativeTimeoutMS == 0) {
return timeoutRemainingMS();
} else {
return Math.min(timeoutRemainingMS(), alternativeTimeoutMS);
}
}
public TimeoutSettings getTimeoutSettings() {
return timeoutSettings;
}
public long getMaxAwaitTimeMS() {
return timeoutSettings.getMaxAwaitTimeMS();
}
public long getMaxTimeMS() {
long maxTimeMS = timeoutOrAlternative(timeoutSettings.getMaxTimeMS());
if (timeout == null || timeout.isInfinite()) {
return maxTimeMS;
}
if (minRoundTripTimeMS >= maxTimeMS) {
throw createMongoTimeoutException();
}
return maxTimeMS - minRoundTripTimeMS;
}
public long getMaxCommitTimeMS() {
return timeoutOrAlternative(timeoutSettings.getMaxCommitTimeMS());
}
public long getReadTimeoutMS() {
return timeoutOrAlternative(timeoutSettings.getReadTimeoutMS());
}
public long getWriteTimeoutMS() {
return timeoutOrAlternative(0);
}
public int getConnectTimeoutMs() {
return (int) calculateMin(getTimeoutSettings().getConnectTimeoutMS());
}
public void resetTimeout() {
assertNotNull(timeout);
timeout = calculateTimeout(timeoutSettings.getTimeoutMS());
}
/**
* Resets the timeout if this timeout context is being used by pool maintenance
*/
public void resetMaintenanceTimeout() {
if (isMaintenanceContext && timeout != null && !timeout.isInfinite()) {
timeout = calculateTimeout(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));
}
private long timeoutRemainingMS() {
assertNotNull(timeout);
if (timeout.hasExpired()) {
throw createMongoTimeoutException("The operation timeout has expired.");
}
return timeout.isInfinite() ? 0 : timeout.remaining(MILLISECONDS);
}
@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 calculateTimeout(@Nullable final Long timeoutMS) {
if (timeoutMS != null) {
return timeoutMS == 0 ? Timeout.infinite() : Timeout.expiresIn(timeoutMS, MILLISECONDS);
}
return null;
}
/**
* Returns the computed server selection timeout
*
* <p>Caches the computed server selection timeout if:
* <ul>
* <li>not in a maintenance context</li>
* <li>there is a timeoutMS, so to keep the same legacy behavior.</li>
* <li>the server selection timeout is less than the remaining overall timeout.</li>
* </ul>
*
* @return the timeout context
*/
public Timeout computeServerSelectionTimeout() {
Timeout serverSelectionTimeout = StartTime.now()
.timeoutAfterOrInfiniteIfNegative(getTimeoutSettings().getServerSelectionTimeoutMS(), MILLISECONDS);
if (isMaintenanceContext || !hasTimeoutMS()) {
return serverSelectionTimeout;
}
if (serverSelectionTimeout.orEarlier(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() {
return computedServerSelectionTimeout == null
? this : new TimeoutContext(false, timeoutSettings, computedServerSelectionTimeout);
}
public Timeout startWaitQueueTimeout(final StartTime checkoutStart) {
final long ms = getTimeoutSettings().getMaxWaitTimeMS();
return checkoutStart.timeoutAfterOrInfiniteIfNegative(ms, MILLISECONDS);
}
@Nullable
public Timeout getTimeout() {
return timeout;
}
}