Skip to content
This repository was archived by the owner on Oct 31, 2023. It is now read-only.

Commit dbb6b0d

Browse files
tseip-mongoEvergreen Agent
authored andcommitted
SERVER-53566: Protect ServiceContext from issuing duplicate operation IDs
1 parent 52c6504 commit dbb6b0d

File tree

8 files changed

+358
-22
lines changed

8 files changed

+358
-22
lines changed

src/mongo/db/SConscript

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ env.Library(
459459
'operation_context.cpp',
460460
'operation_context_group.cpp',
461461
'operation_cpu_timer.cpp',
462+
'operation_id.cpp',
462463
'operation_key_manager.cpp',
463464
'service_context.cpp',
464465
'server_recovery.cpp',
@@ -2338,6 +2339,7 @@ if wiredtiger:
23382339
'op_observer_registry_test.cpp',
23392340
'operation_context_test.cpp',
23402341
'operation_cpu_timer_test.cpp',
2342+
'operation_id_test.cpp',
23412343
'operation_time_tracker_test.cpp',
23422344
'persistent_task_store_test.cpp',
23432345
'range_arithmetic_test.cpp',

src/mongo/db/operation_context.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,11 @@ const auto kNoWaiterThread = stdx::thread::id();
7272
} // namespace
7373

7474
OperationContext::OperationContext(Client* client, OperationId opId)
75+
: OperationContext(client, OperationIdSlot(opId)) {}
76+
77+
OperationContext::OperationContext(Client* client, OperationIdSlot&& opIdSlot)
7578
: _client(client),
76-
_opId(opId),
79+
_opId(std::move(opIdSlot)),
7780
_elapsedTime(client ? client->getServiceContext()->getTickSource()
7881
: SystemTickSource::get()) {}
7982

@@ -387,7 +390,7 @@ void OperationContext::setOperationKey(OperationKey opKey) {
387390
invariant(!_opKey);
388391

389392
_opKey.emplace(std::move(opKey));
390-
OperationKeyManager::get(_client).add(*_opKey, _opId);
393+
OperationKeyManager::get(_client).add(*_opKey, _opId.getId());
391394
}
392395

