|
17 | 17 |
|
18 | 18 | #include "hash_table.h" |
19 | 19 |
|
| 20 | +#include "ep_time.h" |
20 | 21 | #include "item.h" |
21 | 22 | #include "stats.h" |
22 | 23 | #include "stored_value_factories.h" |
@@ -98,6 +99,11 @@ HashTable::StoredValueProxy::~StoredValueProxy() { |
98 | 99 | valueStats.epilogue(pre, value); |
99 | 100 | } |
100 | 101 |
|
| 102 | +void HashTable::StoredValueProxy::setCommitted(CommittedState state) { |
| 103 | + value->setCommitted(state); |
| 104 | + value->setCompletedOrDeletedTime(ep_current_time()); |
| 105 | +} |
| 106 | + |
101 | 107 | HashTable::HashTable(EPStats& st, |
102 | 108 | std::unique_ptr<AbstractStoredValueFactory> svFactory, |
103 | 109 | size_t initialSize, |
@@ -286,7 +292,7 @@ HashTable::FindInnerResult HashTable::findInner(const DocKey& key) { |
286 | 292 | for (StoredValue* v = values[hbl.getBucketNum()].get().get(); v; |
287 | 293 | v = v->getNext().get().get()) { |
288 | 294 | if (v->hasKey(key)) { |
289 | | - if (v->isPending()) { |
| 295 | + if (v->isPending() || v->isCompleted()) { |
290 | 296 | Expects(!foundPend); |
291 | 297 | foundPend = v; |
292 | 298 | } else { |
@@ -340,42 +346,33 @@ HashTable::UpdateResult HashTable::unlocked_updateStoredValue( |
340 | 346 | "call on a non-active HT object"); |
341 | 347 | } |
342 | 348 |
|
343 | | - switch (v.getCommitted()) { |
344 | | - case CommittedState::Pending: |
345 | | - case CommittedState::PreparedMaybeVisible: |
346 | | - // Cannot update a SV if it's a Pending item. |
| 349 | + if (v.isPending()) { |
347 | 350 | return {MutationStatus::IsPendingSyncWrite, nullptr}; |
348 | | - case CommittedState::PrepareAborted: |
349 | | - case CommittedState::PrepareCommitted: |
350 | | - // We shouldn't be trying to use PrepareCompleted states yet |
351 | | - throw std::logic_error( |
352 | | - "HashTable::unlocked_updateStoredValue" |
353 | | - " attempting to update a completed prepare"); |
354 | | - case CommittedState::CommittedViaMutation: |
355 | | - case CommittedState::CommittedViaPrepare: |
356 | | - // Logically /can/ update a non-Pending StoredValue with a Pending Item; |
357 | | - // however internally this is implemented as a separate (new) |
358 | | - // StoredValue object for the Pending item. |
359 | | - if (itm.isPending()) { |
360 | | - auto* sv = HashTable::unlocked_addNewStoredValue(hbl, itm); |
361 | | - return {MutationStatus::WasClean, sv}; |
362 | | - } |
| 351 | + } |
363 | 352 |
|
364 | | - // item is not Pending; can directly replace the existing SV. |
365 | | - MutationStatus status = v.isDirty() ? MutationStatus::WasDirty |
366 | | - : MutationStatus::WasClean; |
| 353 | + // Logically /can/ update a non-Pending StoredValue with a Pending Item; |
| 354 | + // however internally this is implemented as a separate (new) |
| 355 | + // StoredValue object for the Pending item. We can replace a completed |
| 356 | + // prepare with a new prepare though (so just drop through and update |
| 357 | + // normally). |
| 358 | + if (itm.isPending() && !v.isCompleted()) { |
| 359 | + auto* sv = HashTable::unlocked_addNewStoredValue(hbl, itm); |
| 360 | + return {MutationStatus::WasClean, sv}; |
| 361 | + } |
367 | 362 |
|
368 | | - const auto preProps = valueStats.prologue(&v); |
| 363 | + // Can directly replace the existing SV. |
| 364 | + MutationStatus status = |
| 365 | + v.isDirty() ? MutationStatus::WasDirty : MutationStatus::WasClean; |
369 | 366 |
|
370 | | - /* setValue() will mark v as undeleted if required */ |
371 | | - v.setValue(itm); |
372 | | - updateFreqCounter(v); |
| 367 | + const auto preProps = valueStats.prologue(&v); |
373 | 368 |
|
374 | | - valueStats.epilogue(preProps, &v); |
| 369 | + /* setValue() will mark v as undeleted if required */ |
| 370 | + v.setValue(itm); |
| 371 | + updateFreqCounter(v); |
375 | 372 |
|
376 | | - return {status, &v}; |
377 | | - } |
378 | | - folly::assume_unreachable(); |
| 373 | + valueStats.epilogue(preProps, &v); |
| 374 | + |
| 375 | + return {status, &v}; |
379 | 376 | } |
380 | 377 |
|
381 | 378 | StoredValue* HashTable::unlocked_addNewStoredValue(const HashBucketLock& hbl, |
@@ -420,7 +417,7 @@ HashTable::Statistics::StoredValueProperties::StoredValueProperties( |
420 | 417 | isDeleted = sv->isDeleted(); |
421 | 418 | isTempItem = sv->isTempItem(); |
422 | 419 | isSystemItem = sv->getKey().getCollectionID().isSystem(); |
423 | | - isPreparedSyncWrite = sv->isPending(); |
| 420 | + isPreparedSyncWrite = sv->isPending() || sv->isCompleted(); |
424 | 421 | } |
425 | 422 |
|
426 | 423 | HashTable::Statistics::StoredValueProperties HashTable::Statistics::prologue( |
@@ -634,28 +631,53 @@ HashTable::FindResult HashTable::findForWrite(const DocKey& key, |
634 | 631 | WantsDeleted wantsDeleted) { |
635 | 632 | auto result = findInner(key); |
636 | 633 |
|
637 | | - /// Writing using the Pending StoredValue (if found), else committed. |
638 | | - auto* sv = result.pendingSV ? result.pendingSV : result.committedSV; |
| 634 | + // We found a prepare. It may have been completed (Ephemeral) though. If it |
| 635 | + // has been completed we will return the committed StoredValue. |
| 636 | + if (result.pendingSV && !result.pendingSV->isCompleted()) { |
| 637 | + // Early return if we found a prepare. We should always return prepares |
| 638 | + // regardless of whether or not they are deleted or the caller has asked |
| 639 | + // for deleted SVs. For example, consider searching for a SyncDelete, we |
| 640 | + // should always return the deleted prepare. |
| 641 | + return {result.pendingSV, std::move(result.lock)}; |
| 642 | + } |
639 | 643 |
|
640 | | - if (!sv) { |
| 644 | + /// Writing using the Pending StoredValue (if found), else committed. |
| 645 | + if (!result.committedSV) { |
641 | 646 | // No item found - return null. |
642 | 647 | return {nullptr, std::move(result.lock)}; |
643 | 648 | } |
644 | 649 |
|
645 | | - // Early return if we found a prepare. We should always return prepares |
646 | | - // regardless of whether or not they are deleted or the caller has asked for |
647 | | - // deleted SVs. For example, consider searching for a SyncDelete, we should |
648 | | - // always return the deleted prepare. |
| 650 | + if (result.committedSV->isDeleted() && wantsDeleted == WantsDeleted::No) { |
| 651 | + // Deleted items should only be returned if caller asked for them - |
| 652 | + // otherwise return null. |
| 653 | + return {nullptr, std::move(result.lock)}; |
| 654 | + } |
| 655 | + return {result.committedSV, std::move(result.lock)}; |
| 656 | +} |
| 657 | + |
| 658 | +HashTable::FindResult HashTable::findForSyncWrite(const DocKey& key) { |
| 659 | + auto result = findInner(key); |
| 660 | + |
649 | 661 | if (result.pendingSV) { |
650 | | - return {sv, std::move(result.lock)}; |
| 662 | + // Early return if we found a prepare. We should always return |
| 663 | + // prepares regardless of whether or not they are deleted or the caller |
| 664 | + // has asked for deleted SVs. For example, consider searching for a |
| 665 | + // SyncDelete, we should always return the deleted prepare. Also, |
| 666 | + // we always return completed prepares. |
| 667 | + return {result.pendingSV, std::move(result.lock)}; |
651 | 668 | } |
652 | 669 |
|
653 | | - if (sv->isDeleted() && wantsDeleted == WantsDeleted::No) { |
| 670 | + if (!result.committedSV) { |
| 671 | + // No item found - return null. |
| 672 | + return {nullptr, std::move(result.lock)}; |
| 673 | + } |
| 674 | + |
| 675 | + if (result.committedSV->isDeleted()) { |
654 | 676 | // Deleted items should only be returned if caller asked for them - |
655 | 677 | // otherwise return null. |
656 | 678 | return {nullptr, std::move(result.lock)}; |
657 | 679 | } |
658 | | - return {sv, std::move(result.lock)}; |
| 680 | + return {result.committedSV, std::move(result.lock)}; |
659 | 681 | } |
660 | 682 |
|
661 | 683 | HashTable::StoredValueProxy HashTable::findForWrite(StoredValueProxy::RetSVPTag, |
|
0 commit comments