From 06d944db5e10b152415a1d878d97f98e00c9416d Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Thu, 28 Dec 2023 13:30:24 -0800 Subject: [PATCH 01/19] Add CSOT to GridFS operations. JAVA-5277 --- .../com/mongodb/internal/TimeoutContext.java | 2 +- .../client/CommandMonitoringTestHelper.java | 4 +- .../connection/TestCommandListener.java | 10 +- .../client/gridfs/GridFSFindPublisher.java | 16 -- .../gridfs/GridFSFindPublisherImpl.java | 7 - .../ClientSideOperationTimeoutProseTest.java | 29 ++- .../client/ReadConcernTest.java | 3 +- .../client/internal/BatchCursorFluxTest.java | 2 +- .../client/syncadapter/SyncGridFSBucket.java | 11 ++ .../ClientSideOperationTimeoutTest.java | 1 + .../mongodb/client/gridfs/GridFSBucket.java | 48 +++++ .../client/gridfs/GridFSBucketImpl.java | 171 +++++++++++++----- .../gridfs/GridFSDownloadStreamImpl.java | 42 ++++- .../client/gridfs/GridFSFindIterable.java | 24 --- .../client/gridfs/GridFSFindIterableImpl.java | 6 - .../client/gridfs/GridFSUploadStreamImpl.java | 40 +++- .../mongodb/client/gridfs/TimeoutUtils.java | 44 +++++ ...tractClientSideEncryptionDeadlockTest.java | 4 +- .../AbstractClientSideEncryptionTest.java | 3 +- ...tClientSideOperationsTimeoutProseTest.java | 131 +++++++++++++- .../com/mongodb/client/AbstractCrudTest.java | 3 +- .../client/AbstractRetryableReadsTest.java | 3 +- .../AbstractServerSelectionProseTest.java | 3 +- .../mongodb/client/AbstractUnifiedTest.java | 6 +- .../ClientSideOperationTimeoutProseTest.java | 7 + .../ClientSideOperationTimeoutTest.java | 13 +- .../com/mongodb/client/ReadConcernTest.java | 2 +- .../mongodb/client/unified/ErrorMatcher.java | 1 + .../client/unified/UnifiedCrudHelper.java | 11 +- .../client/unified/UnifiedGridFSHelper.java | 94 +++++++++- .../mongodb/client/unified/UnifiedTest.java | 13 +- .../client/unified/UnifiedTestUtils.java | 34 ++++ .../GridFSDownloadStreamSpecification.groovy | 29 +-- 33 files changed, 646 insertions(+), 171 deletions(-) create mode 100644 driver-sync/src/main/com/mongodb/client/gridfs/TimeoutUtils.java create mode 100644 driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestUtils.java diff --git a/driver-core/src/main/com/mongodb/internal/TimeoutContext.java b/driver-core/src/main/com/mongodb/internal/TimeoutContext.java index 010bfb2b0f6..2c1a5eee246 100644 --- a/driver-core/src/main/com/mongodb/internal/TimeoutContext.java +++ b/driver-core/src/main/com/mongodb/internal/TimeoutContext.java @@ -212,7 +212,7 @@ public int hashCode() { } @Nullable - private static Timeout calculateTimeout(@Nullable final Long timeoutMS) { + public static Timeout calculateTimeout(@Nullable final Long timeoutMS) { if (timeoutMS != null) { return timeoutMS == 0 ? Timeout.infinite() : Timeout.expiresIn(timeoutMS, MILLISECONDS); } diff --git a/driver-core/src/test/functional/com/mongodb/client/CommandMonitoringTestHelper.java b/driver-core/src/test/functional/com/mongodb/client/CommandMonitoringTestHelper.java index 8ba3a5b3851..3a99277ef65 100644 --- a/driver-core/src/test/functional/com/mongodb/client/CommandMonitoringTestHelper.java +++ b/driver-core/src/test/functional/com/mongodb/client/CommandMonitoringTestHelper.java @@ -120,11 +120,11 @@ static boolean isWriteCommand(final String commandName) { return asList("insert", "update", "delete").contains(commandName); } - public static void assertEventsEquality(final List expectedEvents, final List events) { + public static void assertEventsEquality(final List expectedEvents, final List events) { assertEventsEquality(expectedEvents, events, null); } - public static void assertEventsEquality(final List expectedEvents, final List events, + public static void assertEventsEquality(final List expectedEvents, final List events, @Nullable final Map lsidMap) { assertEquals(expectedEvents.size(), events.size()); diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/TestCommandListener.java b/driver-core/src/test/functional/com/mongodb/internal/connection/TestCommandListener.java index 0a2838c2d55..6e1dda106bd 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/TestCommandListener.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/TestCommandListener.java @@ -143,17 +143,17 @@ public CommandFailedEvent getCommandFailedEvent(final String commandName) { .orElseThrow(() -> new IllegalArgumentException(commandName + " not found in command failed event list")); } - public List getCommandStartedEvents() { + public List getCommandStartedEvents() { return getCommandStartedEvents(Integer.MAX_VALUE); } - private List getCommandStartedEvents(final int maxEvents) { + private List getCommandStartedEvents(final int maxEvents) { lock.lock(); try { - List commandStartedEvents = new ArrayList<>(); + List commandStartedEvents = new ArrayList<>(); for (CommandEvent cur : getEvents()) { if (cur instanceof CommandStartedEvent) { - commandStartedEvents.add(cur); + commandStartedEvents.add((CommandStartedEvent) cur); } if (commandStartedEvents.size() == maxEvents) { break; @@ -165,7 +165,7 @@ private List getCommandStartedEvents(final int maxEvents) { } } - public List waitForStartedEvents(final int numEvents) { + public List waitForStartedEvents(final int numEvents) { lock.lock(); try { while (!hasCompletedEvents(numEvents)) { diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/gridfs/GridFSFindPublisher.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/gridfs/GridFSFindPublisher.java index e7cecf230a4..75f7a7cac10 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/gridfs/GridFSFindPublisher.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/gridfs/GridFSFindPublisher.java @@ -16,12 +16,9 @@ package com.mongodb.reactivestreams.client.gridfs; -import com.mongodb.client.cursor.TimeoutMode; import com.mongodb.client.gridfs.model.GridFSFile; import com.mongodb.client.model.Collation; import com.mongodb.lang.Nullable; -import com.mongodb.reactivestreams.client.MongoCollection; -import com.mongodb.reactivestreams.client.MongoDatabase; import org.bson.conversions.Bson; import org.reactivestreams.Publisher; @@ -127,17 +124,4 @@ public interface GridFSFindPublisher extends Publisher { * @mongodb.driver.manual reference/method/cursor.batchSize/#cursor.batchSize Batch Size */ GridFSFindPublisher batchSize(int batchSize); - - /** - * Sets the timeoutMode for the cursor. - * - *

- * Requires the {@code timeout} to be set, either in the {@link com.mongodb.MongoClientSettings}, - * via {@link MongoDatabase} or via {@link MongoCollection} - *

- * @param timeoutMode the timeout mode - * @return this - * @since 4.x - */ - GridFSFindPublisher timeoutMode(TimeoutMode timeoutMode); } diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/gridfs/GridFSFindPublisherImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/gridfs/GridFSFindPublisherImpl.java index 020b82fee20..41ee872c05e 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/gridfs/GridFSFindPublisherImpl.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/gridfs/GridFSFindPublisherImpl.java @@ -16,7 +16,6 @@ package com.mongodb.reactivestreams.client.internal.gridfs; -import com.mongodb.client.cursor.TimeoutMode; import com.mongodb.client.gridfs.model.GridFSFile; import com.mongodb.client.model.Collation; import com.mongodb.lang.Nullable; @@ -93,12 +92,6 @@ public GridFSFindPublisher batchSize(final int batchSize) { return this; } - @Override - public GridFSFindPublisher timeoutMode(final TimeoutMode timeoutMode) { - wrapped.timeoutMode(timeoutMode); - return this; - } - @Override public void subscribe(final Subscriber s) { wrapped.subscribe(s); diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java index 3648fa29cee..878fe979fa6 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java @@ -19,16 +19,43 @@ import com.mongodb.MongoClientSettings; import com.mongodb.client.AbstractClientSideOperationsTimeoutProseTest; import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.gridfs.GridFSBucket; +import com.mongodb.reactivestreams.client.gridfs.GridFSBuckets; +import com.mongodb.reactivestreams.client.syncadapter.SyncGridFSBucket; import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient; +import org.junit.jupiter.api.Disabled; /** * See https://github.com/mongodb/specifications/blob/master/source/client-side-operations-timeout/tests/README.rst#prose-tests */ public final class ClientSideOperationTimeoutProseTest extends AbstractClientSideOperationsTimeoutProseTest { + private com.mongodb.reactivestreams.client.MongoClient wrapped; @Override protected MongoClient createMongoClient(final MongoClientSettings mongoClientSettings) { - return new SyncMongoClient(MongoClients.create(mongoClientSettings)); + wrapped = MongoClients.create(mongoClientSettings); + return new SyncMongoClient(wrapped); + } + + @Override + protected GridFSBucket createGridFsBucket(final MongoDatabase mongoDatabase, final String bucketName) { + return new SyncGridFSBucket(GridFSBuckets.create(wrapped.getDatabase(mongoDatabase.getName()), bucketName)); + } + + @Override + @Disabled("TODO (CSOT) - JAVA-4057") + public void testGridFSUploadViaOpenUploadStreamTimeout() { + } + + @Disabled("TODO (CSOT) - JAVA-4057") + @Override + public void testAbortingGridFsUploadStreamTimeout() { + } + + @Disabled("TODO (CSOT) - JAVA-4057") + @Override + public void testGridFsDownloadStreamTimeout() { } } diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ReadConcernTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ReadConcernTest.java index 15b1bc7f5cf..eef7cfbe9fe 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ReadConcernTest.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ReadConcernTest.java @@ -17,7 +17,6 @@ package com.mongodb.reactivestreams.client; import com.mongodb.ReadConcern; -import com.mongodb.event.CommandEvent; import com.mongodb.event.CommandStartedEvent; import com.mongodb.internal.connection.TestCommandListener; import org.bson.BsonDocument; @@ -65,7 +64,7 @@ public void shouldIncludeReadConcernInCommand() throws InterruptedException { .find()) .block(TIMEOUT_DURATION); - List events = commandListener.getCommandStartedEvents(); + List events = commandListener.getCommandStartedEvents(); BsonDocument commandDocument = new BsonDocument("find", new BsonString("test")) .append("readConcern", ReadConcern.LOCAL.asDocument()) diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/internal/BatchCursorFluxTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/internal/BatchCursorFluxTest.java index 410dfd02fc4..ebbd2069f70 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/internal/BatchCursorFluxTest.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/internal/BatchCursorFluxTest.java @@ -373,7 +373,7 @@ public void testBatchCursorReportsCursorErrors() { BsonDocument getMoreCommand = commandListener.getCommandStartedEvents().stream() .filter(e -> e.getCommandName().equals("getMore")) - .map(e -> ((CommandStartedEvent) e).getCommand()) + .map(CommandStartedEvent::getCommand) .findFirst() .get(); diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncGridFSBucket.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncGridFSBucket.java index a09b4ffbec3..5ed29caf4a0 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncGridFSBucket.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncGridFSBucket.java @@ -42,6 +42,7 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; import static com.mongodb.ClusterFixture.TIMEOUT_DURATION; import static com.mongodb.reactivestreams.client.syncadapter.ContextHelper.CONTEXT; @@ -79,6 +80,11 @@ public ReadConcern getReadConcern() { return wrapped.getReadConcern(); } + @Override + public Long getTimeout(final TimeUnit timeUnit) { + throw new UnsupportedOperationException("Not implemented yet!"); + } + @Override public GridFSBucket withChunkSizeBytes(final int chunkSizeBytes) { return new SyncGridFSBucket(wrapped.withChunkSizeBytes(chunkSizeBytes)); @@ -99,6 +105,11 @@ public GridFSBucket withReadConcern(final ReadConcern readConcern) { return new SyncGridFSBucket(wrapped.withReadConcern(readConcern)); } + @Override + public GridFSBucket withTimeout(final long timeout, final TimeUnit timeUnit) { + throw new UnsupportedOperationException("Not implemented yet!"); + } + @Override public GridFSUploadStream openUploadStream(final String filename) { return openUploadStream(filename, new GridFSUploadOptions()); diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/ClientSideOperationTimeoutTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/ClientSideOperationTimeoutTest.java index b3bceab35d2..91b877214cf 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/ClientSideOperationTimeoutTest.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/ClientSideOperationTimeoutTest.java @@ -53,6 +53,7 @@ public ClientSideOperationTimeoutTest(final String fileDescription, final String assumeFalse(testDescription.endsWith("createChangeStream on client")); assumeFalse(testDescription.endsWith("createChangeStream on database")); assumeFalse(testDescription.endsWith("createChangeStream on collection")); + assumeFalse("TODO (CSOT) - JAVA-4057", fileDescription.contains("GridFS")); checkSkipCSOTTest(fileDescription, testDescription); if (testDescription.equals("timeoutMS is refreshed for close")) { diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucket.java b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucket.java index c32f114844c..8b251199d67 100644 --- a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucket.java +++ b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucket.java @@ -23,12 +23,14 @@ import com.mongodb.client.ClientSession; import com.mongodb.client.gridfs.model.GridFSDownloadOptions; import com.mongodb.client.gridfs.model.GridFSUploadOptions; +import com.mongodb.lang.Nullable; import org.bson.BsonValue; import org.bson.conversions.Bson; import org.bson.types.ObjectId; import java.io.InputStream; import java.io.OutputStream; +import java.util.concurrent.TimeUnit; /** * Represents a GridFS Bucket @@ -76,6 +78,36 @@ public interface GridFSBucket { */ ReadConcern getReadConcern(); + /** + * The time limit for the full execution of an operation. + * + *

If not null the following deprecated options will be ignored: + * {@code waitQueueTimeoutMS}, {@code socketTimeoutMS}, {@code wTimeoutMS}, {@code maxTimeMS} and {@code maxCommitTimeMS}

+ * + *
    + *
  • {@code null} means that the timeout mechanism for operations will defer to using: + *
      + *
    • {@code waitQueueTimeoutMS}: The maximum wait time in milliseconds that a thread may wait for a connection to become + * available
    • + *
    • {@code socketTimeoutMS}: How long a send or receive on a socket can take before timing out.
    • + *
    • {@code wTimeoutMS}: How long the server will wait for the write concern to be fulfilled before timing out.
    • + *
    • {@code maxTimeMS}: The cumulative time limit for processing operations on a cursor. + * See: cursor.maxTimeMS.
    • + *
    • {@code maxCommitTimeMS}: The maximum amount of time to allow a single {@code commitTransaction} command to execute. + * See: {@link com.mongodb.TransactionOptions#getMaxCommitTime}.
    • + *
    + *
  • + *
  • {@code 0} means infinite timeout.
  • + *
  • {@code > 0} The time limit to use for the full execution of an operation.
  • + *
+ * + * @param timeUnit the time unit + * @return the timeout in the given time unit + * @since 4.x + */ + @Nullable + Long getTimeout(TimeUnit timeUnit); + /** * Create a new GridFSBucket instance with a new chunk size in bytes. * @@ -111,6 +143,22 @@ public interface GridFSBucket { */ GridFSBucket withReadConcern(ReadConcern readConcern); + /** + * Create a new GridFSBucket instance with the set time limit for the full execution of an operation. + * + *
    + *
  • {@code 0} means infinite timeout.
  • + *
  • {@code > 0} The time limit to use for the full execution of an operation.
  • + *
+ * + * @param timeout the timeout, which must be greater than or equal to 0 + * @param timeUnit the time unit + * @return a new GridFSBucket instance with the set time limit for the full execution of an operation + * @since 4.x + * @see #getTimeout + */ + GridFSBucket withTimeout(long timeout, TimeUnit timeUnit); + /** * Opens a Stream that the application can write the contents of the file to. *

diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucketImpl.java b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucketImpl.java index f365bd2980a..c50e7dfd664 100644 --- a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucketImpl.java +++ b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucketImpl.java @@ -17,6 +17,7 @@ package com.mongodb.client.gridfs; import com.mongodb.MongoClientSettings; +import com.mongodb.MongoExecutionTimeoutException; import com.mongodb.MongoGridFSException; import com.mongodb.ReadConcern; import com.mongodb.ReadPreference; @@ -26,12 +27,16 @@ import com.mongodb.client.ListIndexesIterable; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; +import com.mongodb.client.cursor.TimeoutMode; import com.mongodb.client.gridfs.model.GridFSDownloadOptions; import com.mongodb.client.gridfs.model.GridFSFile; import com.mongodb.client.gridfs.model.GridFSUploadOptions; import com.mongodb.client.model.IndexOptions; import com.mongodb.client.result.DeleteResult; import com.mongodb.client.result.UpdateResult; +import com.mongodb.internal.TimeoutContext; + +import com.mongodb.internal.time.Timeout; import com.mongodb.lang.Nullable; import org.bson.BsonDocument; import org.bson.BsonObjectId; @@ -46,19 +51,25 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.Map; +import java.util.concurrent.TimeUnit; import static com.mongodb.ReadPreference.primary; +import static com.mongodb.assertions.Assertions.isTrueArgument; import static com.mongodb.assertions.Assertions.notNull; import static java.lang.String.format; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.bson.codecs.configuration.CodecRegistries.fromRegistries; final class GridFSBucketImpl implements GridFSBucket { private static final int DEFAULT_CHUNKSIZE_BYTES = 255 * 1024; + private static final String TIMEOUT_MESSAGE = "GridFS operation timed out"; private final String bucketName; private final int chunkSizeBytes; private final MongoCollection filesCollection; private final MongoCollection chunksCollection; private volatile boolean checkedIndexes; + @Nullable + private final Long timeoutMs; GridFSBucketImpl(final MongoDatabase database) { this(database, "fs"); @@ -67,15 +78,16 @@ final class GridFSBucketImpl implements GridFSBucket { GridFSBucketImpl(final MongoDatabase database, final String bucketName) { this(notNull("bucketName", bucketName), DEFAULT_CHUNKSIZE_BYTES, getFilesCollection(notNull("database", database), bucketName), - getChunksCollection(database, bucketName)); + getChunksCollection(database, bucketName), database.getTimeout(TimeUnit.MILLISECONDS)); } GridFSBucketImpl(final String bucketName, final int chunkSizeBytes, final MongoCollection filesCollection, - final MongoCollection chunksCollection) { + final MongoCollection chunksCollection, @Nullable final Long timeoutMs) { this.bucketName = notNull("bucketName", bucketName); this.chunkSizeBytes = chunkSizeBytes; this.filesCollection = notNull("filesCollection", filesCollection); this.chunksCollection = notNull("chunksCollection", chunksCollection); + this.timeoutMs = timeoutMs; } @Override @@ -103,27 +115,40 @@ public ReadConcern getReadConcern() { return filesCollection.getReadConcern(); } + @Override + public Long getTimeout(final TimeUnit timeUnit) { + return timeoutMs == null ? null : notNull("timeUnit", timeUnit).convert(timeoutMs, MILLISECONDS); + } + @Override public GridFSBucket withChunkSizeBytes(final int chunkSizeBytes) { - return new GridFSBucketImpl(bucketName, chunkSizeBytes, filesCollection, chunksCollection); + return new GridFSBucketImpl(bucketName, chunkSizeBytes, filesCollection, chunksCollection, timeoutMs); } @Override public GridFSBucket withReadPreference(final ReadPreference readPreference) { return new GridFSBucketImpl(bucketName, chunkSizeBytes, filesCollection.withReadPreference(readPreference), - chunksCollection.withReadPreference(readPreference)); + chunksCollection.withReadPreference(readPreference), timeoutMs); } @Override public GridFSBucket withWriteConcern(final WriteConcern writeConcern) { return new GridFSBucketImpl(bucketName, chunkSizeBytes, filesCollection.withWriteConcern(writeConcern), - chunksCollection.withWriteConcern(writeConcern)); + chunksCollection.withWriteConcern(writeConcern), timeoutMs); } @Override public GridFSBucket withReadConcern(final ReadConcern readConcern) { return new GridFSBucketImpl(bucketName, chunkSizeBytes, filesCollection.withReadConcern(readConcern), - chunksCollection.withReadConcern(readConcern)); + chunksCollection.withReadConcern(readConcern), timeoutMs); + } + + @Override + public GridFSBucket withTimeout(final long timeout, final TimeUnit timeUnit) { + isTrueArgument("timeout >= 0", timeout >= 0); + notNull("timeUnit", timeUnit); + return new GridFSBucketImpl(bucketName, chunkSizeBytes, filesCollection, + chunksCollection, MILLISECONDS.convert(timeout, timeUnit)); } @Override @@ -176,12 +201,14 @@ public GridFSUploadStream openUploadStream(final ClientSession clientSession, fi private GridFSUploadStream createGridFSUploadStream(@Nullable final ClientSession clientSession, final BsonValue id, final String filename, final GridFSUploadOptions options) { + Timeout operationTimeout = startTimeout(); notNull("options", options); Integer chunkSizeBytes = options.getChunkSizeBytes(); int chunkSize = chunkSizeBytes == null ? this.chunkSizeBytes : chunkSizeBytes; - checkCreateIndex(clientSession); - return new GridFSUploadStreamImpl(clientSession, filesCollection, chunksCollection, id, filename, chunkSize, - options.getMetadata()); + checkCreateIndex(clientSession, operationTimeout); + return new GridFSUploadStreamImpl(clientSession, filesCollection, + chunksCollection, id, filename, chunkSize, + options.getMetadata(), operationTimeout); } @Override @@ -257,7 +284,10 @@ public GridFSDownloadStream openDownloadStream(final ObjectId id) { @Override public GridFSDownloadStream openDownloadStream(final BsonValue id) { - return createGridFSDownloadStream(null, getFileInfoById(null, id)); + Timeout oprationTimeout = startTimeout(); + + GridFSFile fileInfo = getFileInfoById(null, id, oprationTimeout); + return createGridFSDownloadStream(null, fileInfo, oprationTimeout); } @Override @@ -267,7 +297,9 @@ public GridFSDownloadStream openDownloadStream(final String filename) { @Override public GridFSDownloadStream openDownloadStream(final String filename, final GridFSDownloadOptions options) { - return createGridFSDownloadStream(null, getFileByName(null, filename, options)); + Timeout operationTimeout = startTimeout(); + GridFSFile file = getFileByName(null, filename, options, operationTimeout); + return createGridFSDownloadStream(null, file, operationTimeout); } @Override @@ -278,7 +310,9 @@ public GridFSDownloadStream openDownloadStream(final ClientSession clientSession @Override public GridFSDownloadStream openDownloadStream(final ClientSession clientSession, final BsonValue id) { notNull("clientSession", clientSession); - return createGridFSDownloadStream(clientSession, getFileInfoById(clientSession, id)); + Timeout operationTimeout = startTimeout(); + GridFSFile fileInfoById = getFileInfoById(clientSession, id, operationTimeout); + return createGridFSDownloadStream(clientSession, fileInfoById, operationTimeout); } @Override @@ -290,11 +324,14 @@ public GridFSDownloadStream openDownloadStream(final ClientSession clientSession public GridFSDownloadStream openDownloadStream(final ClientSession clientSession, final String filename, final GridFSDownloadOptions options) { notNull("clientSession", clientSession); - return createGridFSDownloadStream(clientSession, getFileByName(clientSession, filename, options)); + Timeout operationTimeout = startTimeout(); + GridFSFile file = getFileByName(clientSession, filename, options, operationTimeout); + return createGridFSDownloadStream(clientSession, file, operationTimeout); } - private GridFSDownloadStream createGridFSDownloadStream(@Nullable final ClientSession clientSession, final GridFSFile gridFSFile) { - return new GridFSDownloadStreamImpl(clientSession, gridFSFile, chunksCollection); + private GridFSDownloadStream createGridFSDownloadStream(@Nullable final ClientSession clientSession, final GridFSFile gridFSFile, + @Nullable final Timeout operationTimeout) { + return new GridFSDownloadStreamImpl(clientSession, gridFSFile, chunksCollection, operationTimeout); } @Override @@ -365,7 +402,12 @@ public GridFSFindIterable find(final ClientSession clientSession, final Bson fil } private GridFSFindIterable createGridFSFindIterable(@Nullable final ClientSession clientSession, @Nullable final Bson filter) { - return new GridFSFindIterableImpl(createFindIterable(clientSession, filter)); + return new GridFSFindIterableImpl(createFindIterable(clientSession, filter, startTimeout())); + } + + private GridFSFindIterable createGridFSFindIterable(@Nullable final ClientSession clientSession, @Nullable final Bson filter, + @Nullable final Timeout operationTimeout) { + return new GridFSFindIterableImpl(createFindIterable(clientSession, filter, operationTimeout)); } @Override @@ -390,13 +432,18 @@ public void delete(final ClientSession clientSession, final BsonValue id) { } private void executeDelete(@Nullable final ClientSession clientSession, final BsonValue id) { + Timeout operationTimeout = startTimeout(); DeleteResult result; if (clientSession != null) { - result = filesCollection.deleteOne(clientSession, new BsonDocument("_id", id)); - chunksCollection.deleteMany(clientSession, new BsonDocument("files_id", id)); + result = withNullableTimeout(filesCollection, operationTimeout) + .deleteOne(clientSession, new BsonDocument("_id", id)); + withNullableTimeout(filesCollection, operationTimeout) + .deleteMany(clientSession, new BsonDocument("files_id", id)); } else { - result = filesCollection.deleteOne(new BsonDocument("_id", id)); - chunksCollection.deleteMany(new BsonDocument("files_id", id)); + result = withNullableTimeout(filesCollection, operationTimeout) + .deleteOne(new BsonDocument("_id", id)); + withNullableTimeout(filesCollection, operationTimeout) + .deleteMany(new BsonDocument("files_id", id)); } if (result.wasAcknowledged() && result.getDeletedCount() == 0) { @@ -426,12 +473,13 @@ public void rename(final ClientSession clientSession, final BsonValue id, final } private void executeRename(@Nullable final ClientSession clientSession, final BsonValue id, final String newFilename) { + Timeout operationTimeout = startTimeout(); UpdateResult updateResult; if (clientSession != null) { - updateResult = filesCollection.updateOne(clientSession, new BsonDocument("_id", id), + updateResult = withNullableTimeout(filesCollection, operationTimeout).updateOne(clientSession, new BsonDocument("_id", id), new BsonDocument("$set", new BsonDocument("filename", new BsonString(newFilename)))); } else { - updateResult = filesCollection.updateOne(new BsonDocument("_id", id), + updateResult = withNullableTimeout(filesCollection, operationTimeout).updateOne(new BsonDocument("_id", id), new BsonDocument("$set", new BsonDocument("filename", new BsonString(newFilename)))); } @@ -442,15 +490,17 @@ private void executeRename(@Nullable final ClientSession clientSession, final Bs @Override public void drop() { - filesCollection.drop(); - chunksCollection.drop(); + Timeout oeprationTimeout = startTimeout(); + withNullableTimeout(filesCollection, oeprationTimeout).drop(); + withNullableTimeout(chunksCollection, oeprationTimeout).drop(); } @Override public void drop(final ClientSession clientSession) { + Timeout oeprationTimeout = startTimeout(); notNull("clientSession", clientSession); - filesCollection.drop(clientSession); - chunksCollection.drop(clientSession); + withNullableTimeout(filesCollection, oeprationTimeout).drop(clientSession); + withNullableTimeout(chunksCollection, oeprationTimeout).drop(clientSession); } private static MongoCollection getFilesCollection(final MongoDatabase database, final String bucketName) { @@ -463,37 +513,45 @@ private static MongoCollection getChunksCollection(final MongoDatabase return database.getCollection(bucketName + ".chunks").withCodecRegistry(MongoClientSettings.getDefaultCodecRegistry()); } - private void checkCreateIndex(@Nullable final ClientSession clientSession) { + private void checkCreateIndex(@Nullable final ClientSession clientSession, @Nullable final Timeout operationTimeout) { if (!checkedIndexes) { - if (collectionIsEmpty(clientSession, filesCollection.withDocumentClass(Document.class).withReadPreference(primary()))) { + if (collectionIsEmpty(clientSession, + filesCollection.withDocumentClass(Document.class).withReadPreference(primary()), + operationTimeout)) { + Document filesIndex = new Document("filename", 1).append("uploadDate", 1); - if (!hasIndex(clientSession, filesCollection.withReadPreference(primary()), filesIndex)) { - createIndex(clientSession, filesCollection, filesIndex, new IndexOptions()); + if (!hasIndex(clientSession, filesCollection.withReadPreference(primary()), filesIndex, operationTimeout)) { + createIndex(clientSession, filesCollection, filesIndex, new IndexOptions(), operationTimeout); } Document chunksIndex = new Document("files_id", 1).append("n", 1); - if (!hasIndex(clientSession, chunksCollection.withReadPreference(primary()), chunksIndex)) { - createIndex(clientSession, chunksCollection, chunksIndex, new IndexOptions().unique(true)); + if (!hasIndex(clientSession, chunksCollection.withReadPreference(primary()), chunksIndex, operationTimeout)) { + createIndex(clientSession, chunksCollection, chunksIndex, new IndexOptions().unique(true), operationTimeout); } } checkedIndexes = true; } } - private boolean collectionIsEmpty(@Nullable final ClientSession clientSession, final MongoCollection collection) { + private boolean collectionIsEmpty(@Nullable final ClientSession clientSession, + final MongoCollection collection, + @Nullable final Timeout operationTimeout) { if (clientSession != null) { - return collection.find(clientSession).projection(new Document("_id", 1)).first() == null; + return withNullableTimeout(collection, operationTimeout) + .find(clientSession).projection(new Document("_id", 1)).first() == null; } else { - return collection.find().projection(new Document("_id", 1)).first() == null; + return withNullableTimeout(collection, operationTimeout) + .find().projection(new Document("_id", 1)).first() == null; } } - private boolean hasIndex(@Nullable final ClientSession clientSession, final MongoCollection collection, final Document index) { + private boolean hasIndex(@Nullable final ClientSession clientSession, final MongoCollection collection, + final Document index, @Nullable final Timeout operationTimeout) { boolean hasIndex = false; ListIndexesIterable listIndexesIterable; if (clientSession != null) { - listIndexesIterable = collection.listIndexes(clientSession); + listIndexesIterable = withNullableTimeout(collection, operationTimeout).listIndexes(clientSession); } else { - listIndexesIterable = collection.listIndexes(); + listIndexesIterable = withNullableTimeout(collection, operationTimeout).listIndexes(); } ArrayList indexes = listIndexesIterable.into(new ArrayList<>()); @@ -513,16 +571,16 @@ private boolean hasIndex(@Nullable final ClientSession clientSession, final } private void createIndex(@Nullable final ClientSession clientSession, final MongoCollection collection, final Document index, - final IndexOptions indexOptions) { + final IndexOptions indexOptions, final @Nullable Timeout operationTimeout) { if (clientSession != null) { - collection.createIndex(clientSession, index, indexOptions); + withNullableTimeout(collection, operationTimeout).createIndex(clientSession, index, indexOptions); } else { - collection.createIndex(index, indexOptions); + withNullableTimeout(collection, operationTimeout).createIndex(index, indexOptions); } } private GridFSFile getFileByName(@Nullable final ClientSession clientSession, final String filename, - final GridFSDownloadOptions options) { + final GridFSDownloadOptions options, @Nullable final Timeout operationTimeout) { int revision = options.getRevision(); int skip; int sort; @@ -534,7 +592,7 @@ private GridFSFile getFileByName(@Nullable final ClientSession clientSession, fi sort = -1; } - GridFSFile fileInfo = createGridFSFindIterable(clientSession, new Document("filename", filename)).skip(skip) + GridFSFile fileInfo = createGridFSFindIterable(clientSession, new Document("filename", filename), operationTimeout).skip(skip) .sort(new Document("uploadDate", sort)).first(); if (fileInfo == null) { throw new MongoGridFSException(format("No file found with the filename: %s and revision: %s", filename, revision)); @@ -542,25 +600,30 @@ private GridFSFile getFileByName(@Nullable final ClientSession clientSession, fi return fileInfo; } - private GridFSFile getFileInfoById(@Nullable final ClientSession clientSession, final BsonValue id) { + private GridFSFile getFileInfoById(@Nullable final ClientSession clientSession, final BsonValue id, + @Nullable final Timeout operationTImeout) { notNull("id", id); - GridFSFile fileInfo = createFindIterable(clientSession, new Document("_id", id)).first(); + GridFSFile fileInfo = createFindIterable(clientSession, new Document("_id", id), operationTImeout).first(); if (fileInfo == null) { throw new MongoGridFSException(format("No file found with the id: %s", id)); } return fileInfo; } - private FindIterable createFindIterable(@Nullable final ClientSession clientSession, @Nullable final Bson filter) { + private FindIterable createFindIterable(@Nullable final ClientSession clientSession, @Nullable final Bson filter, + @Nullable final Timeout operationTImeout) { FindIterable findIterable; if (clientSession != null) { - findIterable = filesCollection.find(clientSession); + findIterable = withNullableTimeout(filesCollection, operationTImeout).find(clientSession); } else { - findIterable = filesCollection.find(); + findIterable = withNullableTimeout(filesCollection, operationTImeout).find(); } if (filter != null) { findIterable = findIterable.filter(filter); } + if (timeoutMs != null) { + findIterable.timeoutMode(TimeoutMode.CURSOR_LIFETIME); + } return findIterable; } @@ -572,6 +635,8 @@ private void downloadToStream(final GridFSDownloadStream downloadStream, final O while ((len = downloadStream.read(buffer)) != -1) { destination.write(buffer, 0, len); } + } catch (MongoExecutionTimeoutException e){ // TODO (CSOT) - JAVA-5248 Update to MongoOperationTimeoutException + throw e; } catch (IOException e) { savedThrowable = new MongoGridFSException("IOException when reading from the OutputStream", e); } catch (Exception e) { @@ -587,4 +652,14 @@ private void downloadToStream(final GridFSDownloadStream downloadStream, final O } } } + + private static MongoCollection withNullableTimeout(final MongoCollection chunksCollection, + @Nullable final Timeout timeout) { + return TimeoutUtils.withNullableTimeout(chunksCollection, TIMEOUT_MESSAGE, timeout); + } + + @Nullable + private Timeout startTimeout() { + return TimeoutContext.calculateTimeout(timeoutMs); + } } diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSDownloadStreamImpl.java b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSDownloadStreamImpl.java index 16f0bcd7fd3..112188c303f 100644 --- a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSDownloadStreamImpl.java +++ b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSDownloadStreamImpl.java @@ -16,12 +16,15 @@ package com.mongodb.client.gridfs; +import com.mongodb.MongoExecutionTimeoutException; import com.mongodb.MongoGridFSException; import com.mongodb.client.ClientSession; import com.mongodb.client.FindIterable; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoCursor; +import com.mongodb.client.cursor.TimeoutMode; import com.mongodb.client.gridfs.model.GridFSFile; +import com.mongodb.internal.time.Timeout; import com.mongodb.lang.Nullable; import org.bson.BsonValue; import org.bson.Document; @@ -33,12 +36,17 @@ import static com.mongodb.assertions.Assertions.notNull; import static com.mongodb.internal.Locks.withInterruptibleLock; import static java.lang.String.format; +import static java.util.concurrent.TimeUnit.MILLISECONDS; class GridFSDownloadStreamImpl extends GridFSDownloadStream { + private static final String TIMEOUT_MESSAGE = "The GridFS download stream has timed out"; private final ClientSession clientSession; private final GridFSFile fileInfo; private final MongoCollection chunksCollection; private final BsonValue fileId; + /** + * The length, in bytes of the file to download. + */ private final long length; private final int chunkSizeInBytes; private final int numberOfChunks; @@ -46,16 +54,20 @@ class GridFSDownloadStreamImpl extends GridFSDownloadStream { private int batchSize; private int chunkIndex; private int bufferOffset; + /** + * Current byte position in the file. + */ private long currentPosition; private byte[] buffer = null; private long markPosition; - + @Nullable + private final Timeout timeout; private final ReentrantLock closeLock = new ReentrantLock(); private final ReentrantLock cursorLock = new ReentrantLock(); private boolean closed = false; GridFSDownloadStreamImpl(@Nullable final ClientSession clientSession, final GridFSFile fileInfo, - final MongoCollection chunksCollection) { + final MongoCollection chunksCollection, @Nullable final Timeout timeout) { this.clientSession = clientSession; this.fileInfo = notNull("file information", fileInfo); this.chunksCollection = notNull("chunks collection", chunksCollection); @@ -64,6 +76,7 @@ class GridFSDownloadStreamImpl extends GridFSDownloadStream { length = fileInfo.getLength(); chunkSizeInBytes = fileInfo.getChunkSize(); numberOfChunks = (int) Math.ceil((double) length / chunkSizeInBytes); + this.timeout = timeout; } @Override @@ -97,6 +110,7 @@ public int read(final byte[] b) { @Override public int read(final byte[] b, final int off, final int len) { checkClosed(); + checkTimeout(); if (currentPosition == length) { return -1; @@ -118,6 +132,7 @@ public int read(final byte[] b, final int off, final int len) { @Override public long skip(final long bytesToSkip) { checkClosed(); + checkTimeout(); if (bytesToSkip <= 0) { return 0; } @@ -146,6 +161,7 @@ public long skip(final long bytesToSkip) { @Override public int available() { checkClosed(); + checkTimeout(); if (buffer == null) { return 0; } else { @@ -166,6 +182,7 @@ public void mark(final int readlimit) { @Override public void reset() { checkClosed(); + checkTimeout(); if (currentPosition == markPosition) { return; } @@ -195,6 +212,12 @@ public void close() { }); } + private void checkTimeout() { + if (timeout != null && timeout.hasExpired()) { + // TODO (CSOT) - JAVA-5248 Update to MongoOperationTimeoutException + throw new MongoExecutionTimeoutException("The GridFS download stream has timed out"); + } + } private void checkClosed() { withInterruptibleLock(closeLock, () -> { if (closed) { @@ -236,11 +259,15 @@ private MongoCursor getCursor(final int startChunkIndex) { FindIterable findIterable; Document filter = new Document("files_id", fileId).append("n", new Document("$gte", startChunkIndex)); if (clientSession != null) { - findIterable = chunksCollection.find(clientSession, filter); + findIterable = withNullableTimeout(chunksCollection, timeout).find(clientSession, filter); } else { - findIterable = chunksCollection.find(filter); + findIterable = withNullableTimeout(chunksCollection, timeout).find(filter); } - return findIterable.batchSize(batchSize).sort(new Document("n", 1)).iterator(); + if (timeout != null){ + findIterable.timeoutMode(TimeoutMode.CURSOR_LIFETIME); + } + return findIterable.batchSize(batchSize) + .sort(new Document("n", 1)).iterator(); } private byte[] getBufferFromChunk(@Nullable final Document chunk, final int expectedChunkIndex) { @@ -279,4 +306,9 @@ private byte[] getBufferFromChunk(@Nullable final Document chunk, final int expe private byte[] getBuffer(final int chunkIndexToFetch) { return getBufferFromChunk(getChunk(chunkIndexToFetch), chunkIndexToFetch); } + + private MongoCollection withNullableTimeout(final MongoCollection chunksCollection, + @Nullable final Timeout timeout) { + return TimeoutUtils.withNullableTimeout(chunksCollection, TIMEOUT_MESSAGE, timeout); + } } diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSFindIterable.java b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSFindIterable.java index abd97fe499b..9b8cb8b9117 100644 --- a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSFindIterable.java +++ b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSFindIterable.java @@ -16,10 +16,7 @@ package com.mongodb.client.gridfs; -import com.mongodb.client.MongoCollection; -import com.mongodb.client.MongoDatabase; import com.mongodb.client.MongoIterable; -import com.mongodb.client.cursor.TimeoutMode; import com.mongodb.client.gridfs.model.GridFSFile; import com.mongodb.client.model.Collation; import com.mongodb.lang.Nullable; @@ -118,25 +115,4 @@ public interface GridFSFindIterable extends MongoIterable { * @mongodb.server.release 3.4 */ GridFSFindIterable collation(@Nullable Collation collation); - - /** - * Sets the timeoutMode for the cursor. - * - *

- * Requires the {@code timeout} to be set, either in the {@link com.mongodb.MongoClientSettings}, - * via {@link MongoDatabase} or via {@link MongoCollection} - *

- *

- * If the {@code timeout} is set then: - *

    - *
  • For non-tailable cursors, the default value of timeoutMode is {@link TimeoutMode#CURSOR_LIFETIME}
  • - *
  • For tailable cursors, the default value of timeoutMode is {@link TimeoutMode#ITERATION} and its an error - * to configure it as: {@link TimeoutMode#CURSOR_LIFETIME}
  • - *
- * - * @param timeoutMode the timeout mode - * @return this - * @since 4.x - */ - GridFSFindIterable timeoutMode(TimeoutMode timeoutMode); } diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSFindIterableImpl.java b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSFindIterableImpl.java index dbd5625f6fa..240f2ec0acc 100644 --- a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSFindIterableImpl.java +++ b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSFindIterableImpl.java @@ -73,12 +73,6 @@ public GridFSFindIterable batchSize(final int batchSize) { return this; } - @Override - public GridFSFindIterable timeoutMode(final TimeoutMode timeoutMode) { - underlying.timeoutMode(timeoutMode); - return this; - } - @Override public GridFSFindIterable collation(@Nullable final Collation collation) { underlying.collation(collation); diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSUploadStreamImpl.java b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSUploadStreamImpl.java index ff359e34781..b57d2b5a76d 100644 --- a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSUploadStreamImpl.java +++ b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSUploadStreamImpl.java @@ -16,10 +16,12 @@ package com.mongodb.client.gridfs; +import com.mongodb.MongoExecutionTimeoutException; import com.mongodb.MongoGridFSException; import com.mongodb.client.ClientSession; import com.mongodb.client.MongoCollection; import com.mongodb.client.gridfs.model.GridFSFile; +import com.mongodb.internal.time.Timeout; import com.mongodb.lang.Nullable; import org.bson.BsonValue; import org.bson.Document; @@ -33,6 +35,7 @@ import static com.mongodb.internal.Locks.withInterruptibleLock; final class GridFSUploadStreamImpl extends GridFSUploadStream { + public static final String TIMEOUT_MESSAGE = "The GridFS upload stream has timed out"; private final ClientSession clientSession; private final MongoCollection filesCollection; private final MongoCollection chunksCollection; @@ -44,13 +47,14 @@ final class GridFSUploadStreamImpl extends GridFSUploadStream { private long lengthInBytes; private int bufferOffset; private int chunkIndex; - + @Nullable + private final Timeout timeout; private final ReentrantLock closeLock = new ReentrantLock(); private boolean closed = false; GridFSUploadStreamImpl(@Nullable final ClientSession clientSession, final MongoCollection filesCollection, final MongoCollection chunksCollection, final BsonValue fileId, final String filename, - final int chunkSizeBytes, @Nullable final Document metadata) { + final int chunkSizeBytes, @Nullable final Document metadata, @Nullable final Timeout timeout) { this.clientSession = clientSession; this.filesCollection = notNull("files collection", filesCollection); this.chunksCollection = notNull("chunks collection", chunksCollection); @@ -61,6 +65,7 @@ final class GridFSUploadStreamImpl extends GridFSUploadStream { chunkIndex = 0; bufferOffset = 0; buffer = new byte[chunkSizeBytes]; + this.timeout = timeout; } @Override @@ -84,9 +89,11 @@ public void abort() { }); if (clientSession != null) { - chunksCollection.deleteMany(clientSession, new Document("files_id", fileId)); + withNullableTimeout(chunksCollection, timeout) + .deleteMany(clientSession, new Document("files_id", fileId)); } else { - chunksCollection.deleteMany(new Document("files_id", fileId)); + withNullableTimeout(chunksCollection, timeout) + .deleteMany(new Document("files_id", fileId)); } } @@ -105,6 +112,7 @@ public void write(final byte[] b) { @Override public void write(final byte[] b, final int off, final int len) { checkClosed(); + checkTimeout(); notNull("b", b); if ((off < 0) || (off > b.length) || (len < 0) @@ -136,6 +144,13 @@ public void write(final byte[] b, final int off, final int len) { } } + private void checkTimeout() { + if (timeout != null && timeout.hasExpired()) { + // TODO (CSOT) - JAVA-5248 Update to MongoOperationTimeoutException + throw new MongoExecutionTimeoutException(TIMEOUT_MESSAGE); + } + } + @Override public void close() { boolean alreadyClosed = withInterruptibleLock(closeLock, () -> { @@ -150,9 +165,9 @@ public void close() { GridFSFile gridFSFile = new GridFSFile(fileId, filename, lengthInBytes, chunkSizeBytes, new Date(), metadata); if (clientSession != null) { - filesCollection.insertOne(clientSession, gridFSFile); + withNullableTimeout(filesCollection, timeout).insertOne(clientSession, gridFSFile); } else { - filesCollection.insertOne(gridFSFile); + withNullableTimeout(filesCollection, timeout).insertOne(gridFSFile); } buffer = null; } @@ -160,10 +175,12 @@ public void close() { private void writeChunk() { if (bufferOffset > 0) { if (clientSession != null) { - chunksCollection.insertOne(clientSession, new Document("files_id", fileId).append("n", chunkIndex) - .append("data", getData())); + withNullableTimeout(chunksCollection, timeout) + .insertOne(clientSession, new Document("files_id", fileId).append("n", chunkIndex).append("data", getData())); } else { - chunksCollection.insertOne(new Document("files_id", fileId).append("n", chunkIndex).append("data", getData())); + withNullableTimeout(chunksCollection, timeout) + .insertOne(new Document("files_id", fileId) + .append("n", chunkIndex).append("data", getData())); } chunkIndex++; bufferOffset = 0; @@ -186,4 +203,9 @@ private void checkClosed() { } }); } + + private static MongoCollection withNullableTimeout(final MongoCollection collection, + @Nullable final Timeout timeout) { + return TimeoutUtils.withNullableTimeout(collection, TIMEOUT_MESSAGE, timeout); + } } diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/TimeoutUtils.java b/driver-sync/src/main/com/mongodb/client/gridfs/TimeoutUtils.java new file mode 100644 index 00000000000..8b7349d751b --- /dev/null +++ b/driver-sync/src/main/com/mongodb/client/gridfs/TimeoutUtils.java @@ -0,0 +1,44 @@ +/* + * 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.client.gridfs; + +import com.mongodb.MongoExecutionTimeoutException; +import com.mongodb.client.MongoCollection; +import com.mongodb.internal.time.Timeout; +import com.mongodb.lang.Nullable; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +final class TimeoutUtils { + private TimeoutUtils(){ + //NOP + } + + public static MongoCollection withNullableTimeout(final MongoCollection collection, + final String message, + @Nullable final Timeout timeout) { + if (timeout != null) { + long remainingMs = timeout.remaining(MILLISECONDS); + if (remainingMs <= 0) { + // TODO (CSOT) - JAVA-5248 Update to MongoOperationTimeoutException + throw new MongoExecutionTimeoutException(message); + } + return collection.withTimeout(remainingMs, MILLISECONDS); + } + return collection; + } +} diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionDeadlockTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionDeadlockTest.java index ef965f0ae95..2ac985f21a6 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionDeadlockTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionDeadlockTest.java @@ -195,11 +195,11 @@ public void shouldPassAllOutcomes(final int maxPoolSize, } private void assertEventEquality(final TestCommandListener commandListener, final List expectedStartEvents) { - List actualStartedEvents = commandListener.getCommandStartedEvents(); + List actualStartedEvents = commandListener.getCommandStartedEvents(); assertEquals(expectedStartEvents.size(), actualStartedEvents.size()); for (int i = 0; i < expectedStartEvents.size(); i++) { ExpectedEvent expectedEvent = expectedStartEvents.get(i); - CommandStartedEvent actualEvent = (CommandStartedEvent) actualStartedEvents.get(i); + CommandStartedEvent actualEvent = actualStartedEvents.get(i); assertEquals(expectedEvent.getDatabase(), actualEvent.getDatabaseName(), "Database name"); assertEquals(expectedEvent.getCommandName(), actualEvent.getCommandName(), "Command name"); } diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionTest.java index 9c14640cb4b..8d5fccaef98 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideEncryptionTest.java @@ -26,6 +26,7 @@ import com.mongodb.client.model.ValidationOptions; import com.mongodb.client.test.CollectionHelper; import com.mongodb.event.CommandEvent; +import com.mongodb.event.CommandStartedEvent; import com.mongodb.internal.connection.TestCommandListener; import com.mongodb.lang.Nullable; import org.bson.BsonArray; @@ -325,7 +326,7 @@ public void shouldPassAllOutcomes() { if (definition.containsKey("expectations")) { List expectedEvents = getExpectedEvents(definition.getArray("expectations"), "default", null); - List events = commandListener.getCommandStartedEvents(); + List events = commandListener.getCommandStartedEvents(); assertEventsEquality(expectedEvents, events); } diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java index fe69d0a66dd..048cab4893c 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -26,10 +26,13 @@ import com.mongodb.ReadConcern; import com.mongodb.ReadPreference; import com.mongodb.WriteConcern; +import com.mongodb.client.gridfs.GridFSBucket; +import com.mongodb.client.gridfs.GridFSDownloadStream; +import com.mongodb.client.gridfs.GridFSUploadStream; import com.mongodb.client.model.CreateCollectionOptions; import com.mongodb.client.model.changestream.ChangeStreamDocument; import com.mongodb.client.test.CollectionHelper; -import com.mongodb.event.CommandEvent; +import com.mongodb.event.CommandStartedEvent; import com.mongodb.internal.connection.ServerHelper; import com.mongodb.internal.connection.TestCommandListener; import com.mongodb.test.FlakyTest; @@ -38,8 +41,10 @@ import org.bson.BsonTimestamp; import org.bson.Document; import org.bson.codecs.BsonDocumentCodec; +import org.bson.types.ObjectId; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Tag; @@ -51,6 +56,7 @@ import java.time.Instant; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import java.util.stream.Stream; import static com.mongodb.ClusterFixture.isDiscoverableReplicaSet; @@ -70,11 +76,120 @@ */ public abstract class AbstractClientSideOperationsTimeoutProseTest { + private static final String GRID_FS_BUCKET_NAME = "db.fs"; + private static final String GRID_FS_COLLECTION_NAME_CHUNKS = GRID_FS_BUCKET_NAME + ".chunks"; + private static final String GRID_FS_COLLECTION_NAME_FILE = GRID_FS_BUCKET_NAME + ".files"; private final MongoNamespace namespace = new MongoNamespace(getDefaultDatabaseName(), this.getClass().getSimpleName()); private TestCommandListener commandListener; private CollectionHelper collectionHelper; - + private CollectionHelper filesCollectionHelper; + private CollectionHelper chunksCollectionHelper; protected abstract MongoClient createMongoClient(MongoClientSettings mongoClientSettings); + protected abstract GridFSBucket createGridFsBucket(MongoDatabase mongoDatabase, String bucketName); + + @Tag("setsFailPoint") + @FlakyTest(maxAttempts = 3) + @DisplayName("6. GridFS Upload - uploads via openUploadStream can be timed out") + @Disabled("TODO (CSOT) - JAVA-4057") + public void testGridFSUploadViaOpenUploadStreamTimeout() { + assumeTrue(serverVersionAtLeast(4, 4)); + collectionHelper.runAdminCommand("{" + + " configureFailPoint: \"failCommand\"," + + " mode: { times: 1 }," + + " data: {" + + " failCommands: [\"insert\"]," + + " blockConnection: true," + + " blockTimeMS: " + 50 + + " }" + + "}"); + try (MongoClient client = createMongoClient(getMongoClientSettingsBuilder() + .timeout(45, TimeUnit.MILLISECONDS))) { + MongoDatabase database = client.getDatabase(namespace.getDatabaseName()); + GridFSBucket gridFsBucket = createGridFsBucket(database, GRID_FS_BUCKET_NAME); + + try (GridFSUploadStream uploadStream = gridFsBucket.openUploadStream("filename")){ + uploadStream.write(0x12); + assertThrows(MongoExecutionTimeoutException.class, uploadStream::close); + } + } + } + + @Tag("setsFailPoint") + @FlakyTest(maxAttempts = 3) + @DisplayName("6. GridFS Upload - Aborting an upload stream can be timed out") + public void testAbortingGridFsUploadStreamTimeout() { + assumeTrue(serverVersionAtLeast(4, 4)); + collectionHelper.runAdminCommand("{" + + " configureFailPoint: \"failCommand\"," + + " mode: { times: 1 }," + + " data: {" + + " failCommands: [\"delete\"]," + + " blockConnection: true," + + " blockTimeMS: " + 50 + + " }" + + "}"); + try (MongoClient client = createMongoClient(getMongoClientSettingsBuilder() + .timeout(45, TimeUnit.MILLISECONDS))) { + MongoDatabase database = client.getDatabase(namespace.getDatabaseName()); + GridFSBucket gridFsBucket = createGridFsBucket(database, GRID_FS_BUCKET_NAME).withChunkSizeBytes(2); + + try (GridFSUploadStream uploadStream = gridFsBucket.openUploadStream("filename")){ + uploadStream.write(new byte[]{0x01, 0x02, 0x03, 0x04}); + assertThrows(MongoExecutionTimeoutException.class, uploadStream::abort); + } + } + } + + @Tag("setsFailPoint") + @FlakyTest(maxAttempts = 3) + @DisplayName("6. GridFS Download") + public void testGridFsDownloadStreamTimeout() { + assumeTrue(serverVersionAtLeast(4, 4)); + + try (MongoClient client = createMongoClient(getMongoClientSettingsBuilder() + .timeout(20, TimeUnit.MILLISECONDS))) { + MongoDatabase database = client.getDatabase(namespace.getDatabaseName()); + GridFSBucket gridFsBucket = createGridFsBucket(database, GRID_FS_BUCKET_NAME).withChunkSizeBytes(2); + database.getCollection(GRID_FS_COLLECTION_NAME_FILE) + .insertOne(Document.parse( + "{" + + " _id: {" + + " $oid: \"000000000000000000000005\"" + + " }," + + " length: 10," + + " chunkSize: 4," + + " uploadDate: {" + + " $date: \"1970-01-01T00:00:00.000Z\"" + + " }," + + " md5: \"57d83cd477bfb1ccd975ab33d827a92b\"," + + " filename: \"length-10\"," + + " contentType: \"application/octet-stream\"," + + " aliases: []," + + " metadata: {}" + + "}" + )); + + try (GridFSDownloadStream downloadStream = gridFsBucket.openDownloadStream(new ObjectId("000000000000000000000005"))){ + collectionHelper.runAdminCommand("{" + + " configureFailPoint: \"failCommand\"," + + " mode: { times: 1 }," + + " data: {" + + " failCommands: [\"find\"]," + + " blockConnection: true," + + " blockTimeMS: " + 25 + + " }" + + "}"); + assertThrows(MongoExecutionTimeoutException.class, downloadStream::read); + + List events = commandListener.getCommandStartedEvents(); + List findCommands = events.stream().filter(e -> e.getCommandName().equals("find")).collect(Collectors.toList()); + + assertEquals(2, findCommands.size()); + assertEquals(GRID_FS_COLLECTION_NAME_FILE, findCommands.get(0).getCommand().getString("find").getValue()); + assertEquals(GRID_FS_COLLECTION_NAME_CHUNKS, findCommands.get(1).getCommand().getString("find").getValue()); + } + } + } @Tag("setsFailPoint") @FlakyTest(maxAttempts = 3) @@ -105,7 +220,7 @@ public void testBlockingIterationMethodsTailableCursor() { assertThrows(MongoExecutionTimeoutException.class, cursor::next); } - List events = commandListener.getCommandStartedEvents(); + List events = commandListener.getCommandStartedEvents(); assertEquals(1, events.stream().filter(e -> e.getCommandName().equals("find")).count()); assertTrue(events.stream().filter(e -> e.getCommandName().equals("getMore")).count() <= 2); } @@ -149,7 +264,7 @@ public void testBlockingIterationMethodsChangeStream() { assertEquals(1, document.getFullDocument().get("x")); assertThrows(MongoExecutionTimeoutException.class, cursor::next); } - List events = commandListener.getCommandStartedEvents(); + List events = commandListener.getCommandStartedEvents(); assertEquals(1, events.stream().filter(e -> e.getCommandName().equals("find")).count()); assertEquals(1, events.stream().filter(e -> e.getCommandName().equals("getMore")).count()); } @@ -203,7 +318,13 @@ private MongoClientSettings.Builder getMongoClientSettingsBuilder() { @BeforeEach public void setUp() { collectionHelper = new CollectionHelper<>(new BsonDocumentCodec(), namespace); + filesCollectionHelper = new CollectionHelper<>(new BsonDocumentCodec(), + new MongoNamespace(getDefaultDatabaseName(), GRID_FS_COLLECTION_NAME_FILE)); + chunksCollectionHelper = new CollectionHelper<>(new BsonDocumentCodec(), + new MongoNamespace(getDefaultDatabaseName(), GRID_FS_COLLECTION_NAME_CHUNKS)); collectionHelper.drop(); + filesCollectionHelper.drop(); + chunksCollectionHelper.drop(); commandListener = new TestCommandListener(); } @@ -214,6 +335,8 @@ public void tearDown(final TestInfo info) { } collectionHelper.drop(); + filesCollectionHelper.drop(); + chunksCollectionHelper.drop(); try { ServerHelper.checkPool(getPrimary()); } catch (InterruptedException e) { diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractCrudTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractCrudTest.java index fe78841bec4..634f9946ea3 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractCrudTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractCrudTest.java @@ -22,6 +22,7 @@ import com.mongodb.client.test.CollectionHelper; import com.mongodb.event.CommandEvent; import com.mongodb.event.CommandListener; +import com.mongodb.event.CommandStartedEvent; import com.mongodb.internal.connection.TestCommandListener; import org.bson.BsonArray; import org.bson.BsonBoolean; @@ -152,7 +153,7 @@ public void shouldPassAllOutcomes() { } if (definition.containsKey("expectations")) { List expectedEvents = getExpectedEvents(definition.getArray("expectations"), databaseName, null); - List events = commandListener.getCommandStartedEvents(); + List events = commandListener.getCommandStartedEvents(); assertEventsEquality(expectedEvents, events.subList(0, expectedEvents.size())); diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractRetryableReadsTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractRetryableReadsTest.java index 1df7174e246..34620130a40 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractRetryableReadsTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractRetryableReadsTest.java @@ -28,6 +28,7 @@ import com.mongodb.client.gridfs.GridFSBuckets; import com.mongodb.client.test.CollectionHelper; import com.mongodb.event.CommandEvent; +import com.mongodb.event.CommandStartedEvent; import com.mongodb.internal.connection.TestCommandListener; import org.bson.BsonArray; import org.bson.BsonBinary; @@ -235,7 +236,7 @@ public void shouldPassAllOutcomes() { if (definition.containsKey("expectations")) { List expectedEvents = getExpectedEvents(definition.getArray("expectations"), databaseName, null); - List events = commandListener.waitForStartedEvents(expectedEvents.size()); + List events = commandListener.waitForStartedEvents(expectedEvents.size()); assertEventsEquality(expectedEvents, events); } diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractServerSelectionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractServerSelectionProseTest.java index 894d291a743..4fff4583426 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractServerSelectionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractServerSelectionProseTest.java @@ -19,6 +19,7 @@ import com.mongodb.MongoClientSettings; import com.mongodb.ServerAddress; import com.mongodb.event.CommandEvent; +import com.mongodb.event.CommandStartedEvent; import com.mongodb.internal.connection.TestCommandListener; import org.bson.BsonArray; import org.bson.BsonBoolean; @@ -133,7 +134,7 @@ private static Map doSelections(final MongoCollection result : results) { result.get(); } - List commandStartedEvents = commandListener.getCommandStartedEvents(); + List commandStartedEvents = commandListener.getCommandStartedEvents(); assertEquals(tasks * opsPerTask, commandStartedEvents.size()); return commandStartedEvents.stream() .collect(groupingBy(event -> event.getConnectionDescription().getServerAddress())) diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractUnifiedTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractUnifiedTest.java index cbfab4725bc..75b3ea67b70 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractUnifiedTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractUnifiedTest.java @@ -393,7 +393,7 @@ public void shouldPassAllOutcomes() { if (definition.containsKey("expectations")) { List expectedEvents = getExpectedEvents(definition.getArray("expectations"), databaseName, null); - List events = commandListener.getCommandStartedEvents(); + List events = commandListener.getCommandStartedEvents(); assertTrue("Actual number of events is less than expected number of events", events.size() >= expectedEvents.size()); assertEventsEquality(expectedEvents, events.subList(0, expectedEvents.size()), lsidMap); @@ -723,9 +723,9 @@ private boolean assertExceptionState(final Exception e, final BsonValue expected } private List lastTwoCommandEvents() { - List events = commandListener.getCommandStartedEvents(); + List events = commandListener.getCommandStartedEvents(); assertTrue(events.size() >= 2); - return events.subList(events.size() - 2, events.size()); + return new ArrayList<>(events.subList(events.size() - 2, events.size())); } private TransactionOptions createTransactionOptions(final BsonDocument options) { diff --git a/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutProseTest.java index 2febc6f271f..9df68ddad5d 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutProseTest.java @@ -17,6 +17,8 @@ package com.mongodb.client; import com.mongodb.MongoClientSettings; +import com.mongodb.client.gridfs.GridFSBucket; +import com.mongodb.client.gridfs.GridFSBuckets; /** @@ -28,4 +30,9 @@ public final class ClientSideOperationTimeoutProseTest extends AbstractClientSid protected MongoClient createMongoClient(final MongoClientSettings mongoClientSettings) { return MongoClients.create(mongoClientSettings); } + + @Override + protected GridFSBucket createGridFsBucket(final MongoDatabase mongoDatabase, final String bucketName) { + return GridFSBuckets.create(mongoDatabase, bucketName); + } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutTest.java b/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutTest.java index 3adaec1916b..2719b85e522 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutTest.java @@ -86,13 +86,24 @@ public static void checkSkipCSOTTest(final String fileDescription, final String assumeFalse("TODO (CSOT) - JAVA-4052", fileDescription.startsWith("legacy timeouts behave correctly for retryable operations")); assumeFalse("TODO (CSOT) - JAVA-4063", testDescription.contains("RTT")); - assumeFalse("TODO (CSOT) - JAVA-4059", fileDescription.contains("GridFS")); assumeFalse("TODO (CSOT) - JAVA-5248", fileDescription.equals("MaxTimeMSExpired server errors are transformed into a custom timeout error")); assumeFalse("TODO (CSOT) - JAVA-4062", testDescription.contains("wTimeoutMS is ignored")); + if (fileDescription.contains("GridFS")) { + assumeFalse("TODO (CSOT) - JAVA-4057", testDescription.contains("chunk insertion")); + assumeFalse("TODO (CSOT) - JAVA-4057", testDescription.contains("creation of files document")); + assumeFalse("TODO (CSOT) - JAVA-4057", testDescription.contains("delete against the files collection")); + assumeFalse("TODO (CSOT) - JAVA-4057", testDescription.contains("delete against the chunks collection")); + assumeFalse("TODO (CSOT) - JAVA-4057", testDescription.contains("overridden for a rename")); + assumeFalse("TODO (CSOT) - JAVA-4057", testDescription.contains("update during a rename")); + assumeFalse("TODO (CSOT) - JAVA-4057", testDescription.contains("collection drop")); + assumeFalse("TODO (CSOT) - JAVA-4057", testDescription.contains("drop as a whole")); + assumeFalse("TODO (CSOT) - JAVA-4057", testDescription.contains("entire delete")); + } + assumeFalse("TODO (CSOT) - JAVA-4057", testDescription.equals("maxTimeMS value in the command is less than timeoutMS")); assumeFalse("TODO (CSOT) - JAVA-4057", fileDescription.contains("bulkWrite") || testDescription.contains("bulkWrite")); diff --git a/driver-sync/src/test/functional/com/mongodb/client/ReadConcernTest.java b/driver-sync/src/test/functional/com/mongodb/client/ReadConcernTest.java index 6521fa67010..3ed0d975650 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ReadConcernTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ReadConcernTest.java @@ -64,7 +64,7 @@ public void shouldIncludeReadConcernInCommand() { mongoClient.getDatabase(getDefaultDatabaseName()).getCollection("test") .withReadConcern(ReadConcern.LOCAL).find().into(new ArrayList<>()); - List events = commandListener.getCommandStartedEvents(); + List events = commandListener.getCommandStartedEvents(); BsonDocument commandDocument = new BsonDocument("find", new BsonString("test")) .append("readConcern", ReadConcern.LOCAL.asDocument()) diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/ErrorMatcher.java b/driver-sync/src/test/functional/com/mongodb/client/unified/ErrorMatcher.java index 90f358be35a..47878c4c11e 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/ErrorMatcher.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/ErrorMatcher.java @@ -65,6 +65,7 @@ void assertErrorsMatch(final BsonDocument expectedError, final Exception e) { || e instanceof MongoSocketException); } if (expectedError.containsKey("isTimeoutError")) { + e.printStackTrace(); assertEquals(context.getMessage("Exception must be of type MongoExecutionTimeoutException"), expectedError.getBoolean("isTimeoutError").getValue(), e instanceof MongoExecutionTimeoutException); diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java index 08c4acc117c..d27c2e71ce3 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java @@ -105,6 +105,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; +import static com.mongodb.client.unified.UnifiedTestUtils.getAndRemoveTimeoutMS; import static java.util.Arrays.asList; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; @@ -1737,16 +1738,6 @@ private MongoCollection getMongoCollection(final BsonDocument oper return collection; } - @Nullable - private Long getAndRemoveTimeoutMS(final BsonDocument arguments) { - Long timeoutMS = null; - if (arguments.containsKey("timeoutMS")) { - timeoutMS = arguments.getNumber("timeoutMS").longValue(); - arguments.remove("timeoutMS"); - } - return timeoutMS; - } - private static void setCursorType(final FindIterable iterable, final Map.Entry cur) { switch (cur.getValue().asString().getValue()) { case "tailable": diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedGridFSHelper.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedGridFSHelper.java index 179d88a4a32..f525d64da3f 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedGridFSHelper.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedGridFSHelper.java @@ -17,6 +17,7 @@ package com.mongodb.client.unified; import com.mongodb.client.gridfs.GridFSBucket; +import com.mongodb.client.gridfs.GridFSFindIterable; import com.mongodb.client.gridfs.model.GridFSDownloadOptions; import com.mongodb.client.gridfs.model.GridFSUploadOptions; import com.mongodb.internal.HexUtils; @@ -32,8 +33,11 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.util.ArrayList; import java.util.Map; +import java.util.concurrent.TimeUnit; +import static com.mongodb.client.unified.UnifiedTestUtils.getAndRemoveTimeoutMS; import static java.util.Objects.requireNonNull; final class UnifiedGridFSHelper { @@ -43,8 +47,35 @@ final class UnifiedGridFSHelper { this.entities = entities; } + public OperationResult executeFind(final BsonDocument operation) { + GridFSFindIterable iterable = createGridFSFindIterable(operation); + try { + iterable.into(new ArrayList<>()); + return OperationResult.NONE; // we don't expect results in the tests. + } catch (Exception e) { + return OperationResult.of(e); + } + } + + public OperationResult executeRename(final BsonDocument operation) { + GridFSBucket bucket = getGirdFsBucket(operation); + BsonDocument arguments = operation.getDocument("arguments"); + BsonValue id = arguments.get("id"); + String fileName = arguments.get("newFilename").asString().getValue(); + + requireNonNull(id); + requireNonNull(fileName); + + try { + bucket.rename(id, fileName); + return OperationResult.NONE; + } catch (Exception e) { + return OperationResult.of(e); + } + } + OperationResult executeDelete(final BsonDocument operation) { - GridFSBucket bucket = entities.getBucket(operation.getString("object").getValue()); + GridFSBucket bucket = getGirdFsBucket(operation); BsonDocument arguments = operation.getDocument("arguments"); BsonValue id = arguments.get("id"); @@ -63,8 +94,23 @@ OperationResult executeDelete(final BsonDocument operation) { } } + public OperationResult executeDrop(final BsonDocument operation) { + GridFSBucket bucket = getGirdFsBucket(operation); + BsonDocument arguments = operation.getDocument("arguments", new BsonDocument()); + if (arguments.size() > 0) { + throw new UnsupportedOperationException("Unexpected arguments " + operation.get("arguments")); + } + + try { + bucket.drop(); + return OperationResult.NONE; + } catch (Exception e) { + return OperationResult.of(e); + } + } + public OperationResult executeDownload(final BsonDocument operation) { - GridFSBucket bucket = entities.getBucket(operation.getString("object").getValue()); + GridFSBucket bucket = getGirdFsBucket(operation); BsonDocument arguments = operation.getDocument("arguments"); BsonValue id = arguments.get("id"); @@ -119,7 +165,7 @@ private GridFSDownloadOptions getDownloadOptions(final BsonDocument arguments) { } public OperationResult executeUpload(final BsonDocument operation) { - GridFSBucket bucket = entities.getBucket(operation.getString("object").getValue()); + GridFSBucket bucket = getGirdFsBucket(operation); BsonDocument arguments = operation.getDocument("arguments"); String filename = null; @@ -165,4 +211,46 @@ public OperationResult executeUpload(final BsonDocument operation) { Document asDocument(final BsonDocument bsonDocument) { return new DocumentCodec().decode(new BsonDocumentReader(bsonDocument), DecoderContext.builder().build()); } + + private GridFSBucket getGirdFsBucket(final BsonDocument operation) { + GridFSBucket bucket = entities.getBucket(operation.getString("object").getValue()); + Long timeoutMS = getAndRemoveTimeoutMS(operation.getDocument("arguments", new BsonDocument())); + if (timeoutMS != null) { + bucket = bucket.withTimeout(timeoutMS, TimeUnit.MILLISECONDS); + } + return bucket; + } + + private GridFSFindIterable createGridFSFindIterable(final BsonDocument operation) { + GridFSBucket bucket = getGirdFsBucket(operation); + + BsonDocument arguments = operation.getDocument("arguments"); + BsonDocument filter = arguments.getDocument("filter"); + GridFSFindIterable iterable = bucket.find(filter); + for (Map.Entry cur : arguments.entrySet()) { + switch (cur.getKey()) { + case "session": + case "filter": + break; + case "sort": + iterable.sort(cur.getValue().asDocument()); + break; + case "batchSize": + iterable.batchSize(cur.getValue().asInt32().intValue()); + break; + case "maxTimeMS": + iterable.maxTime(cur.getValue().asInt32().longValue(), TimeUnit.MILLISECONDS); + break; + case "skip": + iterable.skip(cur.getValue().asInt32().intValue()); + break; + case "limit": + iterable.limit(cur.getValue().asInt32().intValue()); + break; + default: + throw new UnsupportedOperationException("Unsupported argument: " + cur.getKey()); + } + } + return iterable; + } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java index 8b76f426dbc..4754c319c5f 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java @@ -324,6 +324,7 @@ private void assertOperation(final UnifiedTestContext context, final BsonDocumen private OperationResult executeOperation(final UnifiedTestContext context, final BsonDocument operation, final int operationNum) { context.getAssertionContext().push(ContextElement.ofStartedOperation(operation, operationNum)); String name = operation.getString("name").getValue(); + String object = operation.getString("object").getValue(); try { switch (name) { case "createEntities": @@ -393,6 +394,9 @@ private OperationResult executeOperation(final UnifiedTestContext context, final case "aggregate": return crudHelper.executeAggregate(operation); case "find": + if ("bucket".equals(object)){ + return gridFSHelper.executeFind(operation); + } return crudHelper.executeFind(operation); case "findOne": return crudHelper.executeFindOne(operation); @@ -427,6 +431,9 @@ private OperationResult executeOperation(final UnifiedTestContext context, final case "modifyCollection": return crudHelper.executeModifyCollection(operation); case "rename": + if ("bucket".equals(object)){ + return gridFSHelper.executeRename(operation); + } return crudHelper.executeRenameCollection(operation); case "createSearchIndex": return crudHelper.executeCreateSearchIndex(operation); @@ -460,6 +467,8 @@ private OperationResult executeOperation(final UnifiedTestContext context, final return crudHelper.executeIterateUntilDocumentOrError(operation); case "delete": return gridFSHelper.executeDelete(operation); + case "drop": + return gridFSHelper.executeDrop(operation); case "download": return gridFSHelper.executeDownload(operation); case "downloadByName": @@ -885,9 +894,9 @@ private boolean indexExists(final BsonDocument operation) { } private List lastTwoCommandEvents(final TestCommandListener listener) { - List events = listener.getCommandStartedEvents(); + List events = listener.getCommandStartedEvents(); assertTrue(events.size() >= 2); - return events.subList(events.size() - 2, events.size()); + return new ArrayList<>(events.subList(events.size() - 2, events.size())); } private void addInitialData() { diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestUtils.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestUtils.java new file mode 100644 index 00000000000..96fdc3ff3ea --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestUtils.java @@ -0,0 +1,34 @@ +/* + * 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.client.unified; + +import org.bson.BsonDocument; + +public abstract class UnifiedTestUtils { + private UnifiedTestUtils() { + //NOP + } + + static Long getAndRemoveTimeoutMS(final BsonDocument arguments) { + Long timeoutMS = null; + if (arguments.containsKey("timeoutMS")) { + timeoutMS = arguments.getNumber("timeoutMS").longValue(); + arguments.remove("timeoutMS"); + } + return timeoutMS; + } +} diff --git a/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSDownloadStreamSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSDownloadStreamSpecification.groovy index 99e8f7a2167..90b53014ed2 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSDownloadStreamSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSDownloadStreamSpecification.groovy @@ -33,7 +33,7 @@ class GridFSDownloadStreamSpecification extends Specification { def 'should return the file info'() { when: - def downloadStream = new GridFSDownloadStreamImpl(null, fileInfo, Stub(MongoCollection)) + def downloadStream = new GridFSDownloadStreamImpl(null, fileInfo, Stub(MongoCollection), null) then: downloadStream.getGridFSFile() == fileInfo @@ -56,7 +56,7 @@ class GridFSDownloadStreamSpecification extends Specification { def mongoCursor = Mock(MongoCursor) def findIterable = Mock(FindIterable) def chunksCollection = Mock(MongoCollection) - def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection) + def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection, null) then: downloadStream.available() == 0 @@ -126,7 +126,8 @@ class GridFSDownloadStreamSpecification extends Specification { def mongoCursor = Mock(MongoCursor) def findIterable = Mock(FindIterable) def chunksCollection = Mock(MongoCollection) - def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection).batchSize(1) + def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection, + null).batchSize(1) then: downloadStream.available() == 0 @@ -205,7 +206,7 @@ class GridFSDownloadStreamSpecification extends Specification { def mongoCursor = Mock(MongoCursor) def findIterable = Mock(FindIterable) def chunksCollection = Mock(MongoCollection) - def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection) + def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection, null) when: def skipResult = downloadStream.skip(15) @@ -281,7 +282,7 @@ class GridFSDownloadStreamSpecification extends Specification { def mongoCursor = Mock(MongoCursor) def findIterable = Mock(FindIterable) def chunksCollection = Mock(MongoCollection) - def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection) + def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection, null) when: def readByte = new byte[10] @@ -346,7 +347,7 @@ class GridFSDownloadStreamSpecification extends Specification { def mongoCursor = Mock(MongoCursor) def findIterable = Mock(FindIterable) def chunksCollection = Mock(MongoCollection) - def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection) + def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection, null) when: downloadStream.mark() @@ -421,7 +422,7 @@ class GridFSDownloadStreamSpecification extends Specification { def mongoCursor = Mock(MongoCursor) def findIterable = Mock(FindIterable) def chunksCollection = Mock(MongoCollection) - def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection) + def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection, null) when: def readByte = new byte[25] @@ -478,7 +479,7 @@ class GridFSDownloadStreamSpecification extends Specification { def 'should not throw an exception when trying to mark post close'() { given: - def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, Stub(MongoCollection)) + def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, Stub(MongoCollection), null) downloadStream.close() when: @@ -499,7 +500,7 @@ class GridFSDownloadStreamSpecification extends Specification { def 'should handle negative skip value correctly '() { given: - def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, Stub(MongoCollection)) + def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, Stub(MongoCollection), null) when: def result = downloadStream.skip(-1) @@ -514,7 +515,7 @@ class GridFSDownloadStreamSpecification extends Specification { def 'should handle skip that is larger or equal to the file length'() { given: def chunksCollection = Mock(MongoCollection) - def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection) + def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection, null) when: def result = downloadStream.skip(skipValue) @@ -535,7 +536,7 @@ class GridFSDownloadStreamSpecification extends Specification { def 'should throw if trying to pass negative batchSize'() { given: - def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, Stub(MongoCollection)) + def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, Stub(MongoCollection), null) when: downloadStream.batchSize(0) @@ -559,7 +560,7 @@ class GridFSDownloadStreamSpecification extends Specification { def mongoCursor = Mock(MongoCursor) def findIterable = Mock(FindIterable) def chunksCollection = Mock(MongoCollection) - def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection) + def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection, null) when: downloadStream.read() @@ -591,7 +592,7 @@ class GridFSDownloadStreamSpecification extends Specification { def mongoCursor = Mock(MongoCursor) def findIterable = Mock(FindIterable) def chunksCollection = Mock(MongoCollection) - def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection) + def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection, null) when: downloadStream.read() @@ -617,7 +618,7 @@ class GridFSDownloadStreamSpecification extends Specification { def 'should throw an exception when trying to action post close'() { given: - def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, Stub(MongoCollection)) + def downloadStream = new GridFSDownloadStreamImpl(clientSession, fileInfo, Stub(MongoCollection), null) downloadStream.close() when: From bae4b50e93674b431b22c389b69385072e5f0b07 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Wed, 3 Jan 2024 20:50:46 -0800 Subject: [PATCH 02/19] Increase timeouts in prose tests. JAVA-5277 --- .../AbstractClientSideOperationsTimeoutProseTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java index 048cab4893c..0ce489107c8 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -99,11 +99,11 @@ public void testGridFSUploadViaOpenUploadStreamTimeout() { + " data: {" + " failCommands: [\"insert\"]," + " blockConnection: true," - + " blockTimeMS: " + 50 + + " blockTimeMS: " + 100 + " }" + "}"); try (MongoClient client = createMongoClient(getMongoClientSettingsBuilder() - .timeout(45, TimeUnit.MILLISECONDS))) { + .timeout(95, TimeUnit.MILLISECONDS))) { MongoDatabase database = client.getDatabase(namespace.getDatabaseName()); GridFSBucket gridFsBucket = createGridFsBucket(database, GRID_FS_BUCKET_NAME); @@ -116,6 +116,7 @@ public void testGridFSUploadViaOpenUploadStreamTimeout() { @Tag("setsFailPoint") @FlakyTest(maxAttempts = 3) + @Disabled("TODO (CSOT) - JAVA-4057") @DisplayName("6. GridFS Upload - Aborting an upload stream can be timed out") public void testAbortingGridFsUploadStreamTimeout() { assumeTrue(serverVersionAtLeast(4, 4)); @@ -125,11 +126,11 @@ public void testAbortingGridFsUploadStreamTimeout() { + " data: {" + " failCommands: [\"delete\"]," + " blockConnection: true," - + " blockTimeMS: " + 50 + + " blockTimeMS: " + 100 + " }" + "}"); try (MongoClient client = createMongoClient(getMongoClientSettingsBuilder() - .timeout(45, TimeUnit.MILLISECONDS))) { + .timeout(95, TimeUnit.MILLISECONDS))) { MongoDatabase database = client.getDatabase(namespace.getDatabaseName()); GridFSBucket gridFsBucket = createGridFsBucket(database, GRID_FS_BUCKET_NAME).withChunkSizeBytes(2); From 7a397e10b8d72ee6190489453f5013ec633375fb Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Wed, 3 Jan 2024 21:51:42 -0800 Subject: [PATCH 03/19] Fix tests. JAVA-5277 --- .../internal/gridfs/GridFSBucketImpl.java | 2 +- .../client/gridfs/GridFSBucketImpl.java | 31 +++++++++--------- .../mongodb/client/gridfs/TimeoutUtils.java | 2 +- .../gridfs/GridFSBucketSpecification.groovy | 32 +++++++++++-------- .../GridFSUploadStreamSpecification.groovy | 21 ++++++------ 5 files changed, 46 insertions(+), 42 deletions(-) diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/gridfs/GridFSBucketImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/gridfs/GridFSBucketImpl.java index d92f68154dc..87f0217289a 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/gridfs/GridFSBucketImpl.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/gridfs/GridFSBucketImpl.java @@ -72,7 +72,7 @@ public GridFSBucketImpl(final MongoDatabase database, final String bucketName) { getChunksCollection(database, bucketName)); } - GridFSBucketImpl(final String bucketName, final int chunkSizeBytes, final MongoCollection filesCollection, + private GridFSBucketImpl(final String bucketName, final int chunkSizeBytes, final MongoCollection filesCollection, final MongoCollection chunksCollection) { this.bucketName = notNull("bucketName", bucketName); this.chunkSizeBytes = chunkSizeBytes; diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucketImpl.java b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucketImpl.java index c50e7dfd664..c8f5f49521d 100644 --- a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucketImpl.java +++ b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucketImpl.java @@ -36,6 +36,7 @@ import com.mongodb.client.result.UpdateResult; import com.mongodb.internal.TimeoutContext; +import com.mongodb.internal.VisibleForTesting; import com.mongodb.internal.time.Timeout; import com.mongodb.lang.Nullable; import org.bson.BsonDocument; @@ -68,8 +69,6 @@ final class GridFSBucketImpl implements GridFSBucket { private final MongoCollection filesCollection; private final MongoCollection chunksCollection; private volatile boolean checkedIndexes; - @Nullable - private final Long timeoutMs; GridFSBucketImpl(final MongoDatabase database) { this(database, "fs"); @@ -78,16 +77,16 @@ final class GridFSBucketImpl implements GridFSBucket { GridFSBucketImpl(final MongoDatabase database, final String bucketName) { this(notNull("bucketName", bucketName), DEFAULT_CHUNKSIZE_BYTES, getFilesCollection(notNull("database", database), bucketName), - getChunksCollection(database, bucketName), database.getTimeout(TimeUnit.MILLISECONDS)); + getChunksCollection(database, bucketName)); } + @VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE) GridFSBucketImpl(final String bucketName, final int chunkSizeBytes, final MongoCollection filesCollection, - final MongoCollection chunksCollection, @Nullable final Long timeoutMs) { + final MongoCollection chunksCollection) { this.bucketName = notNull("bucketName", bucketName); this.chunkSizeBytes = chunkSizeBytes; this.filesCollection = notNull("filesCollection", filesCollection); this.chunksCollection = notNull("chunksCollection", chunksCollection); - this.timeoutMs = timeoutMs; } @Override @@ -117,38 +116,38 @@ public ReadConcern getReadConcern() { @Override public Long getTimeout(final TimeUnit timeUnit) { - return timeoutMs == null ? null : notNull("timeUnit", timeUnit).convert(timeoutMs, MILLISECONDS); + return filesCollection.getTimeout(timeUnit); } @Override public GridFSBucket withChunkSizeBytes(final int chunkSizeBytes) { - return new GridFSBucketImpl(bucketName, chunkSizeBytes, filesCollection, chunksCollection, timeoutMs); + return new GridFSBucketImpl(bucketName, chunkSizeBytes, filesCollection, chunksCollection); } @Override public GridFSBucket withReadPreference(final ReadPreference readPreference) { return new GridFSBucketImpl(bucketName, chunkSizeBytes, filesCollection.withReadPreference(readPreference), - chunksCollection.withReadPreference(readPreference), timeoutMs); + chunksCollection.withReadPreference(readPreference)); } @Override public GridFSBucket withWriteConcern(final WriteConcern writeConcern) { return new GridFSBucketImpl(bucketName, chunkSizeBytes, filesCollection.withWriteConcern(writeConcern), - chunksCollection.withWriteConcern(writeConcern), timeoutMs); + chunksCollection.withWriteConcern(writeConcern)); } @Override public GridFSBucket withReadConcern(final ReadConcern readConcern) { return new GridFSBucketImpl(bucketName, chunkSizeBytes, filesCollection.withReadConcern(readConcern), - chunksCollection.withReadConcern(readConcern), timeoutMs); + chunksCollection.withReadConcern(readConcern)); } @Override public GridFSBucket withTimeout(final long timeout, final TimeUnit timeUnit) { isTrueArgument("timeout >= 0", timeout >= 0); notNull("timeUnit", timeUnit); - return new GridFSBucketImpl(bucketName, chunkSizeBytes, filesCollection, - chunksCollection, MILLISECONDS.convert(timeout, timeUnit)); + return new GridFSBucketImpl(bucketName, chunkSizeBytes, filesCollection.withTimeout(timeout, timeUnit), + chunksCollection.withTimeout(timeout, timeUnit)); } @Override @@ -437,12 +436,12 @@ private void executeDelete(@Nullable final ClientSession clientSession, final Bs if (clientSession != null) { result = withNullableTimeout(filesCollection, operationTimeout) .deleteOne(clientSession, new BsonDocument("_id", id)); - withNullableTimeout(filesCollection, operationTimeout) + withNullableTimeout(chunksCollection, operationTimeout) .deleteMany(clientSession, new BsonDocument("files_id", id)); } else { result = withNullableTimeout(filesCollection, operationTimeout) .deleteOne(new BsonDocument("_id", id)); - withNullableTimeout(filesCollection, operationTimeout) + withNullableTimeout(chunksCollection, operationTimeout) .deleteMany(new BsonDocument("files_id", id)); } @@ -621,7 +620,7 @@ private FindIterable createFindIterable(@Nullable final ClientSessio if (filter != null) { findIterable = findIterable.filter(filter); } - if (timeoutMs != null) { + if (filesCollection.getTimeout(MILLISECONDS) != null) { findIterable.timeoutMode(TimeoutMode.CURSOR_LIFETIME); } return findIterable; @@ -660,6 +659,6 @@ private static MongoCollection withNullableTimeout(final MongoCollection< @Nullable private Timeout startTimeout() { - return TimeoutContext.calculateTimeout(timeoutMs); + return TimeoutContext.calculateTimeout(filesCollection.getTimeout(MILLISECONDS)); } } diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/TimeoutUtils.java b/driver-sync/src/main/com/mongodb/client/gridfs/TimeoutUtils.java index 8b7349d751b..771eb362513 100644 --- a/driver-sync/src/main/com/mongodb/client/gridfs/TimeoutUtils.java +++ b/driver-sync/src/main/com/mongodb/client/gridfs/TimeoutUtils.java @@ -31,7 +31,7 @@ private TimeoutUtils(){ public static MongoCollection withNullableTimeout(final MongoCollection collection, final String message, @Nullable final Timeout timeout) { - if (timeout != null) { + if (timeout != null && !timeout.isInfinite()) { long remainingMs = timeout.remaining(MILLISECONDS); if (remainingMs <= 0) { // TODO (CSOT) - JAVA-5248 Update to MongoOperationTimeoutException diff --git a/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketSpecification.groovy index fdae1bee894..5f557c0110c 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketSpecification.groovy @@ -16,16 +16,8 @@ package com.mongodb.client.gridfs -import com.mongodb.MongoClientSettings -import com.mongodb.MongoGridFSException -import com.mongodb.MongoNamespace -import com.mongodb.ReadConcern -import com.mongodb.WriteConcern -import com.mongodb.client.ClientSession -import com.mongodb.client.FindIterable -import com.mongodb.client.ListIndexesIterable -import com.mongodb.client.MongoCollection -import com.mongodb.client.MongoCursor +import com.mongodb.* +import com.mongodb.client.* import com.mongodb.client.gridfs.model.GridFSDownloadOptions import com.mongodb.client.gridfs.model.GridFSFile import com.mongodb.client.internal.MongoDatabaseImpl @@ -33,6 +25,7 @@ import com.mongodb.client.internal.OperationExecutor import com.mongodb.client.internal.TestOperationExecutor import com.mongodb.client.result.DeleteResult import com.mongodb.client.result.UpdateResult +import com.mongodb.internal.TimeoutSettings import com.mongodb.internal.operation.BatchCursor import com.mongodb.internal.operation.FindOperation import org.bson.BsonDocument @@ -45,6 +38,8 @@ import org.bson.types.ObjectId import spock.lang.Specification import spock.lang.Unroll +import java.util.concurrent.TimeUnit + import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS import static com.mongodb.CustomMatchers.isTheSameAs import static com.mongodb.ReadPreference.primary @@ -156,7 +151,9 @@ class GridFSBucketSpecification extends Specification { given: def defaultChunkSizeBytes = 255 * 1024 def database = new MongoDatabaseImpl('test', fromProviders(new DocumentCodecProvider()), secondary(), WriteConcern.ACKNOWLEDGED, - false, false, readConcern, JAVA_LEGACY, null, null, new TestOperationExecutor([])) + false, false, readConcern, JAVA_LEGACY, null, + new TimeoutSettings(0, 0, 0, null, 0), + new TestOperationExecutor([])) when: def gridFSBucket = new GridFSBucketImpl(database) @@ -172,6 +169,9 @@ class GridFSBucketSpecification extends Specification { given: def filesCollection = Stub(MongoCollection) def chunksCollection = Stub(MongoCollection) + filesCollection.getTimeout(TimeUnit.MILLISECONDS) >> null + chunksCollection.getTimeout(TimeUnit.MILLISECONDS) >> null + def gridFSBucket = new GridFSBucketImpl('fs', 255, filesCollection, chunksCollection) when: @@ -184,7 +184,7 @@ class GridFSBucketSpecification extends Specification { then: expect stream, isTheSameAs(new GridFSUploadStreamImpl(clientSession, filesCollection, chunksCollection, stream.getId(), 'filename', - 255, null), ['closeLock']) + 255, null, null), ['closeLock']) where: clientSession << [null, Stub(ClientSession)] @@ -291,7 +291,9 @@ class GridFSBucketSpecification extends Specification { def fileInfo = new GridFSFile(fileId, 'File 1', 10, 255, new Date(), new Document()) def findIterable = Mock(FindIterable) def filesCollection = Mock(MongoCollection) + filesCollection.getTimeout(TimeUnit.MILLISECONDS) >> null def chunksCollection = Stub(MongoCollection) + chunksCollection.getTimeout(TimeUnit.MILLISECONDS) >> null def gridFSBucket = new GridFSBucketImpl('fs', 255, filesCollection, chunksCollection) when: @@ -312,7 +314,7 @@ class GridFSBucketSpecification extends Specification { 1 * findIterable.first() >> fileInfo then: - expect stream, isTheSameAs(new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection), ['closeLock', 'cursorLock']) + expect stream, isTheSameAs(new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection,null), ['closeLock', 'cursorLock']) where: @@ -516,7 +518,9 @@ class GridFSBucketSpecification extends Specification { def fileInfo = new GridFSFile(bsonFileId, filename, 10, 255, new Date(), new Document()) def findIterable = Mock(FindIterable) def filesCollection = Mock(MongoCollection) + filesCollection.getTimeout(TimeUnit.MILLISECONDS) >> null def chunksCollection = Stub(MongoCollection) + chunksCollection.getTimeout(TimeUnit.MILLISECONDS) >> null def gridFSBucket = new GridFSBucketImpl('fs', 255, filesCollection, chunksCollection) when: @@ -534,7 +538,7 @@ class GridFSBucketSpecification extends Specification { 1 * findIterable.first() >> fileInfo then: - expect stream, isTheSameAs(new GridFSDownloadStreamImpl(null, fileInfo, chunksCollection), ['closeLock', 'cursorLock']) + expect stream, isTheSameAs(new GridFSDownloadStreamImpl(null, fileInfo, chunksCollection, null), ['closeLock', 'cursorLock']) where: version | skip | sortOrder diff --git a/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSUploadStreamSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSUploadStreamSpecification.groovy index cff602d6f9a..04a11e85550 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSUploadStreamSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSUploadStreamSpecification.groovy @@ -34,7 +34,7 @@ class GridFSUploadStreamSpecification extends Specification { def 'should return the file id'() { when: def uploadStream = new GridFSUploadStreamImpl(null, Stub(MongoCollection), Stub(MongoCollection), fileId, filename, 255 - , metadata) + , metadata, null) then: uploadStream.getId() == fileId } @@ -44,7 +44,7 @@ class GridFSUploadStreamSpecification extends Specification { def filesCollection = Mock(MongoCollection) def chunksCollection = Mock(MongoCollection) def uploadStream = new GridFSUploadStreamImpl(clientSession, filesCollection, chunksCollection, fileId, filename, 2 - , metadata) + , metadata, null) when: uploadStream.write(1) @@ -70,7 +70,7 @@ class GridFSUploadStreamSpecification extends Specification { def filesCollection = Mock(MongoCollection) def chunksCollection = Mock(MongoCollection) def uploadStream = new GridFSUploadStreamImpl(clientSession, filesCollection, chunksCollection, fileId, filename, 255 - , null) + , null, null) when: uploadStream.write('file content ' as byte[]) @@ -100,7 +100,8 @@ class GridFSUploadStreamSpecification extends Specification { def chunksCollection = Mock(MongoCollection) def content = 'file content ' as byte[] def metadata = new Document('contentType', 'text/txt') - def uploadStream = new GridFSUploadStreamImpl(clientSession, filesCollection, chunksCollection, fileId, filename, 255, metadata) + def uploadStream = new GridFSUploadStreamImpl(clientSession, filesCollection, chunksCollection, fileId, filename, 255, + metadata, null) def filesId = fileId when: @@ -158,7 +159,7 @@ class GridFSUploadStreamSpecification extends Specification { def filesCollection = Mock(MongoCollection) def chunksCollection = Mock(MongoCollection) def uploadStream = new GridFSUploadStreamImpl(clientSession, filesCollection, chunksCollection, fileId, filename, 255 - , metadata) + , metadata, null) when: uploadStream.close() @@ -178,7 +179,7 @@ class GridFSUploadStreamSpecification extends Specification { given: def chunksCollection = Mock(MongoCollection) def uploadStream = new GridFSUploadStreamImpl(clientSession, Stub(MongoCollection), chunksCollection, fileId, filename, 255 - , metadata) + , metadata, null) when: uploadStream.write('file content ' as byte[]) @@ -198,7 +199,7 @@ class GridFSUploadStreamSpecification extends Specification { def 'should close the stream on abort'() { given: def uploadStream = new GridFSUploadStreamImpl(clientSession, Stub(MongoCollection), Stub(MongoCollection), fileId, filename, 255 - , metadata) + , metadata, null) uploadStream.write('file content ' as byte[]) uploadStream.abort() @@ -216,7 +217,7 @@ class GridFSUploadStreamSpecification extends Specification { given: def chunksCollection = Mock(MongoCollection) def uploadStream = new GridFSUploadStreamImpl(clientSession, Stub(MongoCollection), chunksCollection, fileId, filename, 255 - , metadata) + , metadata, null) when: uploadStream.write('file content ' as byte[]) @@ -234,7 +235,7 @@ class GridFSUploadStreamSpecification extends Specification { def filesCollection = Mock(MongoCollection) def chunksCollection = Mock(MongoCollection) def uploadStream = new GridFSUploadStreamImpl(clientSession, filesCollection, chunksCollection, fileId, filename, 255 - , metadata) + , metadata, null) when: uploadStream.close() uploadStream.write(1) @@ -252,7 +253,7 @@ class GridFSUploadStreamSpecification extends Specification { def filesCollection = Mock(MongoCollection) def chunksCollection = Mock(MongoCollection) def uploadStream = new GridFSUploadStreamImpl(clientSession, filesCollection, chunksCollection, fileId, filename, 255 - , metadata) + , metadata, null) when: uploadStream.getObjectId() From 3448d3675e5a8ad4a4a44c2d6cc84e589e5c0842 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Wed, 3 Jan 2024 22:09:27 -0800 Subject: [PATCH 04/19] Remove timeoutMode method from Scala GridFSObservable. JAVA-5277 --- .../scala/gridfs/GridFSFindObservable.scala | 21 ------------------- .../mongodb/client/AbstractUnifiedTest.java | 4 ++-- .../mongodb/client/unified/UnifiedTest.java | 2 +- 3 files changed, 3 insertions(+), 24 deletions(-) diff --git a/driver-scala/src/main/scala/org/mongodb/scala/gridfs/GridFSFindObservable.scala b/driver-scala/src/main/scala/org/mongodb/scala/gridfs/GridFSFindObservable.scala index 46066c30bde..fdbea9add70 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/gridfs/GridFSFindObservable.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/gridfs/GridFSFindObservable.scala @@ -121,27 +121,6 @@ case class GridFSFindObservable(private val wrapped: GridFSFindPublisher) extend this } - /** - * Sets the timeoutMode for the cursor. - * - * Requires the `timeout` to be set, either in the `MongoClientSettings`, - * via `MongoDatabase` or via `MongoCollection` - * - * If the `timeout` is set then: - * - * - For non-tailable cursors, the default value of timeoutMode is `TimeoutMode.CURSOR_LIFETIME` - * - For tailable cursors, the default value of timeoutMode is `TimeoutMode.ITERATION` and its an error - * to configure it as: `TimeoutMode.CURSOR_LIFETIME` - * - * @param timeoutMode the timeout mode - * @return this - * @since 4.x - */ - def timeoutMode(timeoutMode: TimeoutMode): GridFSFindObservable = { - wrapped.timeoutMode(timeoutMode) - this - } - /** * Helper to return a single observable limited to the first result. * diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractUnifiedTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractUnifiedTest.java index 75b3ea67b70..ac5aacd9ca2 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractUnifiedTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractUnifiedTest.java @@ -570,7 +570,7 @@ private void executeOperations(final BsonArray operations, final boolean throwEx } else if (operationName.equals("assertDifferentLsidOnLastTwoCommands")) { List events = lastTwoCommandEvents(); String eventsJson = commandListener.getCommandStartedEvents().stream() - .map(e -> ((CommandStartedEvent) e).getCommand().toJson()) + .map(e -> e.getCommand().toJson()) .collect(Collectors.joining(", ")); assertNotEquals(eventsJson, ((CommandStartedEvent) events.get(0)).getCommand().getDocument("lsid"), @@ -578,7 +578,7 @@ private void executeOperations(final BsonArray operations, final boolean throwEx } else if (operationName.equals("assertSameLsidOnLastTwoCommands")) { List events = lastTwoCommandEvents(); String eventsJson = commandListener.getCommandStartedEvents().stream() - .map(e -> ((CommandStartedEvent) e).getCommand().toJson()) + .map(e -> e.getCommand().toJson()) .collect(Collectors.joining(", ")); assertEquals(eventsJson, ((CommandStartedEvent) events.get(0)).getCommand().getDocument("lsid"), ((CommandStartedEvent) events.get(1)).getCommand().getDocument("lsid")); diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java index 4754c319c5f..e7682275438 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java @@ -828,7 +828,7 @@ private OperationResult executeAssertLsidOnLastTwoCommands(final BsonDocument op operation.getDocument("arguments").getString("client").getValue()); List events = lastTwoCommandEvents(listener); String eventsJson = listener.getCommandStartedEvents().stream() - .map(e -> ((CommandStartedEvent) e).getCommand().toJson()) + .map(e -> e.getCommand().toJson()) .collect(Collectors.joining(", ")); BsonDocument expected = ((CommandStartedEvent) events.get(0)).getCommand().getDocument("lsid"); BsonDocument actual = ((CommandStartedEvent) events.get(1)).getCommand().getDocument("lsid"); From 2096bb458238a8eb4eb25928de5bde7de31cfaeb Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Wed, 3 Jan 2024 23:27:35 -0800 Subject: [PATCH 05/19] Fix formatting issues. JAVA-5277 --- .../mongodb/client/AbstractServerSelectionProseTest.java | 1 - .../functional/com/mongodb/client/ReadConcernTest.java | 1 - .../com/mongodb/client/unified/ErrorMatcher.java | 1 - .../com/mongodb/client/unified/UnifiedCrudHelper.java | 1 - .../client/gridfs/GridFSBucketSpecification.groovy | 8 +++++--- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractServerSelectionProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractServerSelectionProseTest.java index 4fff4583426..0aa2ff28536 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractServerSelectionProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractServerSelectionProseTest.java @@ -18,7 +18,6 @@ import com.mongodb.ConnectionString; import com.mongodb.MongoClientSettings; import com.mongodb.ServerAddress; -import com.mongodb.event.CommandEvent; import com.mongodb.event.CommandStartedEvent; import com.mongodb.internal.connection.TestCommandListener; import org.bson.BsonArray; diff --git a/driver-sync/src/test/functional/com/mongodb/client/ReadConcernTest.java b/driver-sync/src/test/functional/com/mongodb/client/ReadConcernTest.java index 3ed0d975650..a40b258ea6c 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ReadConcernTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ReadConcernTest.java @@ -17,7 +17,6 @@ package com.mongodb.client; import com.mongodb.ReadConcern; -import com.mongodb.event.CommandEvent; import com.mongodb.event.CommandStartedEvent; import com.mongodb.internal.connection.TestCommandListener; import org.bson.BsonDocument; diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/ErrorMatcher.java b/driver-sync/src/test/functional/com/mongodb/client/unified/ErrorMatcher.java index 47878c4c11e..90f358be35a 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/ErrorMatcher.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/ErrorMatcher.java @@ -65,7 +65,6 @@ void assertErrorsMatch(final BsonDocument expectedError, final Exception e) { || e instanceof MongoSocketException); } if (expectedError.containsKey("isTimeoutError")) { - e.printStackTrace(); assertEquals(context.getMessage("Exception must be of type MongoExecutionTimeoutException"), expectedError.getBoolean("isTimeoutError").getValue(), e instanceof MongoExecutionTimeoutException); diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java index d27c2e71ce3..589f6084873 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java @@ -79,7 +79,6 @@ import com.mongodb.client.result.InsertOneResult; import com.mongodb.client.result.UpdateResult; import com.mongodb.lang.NonNull; -import com.mongodb.lang.Nullable; import org.bson.BsonArray; import org.bson.BsonDocument; import org.bson.BsonDocumentWriter; diff --git a/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketSpecification.groovy index 5f557c0110c..450f67791df 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketSpecification.groovy @@ -16,8 +16,9 @@ package com.mongodb.client.gridfs -import com.mongodb.* -import com.mongodb.client.* +import com.mongodb.MongoGridFSException +import com.mongodb.MongoNamespace +import com.mongodb.client.ClientSession import com.mongodb.client.gridfs.model.GridFSDownloadOptions import com.mongodb.client.gridfs.model.GridFSFile import com.mongodb.client.internal.MongoDatabaseImpl @@ -314,7 +315,8 @@ class GridFSBucketSpecification extends Specification { 1 * findIterable.first() >> fileInfo then: - expect stream, isTheSameAs(new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection,null), ['closeLock', 'cursorLock']) + expect stream, isTheSameAs(new GridFSDownloadStreamImpl(clientSession, fileInfo, chunksCollection, + null), ['closeLock', 'cursorLock']) where: From b84802ed620a96b0fcb4dfb6b44f659555e14a99 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Wed, 3 Jan 2024 23:31:14 -0800 Subject: [PATCH 06/19] Increase timeout in prose tests. JAVA-5277 --- .../client/AbstractClientSideOperationsTimeoutProseTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java index 0ce489107c8..a1cbf4df3c1 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -148,7 +148,7 @@ public void testGridFsDownloadStreamTimeout() { assumeTrue(serverVersionAtLeast(4, 4)); try (MongoClient client = createMongoClient(getMongoClientSettingsBuilder() - .timeout(20, TimeUnit.MILLISECONDS))) { + .timeout(30, TimeUnit.MILLISECONDS))) { MongoDatabase database = client.getDatabase(namespace.getDatabaseName()); GridFSBucket gridFsBucket = createGridFsBucket(database, GRID_FS_BUCKET_NAME).withChunkSizeBytes(2); database.getCollection(GRID_FS_COLLECTION_NAME_FILE) @@ -177,7 +177,7 @@ public void testGridFsDownloadStreamTimeout() { + " data: {" + " failCommands: [\"find\"]," + " blockConnection: true," - + " blockTimeMS: " + 25 + + " blockTimeMS: " + 35 + " }" + "}"); assertThrows(MongoExecutionTimeoutException.class, downloadStream::read); From 2f27cbc5b583efcef81734c04a8dc5ea4d4f7789 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Thu, 4 Jan 2024 00:12:11 -0800 Subject: [PATCH 07/19] Fix tests. JAVA-5277 --- .../mongodb/client/gridfs/GridFSDownloadStreamImpl.java | 1 - .../com/mongodb/client/gridfs/GridFSFindIterableImpl.java | 1 - .../mongodb/client/gridfs/GridFSBucketSpecification.groovy | 7 +++++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSDownloadStreamImpl.java b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSDownloadStreamImpl.java index 112188c303f..5ddd0c3fce1 100644 --- a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSDownloadStreamImpl.java +++ b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSDownloadStreamImpl.java @@ -36,7 +36,6 @@ import static com.mongodb.assertions.Assertions.notNull; import static com.mongodb.internal.Locks.withInterruptibleLock; import static java.lang.String.format; -import static java.util.concurrent.TimeUnit.MILLISECONDS; class GridFSDownloadStreamImpl extends GridFSDownloadStream { private static final String TIMEOUT_MESSAGE = "The GridFS download stream has timed out"; diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSFindIterableImpl.java b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSFindIterableImpl.java index 240f2ec0acc..80bfb498e12 100644 --- a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSFindIterableImpl.java +++ b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSFindIterableImpl.java @@ -20,7 +20,6 @@ import com.mongodb.client.FindIterable; import com.mongodb.client.MongoCursor; import com.mongodb.client.MongoIterable; -import com.mongodb.client.cursor.TimeoutMode; import com.mongodb.client.gridfs.model.GridFSFile; import com.mongodb.client.model.Collation; import com.mongodb.lang.Nullable; diff --git a/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketSpecification.groovy index 450f67791df..ac3fb98281c 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/gridfs/GridFSBucketSpecification.groovy @@ -16,9 +16,16 @@ package com.mongodb.client.gridfs +import com.mongodb.MongoClientSettings import com.mongodb.MongoGridFSException import com.mongodb.MongoNamespace +import com.mongodb.ReadConcern +import com.mongodb.WriteConcern import com.mongodb.client.ClientSession +import com.mongodb.client.FindIterable +import com.mongodb.client.ListIndexesIterable +import com.mongodb.client.MongoCollection +import com.mongodb.client.MongoCursor import com.mongodb.client.gridfs.model.GridFSDownloadOptions import com.mongodb.client.gridfs.model.GridFSFile import com.mongodb.client.internal.MongoDatabaseImpl From 57452fc8603642d1acbb2e108a30f50a9b35726b Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Thu, 4 Jan 2024 16:00:31 -0800 Subject: [PATCH 08/19] Update tests. JAVA-5277 --- .../client/AbstractClientSideOperationsTimeoutProseTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java index a1cbf4df3c1..fee7bc85e9d 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -151,8 +151,7 @@ public void testGridFsDownloadStreamTimeout() { .timeout(30, TimeUnit.MILLISECONDS))) { MongoDatabase database = client.getDatabase(namespace.getDatabaseName()); GridFSBucket gridFsBucket = createGridFsBucket(database, GRID_FS_BUCKET_NAME).withChunkSizeBytes(2); - database.getCollection(GRID_FS_COLLECTION_NAME_FILE) - .insertOne(Document.parse( + filesCollectionHelper.insertDocuments(Document.parse( "{" + " _id: {" + " $oid: \"000000000000000000000005\"" From 9e8f75ea92917604135f5aceb818b22347f4f4c4 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Fri, 5 Jan 2024 14:29:45 -0800 Subject: [PATCH 09/19] Account for RTT in tests. JAVA-5277 --- ...tClientSideOperationsTimeoutProseTest.java | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java index fee7bc85e9d..8216ee3b549 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -93,17 +93,19 @@ public abstract class AbstractClientSideOperationsTimeoutProseTest { @Disabled("TODO (CSOT) - JAVA-4057") public void testGridFSUploadViaOpenUploadStreamTimeout() { assumeTrue(serverVersionAtLeast(4, 4)); + long rtt = ClusterFixture.getPrimaryRTT(); + collectionHelper.runAdminCommand("{" + " configureFailPoint: \"failCommand\"," + " mode: { times: 1 }," + " data: {" + " failCommands: [\"insert\"]," + " blockConnection: true," - + " blockTimeMS: " + 100 + + " blockTimeMS: " + rtt + 100 + " }" + "}"); try (MongoClient client = createMongoClient(getMongoClientSettingsBuilder() - .timeout(95, TimeUnit.MILLISECONDS))) { + .timeout(rtt + 95, TimeUnit.MILLISECONDS))) { MongoDatabase database = client.getDatabase(namespace.getDatabaseName()); GridFSBucket gridFsBucket = createGridFsBucket(database, GRID_FS_BUCKET_NAME); @@ -120,17 +122,19 @@ public void testGridFSUploadViaOpenUploadStreamTimeout() { @DisplayName("6. GridFS Upload - Aborting an upload stream can be timed out") public void testAbortingGridFsUploadStreamTimeout() { assumeTrue(serverVersionAtLeast(4, 4)); + long rtt = ClusterFixture.getPrimaryRTT(); + collectionHelper.runAdminCommand("{" + " configureFailPoint: \"failCommand\"," + " mode: { times: 1 }," + " data: {" + " failCommands: [\"delete\"]," + " blockConnection: true," - + " blockTimeMS: " + 100 + + " blockTimeMS: " + rtt + 100 + " }" + "}"); try (MongoClient client = createMongoClient(getMongoClientSettingsBuilder() - .timeout(95, TimeUnit.MILLISECONDS))) { + .timeout(rtt + 95, TimeUnit.MILLISECONDS))) { MongoDatabase database = client.getDatabase(namespace.getDatabaseName()); GridFSBucket gridFsBucket = createGridFsBucket(database, GRID_FS_BUCKET_NAME).withChunkSizeBytes(2); @@ -146,9 +150,10 @@ public void testAbortingGridFsUploadStreamTimeout() { @DisplayName("6. GridFS Download") public void testGridFsDownloadStreamTimeout() { assumeTrue(serverVersionAtLeast(4, 4)); + long rtt = ClusterFixture.getPrimaryRTT(); try (MongoClient client = createMongoClient(getMongoClientSettingsBuilder() - .timeout(30, TimeUnit.MILLISECONDS))) { + .timeout(rtt + 100, TimeUnit.MILLISECONDS))) { MongoDatabase database = client.getDatabase(namespace.getDatabaseName()); GridFSBucket gridFsBucket = createGridFsBucket(database, GRID_FS_BUCKET_NAME).withChunkSizeBytes(2); filesCollectionHelper.insertDocuments(Document.parse( @@ -176,7 +181,7 @@ public void testGridFsDownloadStreamTimeout() { + " data: {" + " failCommands: [\"find\"]," + " blockConnection: true," - + " blockTimeMS: " + 35 + + " blockTimeMS: " + rtt + 95 + " }" + "}"); assertThrows(MongoExecutionTimeoutException.class, downloadStream::read); @@ -322,9 +327,6 @@ public void setUp() { new MongoNamespace(getDefaultDatabaseName(), GRID_FS_COLLECTION_NAME_FILE)); chunksCollectionHelper = new CollectionHelper<>(new BsonDocumentCodec(), new MongoNamespace(getDefaultDatabaseName(), GRID_FS_COLLECTION_NAME_CHUNKS)); - collectionHelper.drop(); - filesCollectionHelper.drop(); - chunksCollectionHelper.drop(); commandListener = new TestCommandListener(); } From 0da8b1148261e53007507ed4e511c30302fb9fd5 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Sun, 7 Jan 2024 22:14:47 -0800 Subject: [PATCH 10/19] Fix test issues. JAVA-5277 --- ...bstractClientSideOperationsTimeoutProseTest.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java index 8216ee3b549..06004deb6f9 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -64,6 +64,7 @@ import static com.mongodb.ClusterFixture.sleep; import static com.mongodb.client.Fixture.getDefaultDatabaseName; import static com.mongodb.client.Fixture.getPrimary; +import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -101,7 +102,7 @@ public void testGridFSUploadViaOpenUploadStreamTimeout() { + " data: {" + " failCommands: [\"insert\"]," + " blockConnection: true," - + " blockTimeMS: " + rtt + 100 + + " blockTimeMS: " + (rtt + 100) + " }" + "}"); try (MongoClient client = createMongoClient(getMongoClientSettingsBuilder() @@ -130,7 +131,7 @@ public void testAbortingGridFsUploadStreamTimeout() { + " data: {" + " failCommands: [\"delete\"]," + " blockConnection: true," - + " blockTimeMS: " + rtt + 100 + + " blockTimeMS: " + (rtt + 100) + " }" + "}"); try (MongoClient client = createMongoClient(getMongoClientSettingsBuilder() @@ -156,7 +157,7 @@ public void testGridFsDownloadStreamTimeout() { .timeout(rtt + 100, TimeUnit.MILLISECONDS))) { MongoDatabase database = client.getDatabase(namespace.getDatabaseName()); GridFSBucket gridFsBucket = createGridFsBucket(database, GRID_FS_BUCKET_NAME).withChunkSizeBytes(2); - filesCollectionHelper.insertDocuments(Document.parse( + filesCollectionHelper.insertDocuments(asList(BsonDocument.parse( "{" + " _id: {" + " $oid: \"000000000000000000000005\"" @@ -172,7 +173,7 @@ public void testGridFsDownloadStreamTimeout() { + " aliases: []," + " metadata: {}" + "}" - )); + )), WriteConcern.MAJORITY); try (GridFSDownloadStream downloadStream = gridFsBucket.openDownloadStream(new ObjectId("000000000000000000000005"))){ collectionHelper.runAdminCommand("{" @@ -181,7 +182,7 @@ public void testGridFsDownloadStreamTimeout() { + " data: {" + " failCommands: [\"find\"]," + " blockConnection: true," - + " blockTimeMS: " + rtt + 95 + + " blockTimeMS: " + (rtt + 95) + " }" + "}"); assertThrows(MongoExecutionTimeoutException.class, downloadStream::read); @@ -332,7 +333,7 @@ public void setUp() { @AfterEach public void tearDown(final TestInfo info) { - if (info.getTags().contains("usesFailPoint")) { + if (info.getTags().contains("setsFailPoint")) { collectionHelper.runAdminCommand("{configureFailPoint: \"failCommand\", mode: \"off\"}"); } From 3d33e338350218f04fbbbe457a6e575bd2e1adc3 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 8 Jan 2024 00:56:08 -0800 Subject: [PATCH 11/19] Check for minimum server version. JAVA-5277 --- .../client/AbstractClientSideOperationsTimeoutProseTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java index 06004deb6f9..a062c323bf2 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -333,7 +333,7 @@ public void setUp() { @AfterEach public void tearDown(final TestInfo info) { - if (info.getTags().contains("setsFailPoint")) { + if (info.getTags().contains("setsFailPoint") && serverVersionAtLeast(4, 4)) { collectionHelper.runAdminCommand("{configureFailPoint: \"failCommand\", mode: \"off\"}"); } From 1ba1bd2af73220c9dc9abc27636a94a7c9647ea9 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 8 Jan 2024 01:28:44 -0800 Subject: [PATCH 12/19] Merge with CSOT branch. JAVA-5277 --- .../internal/connection/TestCommandListener.java | 10 +++++++--- .../AbstractClientSideOperationsTimeoutProseTest.java | 6 +++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/TestCommandListener.java b/driver-core/src/test/functional/com/mongodb/internal/connection/TestCommandListener.java index 3f8c3b34c24..c02065363f9 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/TestCommandListener.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/TestCommandListener.java @@ -41,6 +41,7 @@ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; import static com.mongodb.ClusterFixture.TIMEOUT; import static com.mongodb.internal.connection.InternalStreamConnection.getSecuritySensitiveCommands; @@ -147,14 +148,17 @@ public List getCommandStartedEvents() { return getEvents(CommandStartedEvent.class, Integer.MAX_VALUE); } - public List getCommandSucceededEvents() { + public List getCommandSucceededEvents() { return getEvents(CommandSucceededEvent.class, Integer.MAX_VALUE); } - private List getEvents(final Class type, final int maxEvents) { + private List getEvents(final Class type, final int maxEvents) { lock.lock(); try { - return getEvents().stream().filter(e -> e.getClass() == type).limit(maxEvents).collect(Collectors.toList()); + return getEvents().stream() + .filter(e -> e.getClass() == type) + .map(type::cast) + .limit(maxEvents).collect(Collectors.toList()); } finally { lock.unlock(); } diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java index fe65ebc3d5f..58f8ce7f3fd 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -33,8 +33,8 @@ import com.mongodb.client.model.changestream.ChangeStreamDocument; import com.mongodb.client.model.changestream.FullDocument; import com.mongodb.client.test.CollectionHelper; -import com.mongodb.event.CommandEvent; import com.mongodb.event.CommandStartedEvent; +import com.mongodb.event.CommandSucceededEvent; import com.mongodb.internal.connection.ServerHelper; import com.mongodb.internal.connection.TestCommandListener; import com.mongodb.test.FlakyTest; @@ -233,7 +233,7 @@ public void testBlockingIterationMethodsTailableCursor() { assertThrows(MongoExecutionTimeoutException.class, cursor::next); } - List events = commandListener.getCommandSucceededEvents(); + List events = commandListener.getCommandSucceededEvents(); assertEquals(1, events.stream().filter(e -> e.getCommandName().equals("find")).count()); long getMoreCount = events.stream().filter(e -> e.getCommandName().equals("getMore")).count(); assertTrue(getMoreCount <= 2, "getMoreCount expected to less than or equal to two but was: " + getMoreCount); @@ -281,7 +281,7 @@ public void testBlockingIterationMethodsChangeStream() { assertEquals(1, fullDocument.get("x")); assertThrows(MongoExecutionTimeoutException.class, cursor::next); } - List events = commandListener.getCommandSucceededEvents(); + List events = commandListener.getCommandSucceededEvents(); assertEquals(1, events.stream().filter(e -> e.getCommandName().equals("aggregate")).count()); long getMoreCount = events.stream().filter(e -> e.getCommandName().equals("getMore")).count(); assertTrue(getMoreCount <= 2, "getMoreCount expected to less than or equal to two but was: " + getMoreCount); From d1a94497ff7e545a2ecf172a6d3c4d14aa72a2d7 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 8 Jan 2024 14:41:08 -0800 Subject: [PATCH 13/19] Rename classes. Add tests. JAVA-5277 --- ...tils.java => CollectionTimeoutHelper.java} | 12 +-- .../client/gridfs/GridFSBucketImpl.java | 20 ++-- .../gridfs/GridFSDownloadStreamImpl.java | 2 +- .../client/gridfs/GridFSUploadStreamImpl.java | 2 +- ...tClientSideOperationsTimeoutProseTest.java | 10 +- .../client/unified/UnifiedCrudHelper.java | 3 +- .../client/unified/UnifiedGridFSHelper.java | 3 +- ...ifiedTestUtils.java => UnifiedHelper.java} | 5 +- .../client/gridfs/TimeoutUtilsTest.java | 96 +++++++++++++++++++ 9 files changed, 122 insertions(+), 31 deletions(-) rename driver-sync/src/main/com/mongodb/client/gridfs/{TimeoutUtils.java => CollectionTimeoutHelper.java} (76%) rename driver-sync/src/test/functional/com/mongodb/client/unified/{UnifiedTestUtils.java => UnifiedHelper.java} (91%) create mode 100644 driver-sync/src/test/unit/com/mongodb/client/gridfs/TimeoutUtilsTest.java diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/TimeoutUtils.java b/driver-sync/src/main/com/mongodb/client/gridfs/CollectionTimeoutHelper.java similarity index 76% rename from driver-sync/src/main/com/mongodb/client/gridfs/TimeoutUtils.java rename to driver-sync/src/main/com/mongodb/client/gridfs/CollectionTimeoutHelper.java index 771eb362513..696c8d75df7 100644 --- a/driver-sync/src/main/com/mongodb/client/gridfs/TimeoutUtils.java +++ b/driver-sync/src/main/com/mongodb/client/gridfs/CollectionTimeoutHelper.java @@ -23,17 +23,17 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; -final class TimeoutUtils { - private TimeoutUtils(){ +final class CollectionTimeoutHelper { + private CollectionTimeoutHelper(){ //NOP } - public static MongoCollection withNullableTimeout(final MongoCollection collection, - final String message, - @Nullable final Timeout timeout) { + public static MongoCollection collectionWithTimeout(final MongoCollection collection, + final String message, + @Nullable final Timeout timeout) { if (timeout != null && !timeout.isInfinite()) { long remainingMs = timeout.remaining(MILLISECONDS); - if (remainingMs <= 0) { + if (timeout.hasExpired()) { // TODO (CSOT) - JAVA-5248 Update to MongoOperationTimeoutException throw new MongoExecutionTimeoutException(message); } diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucketImpl.java b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucketImpl.java index c8f5f49521d..326cf6ae2a9 100644 --- a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucketImpl.java +++ b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucketImpl.java @@ -283,10 +283,10 @@ public GridFSDownloadStream openDownloadStream(final ObjectId id) { @Override public GridFSDownloadStream openDownloadStream(final BsonValue id) { - Timeout oprationTimeout = startTimeout(); + Timeout operationTimeout = startTimeout(); - GridFSFile fileInfo = getFileInfoById(null, id, oprationTimeout); - return createGridFSDownloadStream(null, fileInfo, oprationTimeout); + GridFSFile fileInfo = getFileInfoById(null, id, operationTimeout); + return createGridFSDownloadStream(null, fileInfo, operationTimeout); } @Override @@ -489,17 +489,17 @@ private void executeRename(@Nullable final ClientSession clientSession, final Bs @Override public void drop() { - Timeout oeprationTimeout = startTimeout(); - withNullableTimeout(filesCollection, oeprationTimeout).drop(); - withNullableTimeout(chunksCollection, oeprationTimeout).drop(); + Timeout operationTimeout = startTimeout(); + withNullableTimeout(filesCollection, operationTimeout).drop(); + withNullableTimeout(chunksCollection, operationTimeout).drop(); } @Override public void drop(final ClientSession clientSession) { - Timeout oeprationTimeout = startTimeout(); + Timeout operationTimeout = startTimeout(); notNull("clientSession", clientSession); - withNullableTimeout(filesCollection, oeprationTimeout).drop(clientSession); - withNullableTimeout(chunksCollection, oeprationTimeout).drop(clientSession); + withNullableTimeout(filesCollection, operationTimeout).drop(clientSession); + withNullableTimeout(chunksCollection, operationTimeout).drop(clientSession); } private static MongoCollection getFilesCollection(final MongoDatabase database, final String bucketName) { @@ -654,7 +654,7 @@ private void downloadToStream(final GridFSDownloadStream downloadStream, final O private static MongoCollection withNullableTimeout(final MongoCollection chunksCollection, @Nullable final Timeout timeout) { - return TimeoutUtils.withNullableTimeout(chunksCollection, TIMEOUT_MESSAGE, timeout); + return CollectionTimeoutHelper.collectionWithTimeout(chunksCollection, TIMEOUT_MESSAGE, timeout); } @Nullable diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSDownloadStreamImpl.java b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSDownloadStreamImpl.java index 5ddd0c3fce1..d3f36643cb5 100644 --- a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSDownloadStreamImpl.java +++ b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSDownloadStreamImpl.java @@ -308,6 +308,6 @@ private byte[] getBuffer(final int chunkIndexToFetch) { private MongoCollection withNullableTimeout(final MongoCollection chunksCollection, @Nullable final Timeout timeout) { - return TimeoutUtils.withNullableTimeout(chunksCollection, TIMEOUT_MESSAGE, timeout); + return CollectionTimeoutHelper.collectionWithTimeout(chunksCollection, TIMEOUT_MESSAGE, timeout); } } diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSUploadStreamImpl.java b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSUploadStreamImpl.java index b57d2b5a76d..3355459e5b0 100644 --- a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSUploadStreamImpl.java +++ b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSUploadStreamImpl.java @@ -206,6 +206,6 @@ private void checkClosed() { private static MongoCollection withNullableTimeout(final MongoCollection collection, @Nullable final Timeout timeout) { - return TimeoutUtils.withNullableTimeout(collection, TIMEOUT_MESSAGE, timeout); + return CollectionTimeoutHelper.collectionWithTimeout(collection, TIMEOUT_MESSAGE, timeout); } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java index cc60299ce72..529afad6b47 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -43,14 +43,15 @@ import org.bson.BsonTimestamp; import org.bson.Document; import org.bson.codecs.BsonDocumentCodec; -import org.jetbrains.annotations.Nullable; import org.bson.types.ObjectId; +import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -61,7 +62,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; -import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; import static com.mongodb.ClusterFixture.isDiscoverableReplicaSet; @@ -99,9 +99,9 @@ public abstract class AbstractClientSideOperationsTimeoutProseTest { protected abstract GridFSBucket createGridFsBucket(MongoDatabase mongoDatabase, String bucketName); @Tag("setsFailPoint") - @FlakyTest(maxAttempts = 3) @DisplayName("6. GridFS Upload - uploads via openUploadStream can be timed out") @Disabled("TODO (CSOT) - JAVA-4057") + @Test public void testGridFSUploadViaOpenUploadStreamTimeout() { assumeTrue(serverVersionAtLeast(4, 4)); long rtt = ClusterFixture.getPrimaryRTT(); @@ -128,9 +128,9 @@ public void testGridFSUploadViaOpenUploadStreamTimeout() { } @Tag("setsFailPoint") - @FlakyTest(maxAttempts = 3) @Disabled("TODO (CSOT) - JAVA-4057") @DisplayName("6. GridFS Upload - Aborting an upload stream can be timed out") + @Test public void testAbortingGridFsUploadStreamTimeout() { assumeTrue(serverVersionAtLeast(4, 4)); long rtt = ClusterFixture.getPrimaryRTT(); @@ -157,8 +157,8 @@ public void testAbortingGridFsUploadStreamTimeout() { } @Tag("setsFailPoint") - @FlakyTest(maxAttempts = 3) @DisplayName("6. GridFS Download") + @Test public void testGridFsDownloadStreamTimeout() { assumeTrue(serverVersionAtLeast(4, 4)); long rtt = ClusterFixture.getPrimaryRTT(); diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java index 82567dcae60..1cd5dff6512 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java @@ -104,12 +104,11 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; -import static com.mongodb.client.unified.UnifiedTestUtils.getAndRemoveTimeoutMS; import static java.util.Arrays.asList; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; -final class UnifiedCrudHelper { +final class UnifiedCrudHelper extends UnifiedHelper { private final Entities entities; private final String testDescription; private final AtomicInteger uniqueIdGenerator = new AtomicInteger(); diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedGridFSHelper.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedGridFSHelper.java index f525d64da3f..31f61f50d13 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedGridFSHelper.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedGridFSHelper.java @@ -37,10 +37,9 @@ import java.util.Map; import java.util.concurrent.TimeUnit; -import static com.mongodb.client.unified.UnifiedTestUtils.getAndRemoveTimeoutMS; import static java.util.Objects.requireNonNull; -final class UnifiedGridFSHelper { +final class UnifiedGridFSHelper extends UnifiedHelper{ private final Entities entities; UnifiedGridFSHelper(final Entities entities) { diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestUtils.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedHelper.java similarity index 91% rename from driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestUtils.java rename to driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedHelper.java index 96fdc3ff3ea..027ccf92fb5 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestUtils.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedHelper.java @@ -18,10 +18,7 @@ import org.bson.BsonDocument; -public abstract class UnifiedTestUtils { - private UnifiedTestUtils() { - //NOP - } +abstract class UnifiedHelper { static Long getAndRemoveTimeoutMS(final BsonDocument arguments) { Long timeoutMS = null; diff --git a/driver-sync/src/test/unit/com/mongodb/client/gridfs/TimeoutUtilsTest.java b/driver-sync/src/test/unit/com/mongodb/client/gridfs/TimeoutUtilsTest.java new file mode 100644 index 00000000000..62fe496234b --- /dev/null +++ b/driver-sync/src/test/unit/com/mongodb/client/gridfs/TimeoutUtilsTest.java @@ -0,0 +1,96 @@ +package com.mongodb.client.gridfs; + +import com.mongodb.MongoExecutionTimeoutException; +import com.mongodb.client.MongoCollection; +import com.mongodb.internal.time.Timeout; +import org.bson.Document; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.TimeUnit; + +import static com.mongodb.client.gridfs.CollectionTimeoutHelper.collectionWithTimeout; +import static com.mongodb.internal.mockito.MongoMockito.mock; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.longThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +class TimeoutUtilsTest { + + private static final String TIMEOUT_ERROR_MESSAGE = "message"; + + @Test + void shouldNotSetRemainingTimeoutWhenTimeoutIsNull() { + //given + MongoCollection collection = mock(MongoCollection.class); + + //when + MongoCollection result = collectionWithTimeout(collection, TIMEOUT_ERROR_MESSAGE, null); + + //then + assertEquals(collection, result); + } + + @Test + void shouldNotSetRemainingTimeoutWhenTimeoutIsInfinite() { + //given + MongoCollection collection = mock(MongoCollection.class); + + //when + MongoCollection result = collectionWithTimeout(collection, TIMEOUT_ERROR_MESSAGE, Timeout.infinite()); + + //then + assertEquals(collection, result); + } + + @Test + void shouldSetRemainingTimeoutWhenTimeout() { + //given + MongoCollection collection = mock(MongoCollection.class); + MongoCollection collectionWithTimeout = mock(MongoCollection.class); + when(collection.withTimeout(anyLong(), eq(TimeUnit.MILLISECONDS))).thenReturn(collectionWithTimeout); + Timeout timeout = Timeout.expiresIn(1, TimeUnit.DAYS); + + //when + MongoCollection result = collectionWithTimeout(collection, TIMEOUT_ERROR_MESSAGE, timeout); + + //then + verify(collection).withTimeout(longThat(remaining -> remaining > 0), eq(TimeUnit.MILLISECONDS)); + assertNotEquals(collectionWithTimeout, result); + } + + @Test + void shouldThrowErrorWhenTimeoutHasExpired() { + //given + MongoCollection collection = mock(MongoCollection.class); + Timeout timeout = Timeout.expiresIn(1, TimeUnit.MICROSECONDS); + + //when + assertThrows(MongoExecutionTimeoutException.class, () -> collectionWithTimeout(collection, TIMEOUT_ERROR_MESSAGE, timeout)); + + //then + verifyNoInteractions(collection); + } + + @Test + void shouldThrowErrorWhenTimeoutHasExpiredWithZeroRemaining() { + //given + MongoCollection collection = mock(MongoCollection.class); + Timeout timeout = mock(Timeout.class, timeout1 -> { + when(timeout1.hasExpired()).thenReturn(true); + when(timeout1.isInfinite()).thenReturn(false); + when(timeout1.remaining(TimeUnit.MILLISECONDS)).thenReturn(0L); + }); + + //when + assertThrows(MongoExecutionTimeoutException.class, () -> collectionWithTimeout(collection, TIMEOUT_ERROR_MESSAGE, timeout)); + + //then + verifyNoInteractions(collection); + } +} \ No newline at end of file From f351d04bb0bb5c19394709193cf304355a8d53b6 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 8 Jan 2024 14:49:03 -0800 Subject: [PATCH 14/19] Add TODO comments for dependencies. JAVA-5277 --- .../com/mongodb/client/ClientSideOperationTimeoutTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutTest.java b/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutTest.java index 2719b85e522..22812b30d18 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/ClientSideOperationTimeoutTest.java @@ -92,7 +92,8 @@ public static void checkSkipCSOTTest(final String fileDescription, final String assumeFalse("TODO (CSOT) - JAVA-4062", testDescription.contains("wTimeoutMS is ignored")); - if (fileDescription.contains("GridFS")) { + + if (fileDescription.contains("GridFS")) { //TODO (CSOT) - JAVA-4057 assumeFalse("TODO (CSOT) - JAVA-4057", testDescription.contains("chunk insertion")); assumeFalse("TODO (CSOT) - JAVA-4057", testDescription.contains("creation of files document")); assumeFalse("TODO (CSOT) - JAVA-4057", testDescription.contains("delete against the files collection")); From a5061d581821676750213ad17c7ed0b789da0b07 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 8 Jan 2024 16:10:49 -0800 Subject: [PATCH 15/19] Add copyrights. JAVA-5277 --- ....java => CollectionTimeoutHelperTest.java} | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) rename driver-sync/src/test/unit/com/mongodb/client/gridfs/{TimeoutUtilsTest.java => CollectionTimeoutHelperTest.java} (74%) diff --git a/driver-sync/src/test/unit/com/mongodb/client/gridfs/TimeoutUtilsTest.java b/driver-sync/src/test/unit/com/mongodb/client/gridfs/CollectionTimeoutHelperTest.java similarity index 74% rename from driver-sync/src/test/unit/com/mongodb/client/gridfs/TimeoutUtilsTest.java rename to driver-sync/src/test/unit/com/mongodb/client/gridfs/CollectionTimeoutHelperTest.java index 62fe496234b..a4dd876693c 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/gridfs/TimeoutUtilsTest.java +++ b/driver-sync/src/test/unit/com/mongodb/client/gridfs/CollectionTimeoutHelperTest.java @@ -1,3 +1,19 @@ +/* + * 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.client.gridfs; import com.mongodb.MongoExecutionTimeoutException; @@ -20,7 +36,7 @@ import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; -class TimeoutUtilsTest { +class CollectionTimeoutHelperTest { private static final String TIMEOUT_ERROR_MESSAGE = "message"; @@ -51,9 +67,10 @@ void shouldNotSetRemainingTimeoutWhenTimeoutIsInfinite() { @Test void shouldSetRemainingTimeoutWhenTimeout() { //given - MongoCollection collection = mock(MongoCollection.class); MongoCollection collectionWithTimeout = mock(MongoCollection.class); - when(collection.withTimeout(anyLong(), eq(TimeUnit.MILLISECONDS))).thenReturn(collectionWithTimeout); + MongoCollection collection = mock(MongoCollection.class, mongoCollection -> { + when(mongoCollection.withTimeout(anyLong(), eq(TimeUnit.MILLISECONDS))).thenReturn(mongoCollection); + }); Timeout timeout = Timeout.expiresIn(1, TimeUnit.DAYS); //when @@ -71,9 +88,11 @@ void shouldThrowErrorWhenTimeoutHasExpired() { Timeout timeout = Timeout.expiresIn(1, TimeUnit.MICROSECONDS); //when - assertThrows(MongoExecutionTimeoutException.class, () -> collectionWithTimeout(collection, TIMEOUT_ERROR_MESSAGE, timeout)); + MongoExecutionTimeoutException mongoExecutionTimeoutException = + assertThrows(MongoExecutionTimeoutException.class, () -> collectionWithTimeout(collection, TIMEOUT_ERROR_MESSAGE, timeout)); //then + assertEquals(TIMEOUT_ERROR_MESSAGE, mongoExecutionTimeoutException.getMessage()); verifyNoInteractions(collection); } @@ -93,4 +112,4 @@ void shouldThrowErrorWhenTimeoutHasExpiredWithZeroRemaining() { //then verifyNoInteractions(collection); } -} \ No newline at end of file +} From a05cf56fa81e58eed4e1c56c51400bc3c4e749c5 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Tue, 9 Jan 2024 23:31:50 -0800 Subject: [PATCH 16/19] Reduce bock time for download test. JAVA-5277 --- .../client-side-operation-timeout/gridfs-download.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-core/src/test/resources/unified-test-format/client-side-operation-timeout/gridfs-download.json b/driver-core/src/test/resources/unified-test-format/client-side-operation-timeout/gridfs-download.json index 8542f69e898..3cf4a285fa6 100644 --- a/driver-core/src/test/resources/unified-test-format/client-side-operation-timeout/gridfs-download.json +++ b/driver-core/src/test/resources/unified-test-format/client-side-operation-timeout/gridfs-download.json @@ -299,7 +299,7 @@ "find" ], "blockConnection": true, - "blockTimeMS": 50 + "blockTimeMS": 40 } } } From 3025b3c2083810fcc2de40b2442cdc4b7985c77b Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Fri, 12 Jan 2024 17:34:39 -0800 Subject: [PATCH 17/19] Fix merge conflicts with CSOT branch. JAVA-5277 --- .../gridfs/CollectionTimeoutHelper.java | 4 +-- .../mongodb/client/gridfs/GridFSBucket.java | 25 +++++++++++++++++++ .../client/gridfs/GridFSBucketImpl.java | 4 +-- .../gridfs/GridFSDownloadStreamImpl.java | 4 +-- .../client/gridfs/GridFSUploadStreamImpl.java | 5 ++-- ...tClientSideOperationsTimeoutProseTest.java | 6 ++--- .../client/unified/UnifiedGridFSHelper.java | 25 ++++++++++++------- .../gridfs/CollectionTimeoutHelperTest.java | 1 + 8 files changed, 53 insertions(+), 21 deletions(-) diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/CollectionTimeoutHelper.java b/driver-sync/src/main/com/mongodb/client/gridfs/CollectionTimeoutHelper.java index 696c8d75df7..9df56290b11 100644 --- a/driver-sync/src/main/com/mongodb/client/gridfs/CollectionTimeoutHelper.java +++ b/driver-sync/src/main/com/mongodb/client/gridfs/CollectionTimeoutHelper.java @@ -16,7 +16,7 @@ package com.mongodb.client.gridfs; -import com.mongodb.MongoExecutionTimeoutException; +import com.mongodb.MongoOperationTimeoutException; import com.mongodb.client.MongoCollection; import com.mongodb.internal.time.Timeout; import com.mongodb.lang.Nullable; @@ -35,7 +35,7 @@ public static MongoCollection collectionWithTimeout(final MongoCollection long remainingMs = timeout.remaining(MILLISECONDS); if (timeout.hasExpired()) { // TODO (CSOT) - JAVA-5248 Update to MongoOperationTimeoutException - throw new MongoExecutionTimeoutException(message); + throw new MongoOperationTimeoutException(message); } return collection.withTimeout(remainingMs, MILLISECONDS); } diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucket.java b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucket.java index 8b251199d67..d79ae468db7 100644 --- a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucket.java +++ b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucket.java @@ -21,6 +21,7 @@ import com.mongodb.WriteConcern; import com.mongodb.annotations.ThreadSafe; import com.mongodb.client.ClientSession; +import com.mongodb.client.MongoDatabase; import com.mongodb.client.gridfs.model.GridFSDownloadOptions; import com.mongodb.client.gridfs.model.GridFSUploadOptions; import com.mongodb.lang.Nullable; @@ -344,6 +345,10 @@ public interface GridFSBucket { * chunks have been uploaded, it creates a files collection document for {@code filename} in the files collection. *

* +

Note: When this {@link GridFSBucket} is set with a operation timeout (via timeout inherited from {@link MongoDatabase} + * settings or {@link #withTimeout(long, TimeUnit)}), timeout breaches may occur due to the {@link InputStream} + * lacking inherent read timeout support, which might extend the operation beyond the specified timeout limit.

+ * * @param id the custom id value of the file * @param filename the filename for the stream * @param source the Stream providing the file data @@ -358,6 +363,10 @@ public interface GridFSBucket { * chunks have been uploaded, it creates a files collection document for {@code filename} in the files collection. *

* +

Note: When this {@link GridFSBucket} is set with a operation timeout (via timeout inherited from {@link MongoDatabase} + * settings or {@link #withTimeout(long, TimeUnit)}), timeout breaches may occur due to the {@link InputStream} + * lacking inherent read timeout support, which might extend the operation beyond the specified timeout limit.

+ * * @param id the custom id value of the file * @param filename the filename for the stream * @param source the Stream providing the file data @@ -373,6 +382,10 @@ public interface GridFSBucket { * chunks have been uploaded, it creates a files collection document for {@code filename} in the files collection. *

* +

Note: When this {@link GridFSBucket} is set with a operation timeout (via timeout inherited from {@link MongoDatabase} + * settings or {@link #withTimeout(long, TimeUnit)}), timeout breaches may occur due to the {@link InputStream} + * lacking inherent read timeout support, which might extend the operation beyond the specified timeout limit.

+ * * @param clientSession the client session with which to associate this operation * @param filename the filename for the stream * @param source the Stream providing the file data @@ -389,6 +402,10 @@ public interface GridFSBucket { * chunks have been uploaded, it creates a files collection document for {@code filename} in the files collection. *

* +

Note: When this {@link GridFSBucket} is set with a operation timeout (via timeout inherited from {@link MongoDatabase} + * settings or {@link #withTimeout(long, TimeUnit)}), timeout breaches may occur due to the {@link InputStream} + * lacking inherent read timeout support, which might extend the operation beyond the specified timeout limit.

+ * * @param clientSession the client session with which to associate this operation * @param filename the filename for the stream * @param source the Stream providing the file data @@ -406,6 +423,10 @@ public interface GridFSBucket { * chunks have been uploaded, it creates a files collection document for {@code filename} in the files collection. *

* +

Note: When this {@link GridFSBucket} is set with a operation timeout (via timeout inherited from {@link MongoDatabase} + * settings or {@link #withTimeout(long, TimeUnit)}), timeout breaches may occur due to the {@link InputStream} + * lacking inherent read timeout support, which might extend the operation beyond the specified timeout limit.

+ * * @param clientSession the client session with which to associate this operation * @param id the custom id value of the file * @param filename the filename for the stream @@ -422,6 +443,10 @@ public interface GridFSBucket { * chunks have been uploaded, it creates a files collection document for {@code filename} in the files collection. *

* +

Note: When this {@link GridFSBucket} is set with a operation timeout (via timeout inherited from {@link MongoDatabase} + * settings or {@link #withTimeout(long, TimeUnit)}), timeout breaches may occur due to the {@link InputStream} + * lacking inherent read timeout support, which might extend the operation beyond the specified timeout limit.

+ * * @param clientSession the client session with which to associate this operation * @param id the custom id value of the file * @param filename the filename for the stream diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucketImpl.java b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucketImpl.java index 326cf6ae2a9..ca6ca3d30c5 100644 --- a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucketImpl.java +++ b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSBucketImpl.java @@ -17,7 +17,7 @@ package com.mongodb.client.gridfs; import com.mongodb.MongoClientSettings; -import com.mongodb.MongoExecutionTimeoutException; +import com.mongodb.MongoOperationTimeoutException; import com.mongodb.MongoGridFSException; import com.mongodb.ReadConcern; import com.mongodb.ReadPreference; @@ -634,7 +634,7 @@ private void downloadToStream(final GridFSDownloadStream downloadStream, final O while ((len = downloadStream.read(buffer)) != -1) { destination.write(buffer, 0, len); } - } catch (MongoExecutionTimeoutException e){ // TODO (CSOT) - JAVA-5248 Update to MongoOperationTimeoutException + } catch (MongoOperationTimeoutException e){ // TODO (CSOT) - JAVA-5248 Update to MongoOperationTimeoutException throw e; } catch (IOException e) { savedThrowable = new MongoGridFSException("IOException when reading from the OutputStream", e); diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSDownloadStreamImpl.java b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSDownloadStreamImpl.java index d3f36643cb5..e744607a0ad 100644 --- a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSDownloadStreamImpl.java +++ b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSDownloadStreamImpl.java @@ -16,7 +16,7 @@ package com.mongodb.client.gridfs; -import com.mongodb.MongoExecutionTimeoutException; +import com.mongodb.MongoOperationTimeoutException; import com.mongodb.MongoGridFSException; import com.mongodb.client.ClientSession; import com.mongodb.client.FindIterable; @@ -214,7 +214,7 @@ public void close() { private void checkTimeout() { if (timeout != null && timeout.hasExpired()) { // TODO (CSOT) - JAVA-5248 Update to MongoOperationTimeoutException - throw new MongoExecutionTimeoutException("The GridFS download stream has timed out"); + throw new MongoOperationTimeoutException("The GridFS download stream has timed out"); } } private void checkClosed() { diff --git a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSUploadStreamImpl.java b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSUploadStreamImpl.java index 3355459e5b0..e722f3a4c0a 100644 --- a/driver-sync/src/main/com/mongodb/client/gridfs/GridFSUploadStreamImpl.java +++ b/driver-sync/src/main/com/mongodb/client/gridfs/GridFSUploadStreamImpl.java @@ -16,11 +16,11 @@ package com.mongodb.client.gridfs; -import com.mongodb.MongoExecutionTimeoutException; import com.mongodb.MongoGridFSException; import com.mongodb.client.ClientSession; import com.mongodb.client.MongoCollection; import com.mongodb.client.gridfs.model.GridFSFile; +import com.mongodb.internal.TimeoutContext; import com.mongodb.internal.time.Timeout; import com.mongodb.lang.Nullable; import org.bson.BsonValue; @@ -146,8 +146,7 @@ public void write(final byte[] b, final int off, final int len) { private void checkTimeout() { if (timeout != null && timeout.hasExpired()) { - // TODO (CSOT) - JAVA-5248 Update to MongoOperationTimeoutException - throw new MongoExecutionTimeoutException(TIMEOUT_MESSAGE); + throw TimeoutContext.createMongoTimeoutException(TIMEOUT_MESSAGE); } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java index 5a934d41aca..392cd1e81bf 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientSideOperationsTimeoutProseTest.java @@ -123,7 +123,7 @@ public void testGridFSUploadViaOpenUploadStreamTimeout() { try (GridFSUploadStream uploadStream = gridFsBucket.openUploadStream("filename")){ uploadStream.write(0x12); - assertThrows(MongoExecutionTimeoutException.class, uploadStream::close); + assertThrows(MongoOperationTimeoutException.class, uploadStream::close); } } } @@ -152,7 +152,7 @@ public void testAbortingGridFsUploadStreamTimeout() { try (GridFSUploadStream uploadStream = gridFsBucket.openUploadStream("filename")){ uploadStream.write(new byte[]{0x01, 0x02, 0x03, 0x04}); - assertThrows(MongoExecutionTimeoutException.class, uploadStream::abort); + assertThrows(MongoOperationTimeoutException.class, uploadStream::abort); } } } @@ -196,7 +196,7 @@ public void testGridFsDownloadStreamTimeout() { + " blockTimeMS: " + (rtt + 95) + " }" + "}"); - assertThrows(MongoExecutionTimeoutException.class, downloadStream::read); + assertThrows(MongoOperationTimeoutException.class, downloadStream::read); List events = commandListener.getCommandStartedEvents(); List findCommands = events.stream().filter(e -> e.getCommandName().equals("find")).collect(Collectors.toList()); diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedGridFSHelper.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedGridFSHelper.java index 31f61f50d13..915465836d4 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedGridFSHelper.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedGridFSHelper.java @@ -19,6 +19,7 @@ import com.mongodb.client.gridfs.GridFSBucket; import com.mongodb.client.gridfs.GridFSFindIterable; import com.mongodb.client.gridfs.model.GridFSDownloadOptions; +import com.mongodb.client.gridfs.model.GridFSFile; import com.mongodb.client.gridfs.model.GridFSUploadOptions; import com.mongodb.internal.HexUtils; import org.bson.BsonDocument; @@ -49,15 +50,21 @@ final class UnifiedGridFSHelper extends UnifiedHelper{ public OperationResult executeFind(final BsonDocument operation) { GridFSFindIterable iterable = createGridFSFindIterable(operation); try { - iterable.into(new ArrayList<>()); - return OperationResult.NONE; // we don't expect results in the tests. + ArrayList target = new ArrayList<>(); + iterable.into(target); + + if (target.isEmpty()) { + return OperationResult.NONE; + } + + throw new UnsupportedOperationException("expectResult is not implemented for Unified GridFS tests."); } catch (Exception e) { return OperationResult.of(e); } } public OperationResult executeRename(final BsonDocument operation) { - GridFSBucket bucket = getGirdFsBucket(operation); + GridFSBucket bucket = getGridFsBucket(operation); BsonDocument arguments = operation.getDocument("arguments"); BsonValue id = arguments.get("id"); String fileName = arguments.get("newFilename").asString().getValue(); @@ -74,7 +81,7 @@ public OperationResult executeRename(final BsonDocument operation) { } OperationResult executeDelete(final BsonDocument operation) { - GridFSBucket bucket = getGirdFsBucket(operation); + GridFSBucket bucket = getGridFsBucket(operation); BsonDocument arguments = operation.getDocument("arguments"); BsonValue id = arguments.get("id"); @@ -94,7 +101,7 @@ OperationResult executeDelete(final BsonDocument operation) { } public OperationResult executeDrop(final BsonDocument operation) { - GridFSBucket bucket = getGirdFsBucket(operation); + GridFSBucket bucket = getGridFsBucket(operation); BsonDocument arguments = operation.getDocument("arguments", new BsonDocument()); if (arguments.size() > 0) { throw new UnsupportedOperationException("Unexpected arguments " + operation.get("arguments")); @@ -109,7 +116,7 @@ public OperationResult executeDrop(final BsonDocument operation) { } public OperationResult executeDownload(final BsonDocument operation) { - GridFSBucket bucket = getGirdFsBucket(operation); + GridFSBucket bucket = getGridFsBucket(operation); BsonDocument arguments = operation.getDocument("arguments"); BsonValue id = arguments.get("id"); @@ -164,7 +171,7 @@ private GridFSDownloadOptions getDownloadOptions(final BsonDocument arguments) { } public OperationResult executeUpload(final BsonDocument operation) { - GridFSBucket bucket = getGirdFsBucket(operation); + GridFSBucket bucket = getGridFsBucket(operation); BsonDocument arguments = operation.getDocument("arguments"); String filename = null; @@ -211,7 +218,7 @@ Document asDocument(final BsonDocument bsonDocument) { return new DocumentCodec().decode(new BsonDocumentReader(bsonDocument), DecoderContext.builder().build()); } - private GridFSBucket getGirdFsBucket(final BsonDocument operation) { + private GridFSBucket getGridFsBucket(final BsonDocument operation) { GridFSBucket bucket = entities.getBucket(operation.getString("object").getValue()); Long timeoutMS = getAndRemoveTimeoutMS(operation.getDocument("arguments", new BsonDocument())); if (timeoutMS != null) { @@ -221,7 +228,7 @@ private GridFSBucket getGirdFsBucket(final BsonDocument operation) { } private GridFSFindIterable createGridFSFindIterable(final BsonDocument operation) { - GridFSBucket bucket = getGirdFsBucket(operation); + GridFSBucket bucket = getGridFsBucket(operation); BsonDocument arguments = operation.getDocument("arguments"); BsonDocument filter = arguments.getDocument("filter"); diff --git a/driver-sync/src/test/unit/com/mongodb/client/gridfs/CollectionTimeoutHelperTest.java b/driver-sync/src/test/unit/com/mongodb/client/gridfs/CollectionTimeoutHelperTest.java index a4dd876693c..25402c92c4f 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/gridfs/CollectionTimeoutHelperTest.java +++ b/driver-sync/src/test/unit/com/mongodb/client/gridfs/CollectionTimeoutHelperTest.java @@ -36,6 +36,7 @@ import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; +@SuppressWarnings("unchecked") class CollectionTimeoutHelperTest { private static final String TIMEOUT_ERROR_MESSAGE = "message"; From 0d15fffb686e21b2d7e7b545149adec61c2d7c36 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Fri, 12 Jan 2024 18:04:31 -0800 Subject: [PATCH 18/19] Add detailed message for unsupported GridFS test execution. JAVA-5277 --- .../com/mongodb/client/unified/UnifiedGridFSHelper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedGridFSHelper.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedGridFSHelper.java index 915465836d4..13e95a58463 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedGridFSHelper.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedGridFSHelper.java @@ -57,7 +57,8 @@ public OperationResult executeFind(final BsonDocument operation) { return OperationResult.NONE; } - throw new UnsupportedOperationException("expectResult is not implemented for Unified GridFS tests."); + throw new UnsupportedOperationException("expectResult is not implemented for Unified GridFS tests. " + + "Unexpected result: " + target); } catch (Exception e) { return OperationResult.of(e); } From 66bee9bd9c509e5013de1c9723f2dff3f2c14857 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Fri, 12 Jan 2024 18:18:55 -0800 Subject: [PATCH 19/19] Fix tests. JAVA-5277 --- .../client/gridfs/CollectionTimeoutHelperTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/driver-sync/src/test/unit/com/mongodb/client/gridfs/CollectionTimeoutHelperTest.java b/driver-sync/src/test/unit/com/mongodb/client/gridfs/CollectionTimeoutHelperTest.java index 25402c92c4f..e7e851b270b 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/gridfs/CollectionTimeoutHelperTest.java +++ b/driver-sync/src/test/unit/com/mongodb/client/gridfs/CollectionTimeoutHelperTest.java @@ -16,7 +16,7 @@ package com.mongodb.client.gridfs; -import com.mongodb.MongoExecutionTimeoutException; +import com.mongodb.MongoOperationTimeoutException; import com.mongodb.client.MongoCollection; import com.mongodb.internal.time.Timeout; import org.bson.Document; @@ -89,8 +89,8 @@ void shouldThrowErrorWhenTimeoutHasExpired() { Timeout timeout = Timeout.expiresIn(1, TimeUnit.MICROSECONDS); //when - MongoExecutionTimeoutException mongoExecutionTimeoutException = - assertThrows(MongoExecutionTimeoutException.class, () -> collectionWithTimeout(collection, TIMEOUT_ERROR_MESSAGE, timeout)); + MongoOperationTimeoutException mongoExecutionTimeoutException = + assertThrows(MongoOperationTimeoutException.class, () -> collectionWithTimeout(collection, TIMEOUT_ERROR_MESSAGE, timeout)); //then assertEquals(TIMEOUT_ERROR_MESSAGE, mongoExecutionTimeoutException.getMessage()); @@ -108,7 +108,7 @@ void shouldThrowErrorWhenTimeoutHasExpiredWithZeroRemaining() { }); //when - assertThrows(MongoExecutionTimeoutException.class, () -> collectionWithTimeout(collection, TIMEOUT_ERROR_MESSAGE, timeout)); + assertThrows(MongoOperationTimeoutException.class, () -> collectionWithTimeout(collection, TIMEOUT_ERROR_MESSAGE, timeout)); //then verifyNoInteractions(collection);