393396
void OperationContext::releaseOperationKey() {

src/mongo/db/operation_context.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "mongo/db/client.h"
3737
#include "mongo/db/concurrency/locker.h"
3838
#include "mongo/db/logical_session_id.h"
39+
#include "mongo/db/operation_id.h"
3940
#include "mongo/db/query/datetime/date_time_support.h"
4041
#include "mongo/db/storage/recovery_unit.h"
4142
#include "mongo/db/storage/storage_options.h"
@@ -56,7 +57,6 @@
5657

5758
namespace mongo {
5859

59-
class Client;
6060
class CurOp;
6161
class ProgressMeter;
6262
class ServiceContext;
@@ -97,7 +97,12 @@ class OperationContext : public Interruptible, public Decorable<OperationContext
9797
public:
9898
static constexpr auto kDefaultOperationContextTimeoutError = ErrorCodes::ExceededTimeLimit;
9999

100+
/**
101+
* Creates an op context with no unique operation ID tracking - prefer using the OperationIdSlot
102+
* CTOR if possible to avoid OperationId collisions.
103+
*/
100104
OperationContext(Client* client, OperationId opId);
105+
OperationContext(Client* client, OperationIdSlot&& opIdSlot);
101106
virtual ~OperationContext();
102107

103108
bool shouldParticipateInFlowControl() const {
@@ -185,7 +190,7 @@ class OperationContext : public Interruptible, public Decorable<OperationContext
185190
* Returns the operation ID associated with this operation.
186191
*/
187192
OperationId getOpID() const {
188-
return _opId;
193+
return _opId.getId();
189194
}
190195

191196
/**
@@ -614,7 +619,7 @@ class OperationContext : public Interruptible, public Decorable<OperationContext
614619

615620
Client* const _client;
616621

617-
const OperationId _opId;
622+
const OperationIdSlot _opId;
618623
boost::optional<OperationKey> _opKey;
619624

620625
boost::optional<LogicalSessionId> _lsid;

src/mongo/db/operation_id.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* Copyright (C) 2018-present MongoDB, Inc.
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the Server Side Public License, version 1,
6+
* as published by MongoDB, Inc.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* Server Side Public License for more details.
12+
*
13+
* You should have received a copy of the Server Side Public License
14+
* along with this program. If not, see
15+
* <http://www.mongodb.com/licensing/server-side-public-license>.
16+
*
17+
* As a special exception, the copyright holders give permission to link the
18+
* code of portions of this program with the OpenSSL library under certain
19+
* conditions as described in each individual source file and distribute
20+
* linked combinations including the program with the OpenSSL library. You
21+
* must comply with the Server Side Public License in all respects for
22+
* all of the code used other than as permitted herein. If you modify file(s)
23+
* with this exception, you may extend this exception to your version of the
24+
* file(s), but you are not obligated to do so. If you do not wish to do so,
25+
* delete this exception statement from your version. If you delete this
26+
* exception statement from all source files in the program, then also delete
27+
* it in the license file.
28+
*/
29+
30+
#include "mongo/db/operation_id.h"
31+
32+
#include "mongo/util/assert_util.h"
33+
34+
namespace mongo {
35+
36+
OperationIdSlot UniqueOperationIdRegistry::acquireSlot() {
37+
stdx::lock_guard lk(_mutex);
38+
39+
// Make sure the set isn't absolutely enormous. If it is, something else is wrong,
40+
// and the loop below could fail.
41+
invariant(_activeIds.size() < (1 << 20));
42+
43+
while (true) {
44+
const auto&& [it, ok] = _activeIds.insert(_nextOpId++);
45+
if (ok) {
46+
return OperationIdSlot(*it, shared_from_this());
47+
}
48+
}
49+
}
50+
51+
void UniqueOperationIdRegistry::_releaseSlot(OperationId id) {
52+
stdx::lock_guard lk(_mutex);
53+
invariant(_activeIds.erase(id));
54+
}
55+
56+
} // namespace mongo

src/mongo/db/operation_id.h

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/**
2+
* Copyright (C) 2021-present MongoDB, Inc.
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the Server Side Public License, version 1,
6+
* as published by MongoDB, Inc.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* Server Side Public License for more details.
12+
*
13+
* You should have received a copy of the Server Side Public License
14+
* along with this program. If not, see
15+
* <http://www.mongodb.com/licensing/server-side-public-license>.
16+
*
17+
* As a special exception, the copyright holders give permission to link the
18+
* code of portions of this program with the OpenSSL library under certain
19+
* conditions as described in each individual source file and distribute
20+
* linked combinations including the program with the OpenSSL library. You
21+
* must comply with the Server Side Public License in all respects for
22+
* all of the code used other than as permitted herein. If you modify file(s)
23+
* with this exception, you may extend this exception to your version of the
24+
* file(s), but you are not obligated to do so. If you do not wish to do so,
25+
* delete this exception statement from your version. If you delete this
26+
* exception statement from all source files in the program, then also delete
27+
* it in the license file.
28+
*/
29+
30+
#pragma once
31+
32+
#include "mongo/platform/mutex.h"
33+
#include "mongo/stdx/unordered_set.h"
34+
35+
namespace mongo {
36+
37+
/**
38+
* Every OperationContext is expected to have a unique OperationId within the domain of its
39+
* ServiceContext. Generally speaking, OperationId is used for forming maps of OperationContexts and
40+
* directing metaoperations like killop.
41+
*/
42+
using OperationId = uint32_t;
43+
44+
/**
45+
* This class issues guaranteed unique OperationIds for a given instance of this class.
46+
*/
47+
class UniqueOperationIdRegistry : public std::enable_shared_from_this<UniqueOperationIdRegistry> {
48+
public:
49+
/**
50+
* This class represents a slot issued by a UniqueOperationIdRegistry.
51+
* It functions as an RAII wrapper for a unique OperationId.
52+
*/
53+
class OperationIdSlot {
54+
public:
55+
explicit OperationIdSlot(OperationId id) : _id(id), _registry() {}
56+
57+
OperationIdSlot(OperationId id, std::weak_ptr<UniqueOperationIdRegistry> registry)
58+
: _id(id), _registry(std::move(registry)) {}
59+
60+
OperationIdSlot(OperationIdSlot&& other) = default;
61+
62+
OperationIdSlot& operator=(OperationIdSlot&& other) noexcept {
63+
if (&other == this) {
64+
return *this;
65+
}
66+
_releaseSlot();
67+
_id = std::exchange(other._id, {});
68+
_registry = std::exchange(other._registry, {});
69+
return *this;
70+
}
71+
72+
// Disable copies.
73+
OperationIdSlot(const OperationIdSlot&) = delete;
74+
OperationIdSlot& operator=(const OperationIdSlot&) = delete;
75+
76+
~OperationIdSlot() {
77+
_releaseSlot();
78+
}
79+
80+
/**
81+
* Get the contained ID.
82+
*/
83+
OperationId getId() const {
84+
return _id;
85+
}
86+
87+
private:
88+
void _releaseSlot() {
89+
if (auto registry = _registry.lock()) {
90+
registry->_releaseSlot(_id);
91+
}
92+
}
93+
94+
OperationId _id;
95+
std::weak_ptr<UniqueOperationIdRegistry> _registry;
96+
};
97+
98+
/**
99+
* Public factory function.
100+
*/
101+
static std::shared_ptr<UniqueOperationIdRegistry> create() {
102+
return std::shared_ptr<UniqueOperationIdRegistry>(new UniqueOperationIdRegistry());
103+
}
104+
105+
/**
106+
* Gets a unique OperationIdSlot which will clear itself from the map when destroyed.
107+
*/
108+
OperationIdSlot acquireSlot();
109+
110+
/**
111+
* A helper class for exposing test functions.
112+
*/
113+
class UniqueOperationIdRegistryTestHarness {
114+
public:
115+
/**
116+
* Returns true if the given operation ID exists.
117+
*/
118+
static bool isActive(UniqueOperationIdRegistry& registry, OperationId id) {
119+
stdx::lock_guard lk(registry._mutex);
120+
return registry._activeIds.find(id) != registry._activeIds.end();
121+
}
122+
123+
static void setNextOpId(UniqueOperationIdRegistry& registry, OperationId id) {
124+
stdx::lock_guard lk(registry._mutex);
125+
registry._nextOpId = id;
126+
}
127+
};
128+
129+
private:
130+
UniqueOperationIdRegistry() = default;
131+
132+
/**
133+
* Clears a unique ID from the set.
134+
*/
135+
void _releaseSlot(OperationId id);
136+
137+
Mutex _mutex = MONGO_MAKE_LATCH("UniqueOperationIdRegistry::_mutex");
138+
stdx::unordered_set<OperationId> _activeIds;
139+
140+
OperationId _nextOpId = 1U;
141+
};
142+
143+
using OperationIdSlot = UniqueOperationIdRegistry::OperationIdSlot;
144+
145+
} // namespace mongo

src/mongo/db/operation_id_test.cpp

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* Copyright (C) 2018-present MongoDB, Inc.
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the Server Side Public License, version 1,
6+
* as published by MongoDB, Inc.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* Server Side Public License for more details.
12+
*
13+
* You should have received a copy of the Server Side Public License
14+
* along with this program. If not, see
15+
* <http://www.mongodb.com/licensing/server-side-public-license>.
16+
*
17+
* As a special exception, the copyright holders give permission to link the
18+
* code of portions of this program with the OpenSSL library under certain
19+
* conditions as described in each individual source file and distribute
20+
* linked combinations including the program with the OpenSSL library. You
21+
* must comply with the Server Side Public License in all respects for
22+
* all of the code used other than as permitted herein. If you modify file(s)
23+
* with this exception, you may extend this exception to your version of the
24+
* file(s), but you are not obligated to do so. If you do not wish to do so,
25+
* delete this exception statement from your version. If you delete this
26+
* exception statement from all source files in the program, then also delete
27+
* it in the license file.
28+
*/
29+
30+
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest
31+
32+
#include "mongo/platform/basic.h"
33+
34+
#include "mongo/db/operation_id.h"
35+
#include "mongo/logv2/log.h"
36+
#include "mongo/platform/mutex.h"
37+
#include "mongo/unittest/death_test.h"
38+
#include "mongo/unittest/unittest.h"
39+
40+
namespace mongo {
41+
namespace {
42+
43+
using UniqueOperationIdRegistryTestHarness =
44+
UniqueOperationIdRegistry::UniqueOperationIdRegistryTestHarness;
45+
46+
TEST(OperationIdTest, OperationIdIncrementsProperly) {
47+
auto registry = UniqueOperationIdRegistry::create();
48+
auto slot = registry->acquireSlot();
49+
ASSERT_EQ(slot.getId(), 1);
50+
51+
{
52+
auto slot2 = registry->acquireSlot();
53+
ASSERT_EQ(slot2.getId(), 2);
54+
}
55+
56+
ASSERT(UniqueOperationIdRegistryTestHarness::isActive(*registry, 1));
57+
ASSERT(!UniqueOperationIdRegistryTestHarness::isActive(*registry, 2));
58+
59+
slot = registry->acquireSlot();
60+
ASSERT_EQ(slot.getId(), 3);
61+
62+
ASSERT(!UniqueOperationIdRegistryTestHarness::isActive(*registry, 1));
63+
ASSERT(UniqueOperationIdRegistryTestHarness::isActive(*registry, 3));
64+
}
65+
66+
TEST(OperationIdTest, OperationIdsAreUnique) {
67+
auto registry = UniqueOperationIdRegistry::create();
68+
auto slot = registry->acquireSlot();
69+
ASSERT_EQ(slot.getId(), 1);
70+
71+
auto slot2 = registry->acquireSlot();
72+
ASSERT_EQ(slot2.getId(), 2);
73+
74+
// If the registry's state points to a currently active operation ID, we will
75+
// find the next hole and issue that instead.
76+
UniqueOperationIdRegistryTestHarness::setNextOpId(*registry, 1);
77+
auto slot3 = registry->acquireSlot();
78+
ASSERT_EQ(slot3.getId(), 3);
79+
ASSERT(UniqueOperationIdRegistryTestHarness::isActive(*registry, 1));
80+
ASSERT(UniqueOperationIdRegistryTestHarness::isActive(*registry, 2));
81+
ASSERT(UniqueOperationIdRegistryTestHarness::isActive(*registry, 3));
82+
}
83+
84+
TEST(OperationIdTest, OperationIdsAreMovable) {
85+
auto registry = UniqueOperationIdRegistry::create();
86+
auto slot = registry->acquireSlot();
87+
ASSERT_EQ(slot.getId(), 1);
88+
ASSERT(UniqueOperationIdRegistryTestHarness::isActive(*registry, 1));
89+
90+
auto moveSlot(std::move(slot));
91+
ASSERT_EQ(moveSlot.getId(), 1);
92+
ASSERT(UniqueOperationIdRegistryTestHarness::isActive(*registry, 1));
93+
94+
auto assignSlot = std::move(moveSlot);
95+
ASSERT_EQ(assignSlot.getId(), 1);
96+
ASSERT(UniqueOperationIdRegistryTestHarness::isActive(*registry, 1));
97+
}
98+
99+
DEATH_TEST(OperationIdTest, TooManyTransactionsShouldCrash, "invariant") {
100+
auto registry = UniqueOperationIdRegistry::create();
101+
std::vector<OperationIdSlot> slots;
102+
103+
while (true) {
104+
slots.push_back(registry->acquireSlot());
105+
}
106+
}
107+
108+
} // namespace
109+
110+
} // namespace mongo

0 commit comments

Comments
 (0)