3434
3535#include < stdlib.h>
3636
37+ #include " mongo/bson/util/bson_extract.h"
38+ #include " mongo/bson/util/builder.h"
3739#include " mongo/db/concurrency/d_concurrency.h"
3840#include " mongo/db/namespace_string.h"
3941#include " mongo/db/operation_context.h"
42+ #include " mongo/db/storage/kv/kv_catalog_feature_tracker.h"
4043#include " mongo/db/storage/record_store.h"
4144#include " mongo/db/storage/recovery_unit.h"
45+ #include " mongo/platform/bits.h"
4246#include " mongo/platform/random.h"
4347#include " mongo/util/log.h"
4448#include " mongo/util/mongoutils/str.h"
@@ -51,6 +55,28 @@ namespace {
5155//
5256// NOTE: Must be locked *before* _identLock.
5357const ResourceId resourceIdCatalogMetadata (RESOURCE_METADATA, 1ULL );
58+
59+ const char kIsFeatureDocumentFieldName [] = " isFeatureDoc" ;
60+ const char kNamespaceFieldName [] = " ns" ;
61+ const char kNonRepairableFeaturesFieldName [] = " nonRepairable" ;
62+ const char kRepairableFeaturesFieldName [] = " repairable" ;
63+
64+ void appendPositionsOfBitsSet (uint64_t value, StringBuilder* sb) {
65+ invariant (sb);
66+
67+ *sb << " [ " ;
68+ bool firstIteration = true ;
69+ while (value) {
70+ const int lowestSetBitPosition = countTrailingZeros64 (value);
71+ if (!firstIteration) {
72+ *sb << " , " ;
73+ }
74+ *sb << lowestSetBitPosition;
75+ value ^= (1 << lowestSetBitPosition);
76+ firstIteration = false ;
77+ }
78+ *sb << " ]" ;
79+ }
5480}
5581
5682using std::unique_ptr;
@@ -87,6 +113,192 @@ class KVCatalog::RemoveIdentChange : public RecoveryUnit::Change {
87113 const Entry _entry;
88114};
89115
116+ bool KVCatalog::FeatureTracker::isFeatureDocument (BSONObj obj) {
117+ BSONElement firstElem = obj.firstElement ();
118+ if (firstElem.fieldNameStringData () == kIsFeatureDocumentFieldName ) {
119+ return firstElem.booleanSafe ();
120+ }
121+ return false ;
122+ }
123+
124+ Status KVCatalog::FeatureTracker::isCompatibleWithCurrentCode (OperationContext* opCtx) const {
125+ FeatureBits versionInfo = getInfo (opCtx);
126+
127+ uint64_t unrecognizedNonRepairableFeatures =
128+ versionInfo.nonRepairableFeatures & ~_usedNonRepairableFeaturesMask;
129+ if (unrecognizedNonRepairableFeatures) {
130+ StringBuilder sb;
131+ sb << " The data files use features not recognized by this version of mongod; the NR feature"
132+ " bits in positions " ;
133+ appendPositionsOfBitsSet (unrecognizedNonRepairableFeatures, &sb);
134+ sb << " aren't recognized by this version of mongod" ;
135+ return {ErrorCodes::MustUpgrade, sb.str ()};
136+ }
137+
138+ uint64_t unrecognizedRepairableFeatures =
139+ versionInfo.repairableFeatures & ~_usedRepairableFeaturesMask;
140+ if (unrecognizedRepairableFeatures) {
141+ StringBuilder sb;
142+ sb << " The data files use features not recognized by this version of mongod; the R feature"
143+ " bits in positions " ;
144+ appendPositionsOfBitsSet (unrecognizedRepairableFeatures, &sb);
145+ sb << " aren't recognized by this version of mongod" ;
146+ return {ErrorCodes::CanRepairToDowngrade, sb.str ()};
147+ }
148+
149+ return Status::OK ();
150+ }
151+
152+ std::unique_ptr<KVCatalog::FeatureTracker> KVCatalog::FeatureTracker::get (OperationContext* opCtx,
153+ KVCatalog* catalog,
154+ RecordId rid) {
155+ auto record = catalog->_rs ->dataFor (opCtx, rid);
156+ BSONObj obj = record.toBson ();
157+ invariant (isFeatureDocument (obj));
158+ return std::unique_ptr<KVCatalog::FeatureTracker>(new KVCatalog::FeatureTracker (catalog, rid));
159+ }
160+
161+ std::unique_ptr<KVCatalog::FeatureTracker> KVCatalog::FeatureTracker::create (
162+ OperationContext* opCtx, KVCatalog* catalog) {
163+ return std::unique_ptr<KVCatalog::FeatureTracker>(
164+ new KVCatalog::FeatureTracker (catalog, RecordId ()));
165+ }
166+
167+ bool KVCatalog::FeatureTracker::isNonRepairableFeatureInUse (OperationContext* opCtx,
168+ NonRepairableFeature feature) const {
169+ std::unique_ptr<Lock::ResourceLock> rLk;
170+ if (!_catalog->_isRsThreadSafe && opCtx->lockState ()) {
171+ rLk.reset (new Lock::ResourceLock (opCtx->lockState (), resourceIdCatalogMetadata, MODE_S));
172+ }
173+
174+ FeatureBits versionInfo = getInfo (opCtx);
175+ return versionInfo.nonRepairableFeatures & static_cast <NonRepairableFeatureMask>(feature);
176+ }
177+
178+ void KVCatalog::FeatureTracker::markNonRepairableFeatureAsInUse (OperationContext* opCtx,
179+ NonRepairableFeature feature) {
180+ std::unique_ptr<Lock::ResourceLock> rLk;
181+ if (!_catalog->_isRsThreadSafe && opCtx->lockState ()) {
182+ rLk.reset (new Lock::ResourceLock (opCtx->lockState (), resourceIdCatalogMetadata, MODE_X));
183+ }
184+
185+ FeatureBits versionInfo = getInfo (opCtx);
186+ versionInfo.nonRepairableFeatures |= static_cast <NonRepairableFeatureMask>(feature);
187+ putInfo (opCtx, versionInfo);
188+ }
189+
190+ void KVCatalog::FeatureTracker::markNonRepairableFeatureAsNotInUse (OperationContext* opCtx,
191+ NonRepairableFeature feature) {
192+ std::unique_ptr<Lock::ResourceLock> rLk;
193+ if (!_catalog->_isRsThreadSafe && opCtx->lockState ()) {
194+ rLk.reset (new Lock::ResourceLock (opCtx->lockState (), resourceIdCatalogMetadata, MODE_X));
195+ }
196+
197+ FeatureBits versionInfo = getInfo (opCtx);
198+ versionInfo.nonRepairableFeatures &= ~static_cast <NonRepairableFeatureMask>(feature);
199+ putInfo (opCtx, versionInfo);
200+ }
201+
202+ bool KVCatalog::FeatureTracker::isRepairableFeatureInUse (OperationContext* opCtx,
203+ RepairableFeature feature) const {
204+ std::unique_ptr<Lock::ResourceLock> rLk;
205+ if (!_catalog->_isRsThreadSafe && opCtx->lockState ()) {
206+ rLk.reset (new Lock::ResourceLock (opCtx->lockState (), resourceIdCatalogMetadata, MODE_S));
207+ }
208+
209+ FeatureBits versionInfo = getInfo (opCtx);
210+ return versionInfo.repairableFeatures & static_cast <RepairableFeatureMask>(feature);
211+ }
212+
213+ void KVCatalog::FeatureTracker::markRepairableFeatureAsInUse (OperationContext* opCtx,
214+ RepairableFeature feature) {
215+ std::unique_ptr<Lock::ResourceLock> rLk;
216+ if (!_catalog->_isRsThreadSafe && opCtx->lockState ()) {
217+ rLk.reset (new Lock::ResourceLock (opCtx->lockState (), resourceIdCatalogMetadata, MODE_X));
218+ }
219+
220+ FeatureBits versionInfo = getInfo (opCtx);
221+ versionInfo.repairableFeatures |= static_cast <RepairableFeatureMask>(feature);
222+ putInfo (opCtx, versionInfo);
223+ }
224+
225+ void KVCatalog::FeatureTracker::markRepairableFeatureAsNotInUse (OperationContext* opCtx,
226+ RepairableFeature feature) {
227+ std::unique_ptr<Lock::ResourceLock> rLk;
228+ if (!_catalog->_isRsThreadSafe && opCtx->lockState ()) {
229+ rLk.reset (new Lock::ResourceLock (opCtx->lockState (), resourceIdCatalogMetadata, MODE_X));
230+ }
231+
232+ FeatureBits versionInfo = getInfo (opCtx);
233+ versionInfo.repairableFeatures &= ~static_cast <RepairableFeatureMask>(feature);
234+ putInfo (opCtx, versionInfo);
235+ }
236+
237+ KVCatalog::FeatureTracker::FeatureBits KVCatalog::FeatureTracker::getInfo (
238+ OperationContext* opCtx) const {
239+ if (!_catalog->_isRsThreadSafe && opCtx->lockState ()) {
240+ invariant (opCtx->lockState ()->isLockHeldForMode (resourceIdCatalogMetadata, MODE_S));
241+ }
242+
243+ if (_rid.isNull ()) {
244+ return {};
245+ }
246+
247+ auto record = _catalog->_rs ->dataFor (opCtx, _rid);
248+ BSONObj obj = record.toBson ();
249+ invariant (isFeatureDocument (obj));
250+
251+ BSONElement nonRepairableFeaturesElem;
252+ auto nonRepairableFeaturesStatus = bsonExtractTypedField (
253+ obj, kNonRepairableFeaturesFieldName , BSONType::NumberLong, &nonRepairableFeaturesElem);
254+ fassert (40111 , nonRepairableFeaturesStatus);
255+
256+ BSONElement repairableFeaturesElem;
257+ auto repairableFeaturesStatus = bsonExtractTypedField (
258+ obj, kRepairableFeaturesFieldName , BSONType::NumberLong, &repairableFeaturesElem);
259+ fassert (40112 , repairableFeaturesStatus);
260+
261+ FeatureBits versionInfo;
262+ versionInfo.nonRepairableFeatures =
263+ static_cast <NonRepairableFeatureMask>(nonRepairableFeaturesElem.numberLong ());
264+ versionInfo.repairableFeatures =
265+ static_cast <RepairableFeatureMask>(repairableFeaturesElem.numberLong ());
266+ return versionInfo;
267+ }
268+
269+ void KVCatalog::FeatureTracker::putInfo (OperationContext* opCtx, const FeatureBits& versionInfo) {
270+ if (!_catalog->_isRsThreadSafe && opCtx->lockState ()) {
271+ invariant (opCtx->lockState ()->isLockHeldForMode (resourceIdCatalogMetadata, MODE_X));
272+ }
273+
274+ BSONObjBuilder bob;
275+ bob.appendBool (kIsFeatureDocumentFieldName , true );
276+ // We intentionally include the "ns" field with a null value in the feature document to prevent
277+ // older versions that do 'obj["ns"].String()' from starting up. This way only versions that are
278+ // aware of the feature document's existence can successfully start up.
279+ bob.appendNull (kNamespaceFieldName );
280+ bob.append (kNonRepairableFeaturesFieldName ,
281+ static_cast <long long >(versionInfo.nonRepairableFeatures ));
282+ bob.append (kRepairableFeaturesFieldName ,
283+ static_cast <long long >(versionInfo.repairableFeatures ));
284+ BSONObj obj = bob.done ();
285+
286+ if (_rid.isNull ()) {
287+ // This is the first time a feature is being marked as in-use or not in-use, so we must
288+ // insert the feature document rather than update it.
289+ const bool enforceQuota = false ;
290+ auto rid = _catalog->_rs ->insertRecord (opCtx, obj.objdata (), obj.objsize (), enforceQuota);
291+ fassert (40113 , rid.getStatus ());
292+ _rid = rid.getValue ();
293+ } else {
294+ const bool enforceQuota = false ;
295+ UpdateNotifier* notifier = nullptr ;
296+ auto status = _catalog->_rs ->updateRecord (
297+ opCtx, _rid, obj.objdata (), obj.objsize (), enforceQuota, notifier);
298+ fassert (40114 , status);
299+ }
300+ }
301+
90302KVCatalog::KVCatalog (RecordStore* rs,
91303 bool isRsThreadSafe,
92304 bool directoryPerDb,
@@ -132,12 +344,28 @@ void KVCatalog::init(OperationContext* opCtx) {
132344 while (auto record = cursor->next ()) {
133345 BSONObj obj = record->data .releaseToBson ();
134346
347+ if (FeatureTracker::isFeatureDocument (obj)) {
348+ // There should be at most one version document in the catalog.
349+ invariant (!_featureTracker);
350+
351+ // Initialize the feature tracker and skip over the version document because it doesn't
352+ // correspond to a namespace entry.
353+ _featureTracker = std::move (FeatureTracker::get (opCtx, this , record->id ));
354+ continue ;
355+ }
356+
135357 // No rollback since this is just loading already committed data.
136358 string ns = obj[" ns" ].String ();
137359 string ident = obj[" ident" ].String ();
138360 _idents[ns] = Entry (ident, record->id );
139361 }
140362
363+ if (!_featureTracker) {
364+ // If there wasn't a feature document, then just an initialize a feature tracker that
365+ // doesn't manage a feature document yet.
366+ _featureTracker = std::move (KVCatalog::FeatureTracker::create (opCtx, this ));
367+ }
368+
141369 // In the unlikely event that we have used this _rand before generate a new one.
142370 while (_hasEntryCollidingWithRand ()) {
143371 _rand = _newRand ();
0 commit comments