From b867bb7f1556c3ac18944f05a41a017957804d17 Mon Sep 17 00:00:00 2001 From: Gregory Noma Date: Fri, 10 Sep 2021 19:08:51 -0400 Subject: [PATCH] SERVER-59888 --- jstests/noPassthrough/SERVER-59888.js | 52 ++++++++++++++++++++++ src/mongo/db/timeseries/bucket_catalog.cpp | 7 +++ 2 files changed, 59 insertions(+) create mode 100644 jstests/noPassthrough/SERVER-59888.js diff --git a/jstests/noPassthrough/SERVER-59888.js b/jstests/noPassthrough/SERVER-59888.js new file mode 100644 index 0000000000..96bd30ce67 --- /dev/null +++ b/jstests/noPassthrough/SERVER-59888.js @@ -0,0 +1,52 @@ +/** + * Reprodues the deadlock described in SERVER-59888. + */ +(function() { +'use strict'; + +load('jstests/libs/fail_point_util.js'); +load('jstests/libs/parallel_shell_helpers.js'); + +const conn = + MongoRunner.runMongod({setParameter: {timeseriesIdleBucketExpiryMemoryUsageThreshold: 1000}}); + +const testDB = conn.getDB('test'); +const coll = testDB[jsTestName()]; + +const timeFieldName = 'time'; +const metaFieldName = 'meta'; + +assert.commandWorked(testDB.createCollection( + coll.getName(), {timeseries: {timeField: timeFieldName, metaField: metaFieldName}})); + +assert.commandWorked(coll.insert({_id: 0, [metaFieldName]: 0, [timeFieldName]: ISODate()})); +assert.commandWorked(coll.insert({_id: 1, [metaFieldName]: 1, [timeFieldName]: ISODate()})); + +// Perform an insert which will use an existing bucket. +const fp1 = + configureFailPoint(conn, 'hangInConfirmStateForAcquiredBucketAfterAcquiringStatesMutex'); +const awaitInsert2 = startParallelShell( + funWithArgs(function(collName, timeFieldName, metaFieldName) { + assert.commandWorked( + db[collName].insert({_id: 3, [metaFieldName]: 1, [timeFieldName]: ISODate()})); + }, coll.getName(), timeFieldName, metaFieldName), conn.port); +fp1.wait(); + +// Perform an insert which will expire an idle bucket. +const fp2 = configureFailPoint(conn, 'hangInExpireIdleBucketsAfterAcquiringIdleMutex'); +const awaitInsert1 = startParallelShell( + funWithArgs(function(collName, timeFieldName, metaFieldName) { + assert.commandWorked( + db[collName].insert({_id: 2, [metaFieldName]: 2, [timeFieldName]: ISODate()})); + }, coll.getName(), timeFieldName, metaFieldName), conn.port); +fp2.wait(); + +jsTestLog('Disabling fail points -- inserts should be able to proceed') +fp1.off(); +fp2.off(); + +awaitInsert1(); +awaitInsert2(); + +MongoRunner.stopMongod(conn); +})(); diff --git a/src/mongo/db/timeseries/bucket_catalog.cpp b/src/mongo/db/timeseries/bucket_catalog.cpp index 13e90a428f..51cead5526 100644 --- a/src/mongo/db/timeseries/bucket_catalog.cpp +++ b/src/mongo/db/timeseries/bucket_catalog.cpp @@ -52,6 +52,8 @@ void normalizeObject(BSONObjBuilder* builder, const BSONObj& obj); const auto getBucketCatalog = ServiceContext::declareDecoration(); MONGO_FAIL_POINT_DEFINE(hangTimeseriesDirectModificationBeforeWriteConflict); +MONGO_FAIL_POINT_DEFINE(hangInExpireIdleBucketsAfterAcquiringIdleMutex); +MONGO_FAIL_POINT_DEFINE(hangInConfirmStateForAcquiredBucketAfterAcquiringStatesMutex); uint8_t numDigits(uint32_t num) { uint8_t numDigits = 0; @@ -584,6 +586,8 @@ void BucketCatalog::_expireIdleBuckets(ExecutionStats* stats) { // Must hold an exclusive lock on _bucketMutex from outside. stdx::lock_guard lk{_idleMutex}; + hangInExpireIdleBucketsAfterAcquiringIdleMutex.pauseWhileSet(); + // As long as we still need space and have entries, close idle buckets. while (!_idleBuckets.empty() && _memoryUsage.load() > @@ -967,6 +971,9 @@ BucketCatalog::BucketState BucketCatalog::BucketAccess::_findOpenBucketThenLockA BucketCatalog::BucketState BucketCatalog::BucketAccess::_confirmStateForAcquiredBucket() { stdx::lock_guard statesLk{_catalog->_statesMutex}; + + hangInConfirmStateForAcquiredBucketAfterAcquiringStatesMutex.pauseWhileSet(); + auto statesIt = _catalog->_bucketStates.find(_bucket->_id); invariant(statesIt != _catalog->_bucketStates.end()); auto& [_, state] = *statesIt; -- 2.17.1