});
}
+seastar::future<TransactionManager::mutated_extents_locker>
+TransactionManager::lock_mutated_nodes(
+ Transaction &t)
+{
+ mutated_extents_set_t mutated_extents;
+ LOG_PREFIX(TransactionManager::lock_mutated_nodes);
+ t.for_each_mutated_extent(
+ [&mutated_extents](auto &extent) {
+ if (!is_root_type(extent.get_type())) {
+ std::ignore = mutated_extents.emplace(&extent);
+ }
+ });
+ if (unlikely(mutated_extents.empty())) {
+ co_return mutated_extents_locker();
+ }
+ std::vector<std::unique_lock<seastar::shared_mutex>> unique_locks;
+ std::vector<std::shared_lock<seastar::shared_mutex>> shared_locks;
+ if (auto &e = *mutated_extents.begin();
+ should_use_no_conflict_publish(t, e->get_type())) {
+ for (auto &ext : mutated_extents) {
+ assert(should_use_no_conflict_publish(t, ext->get_type()));
+ TRACET("locking {}", t, *ext);
+ auto lock = co_await seastar::get_unique_lock(ext->commit_lock);
+ unique_locks.emplace_back(std::move(lock));
+ }
+ } else {
+ for (auto &ext : mutated_extents) {
+ assert(!should_use_no_conflict_publish(t, ext->get_type()));
+ TRACET("shared locking {}", t, *ext);
+ auto lock = co_await seastar::get_shared_lock(ext->commit_lock);
+ shared_locks.emplace_back(std::move(lock));
+ }
+ }
+ co_return mutated_extents_locker(
+ std::move(mutated_extents), std::move(unique_locks), std::move(shared_locks));
+}
+
TransactionManager::submit_transaction_direct_ret
TransactionManager::do_submit_transaction(
Transaction &tref,
tref.get_phase_durations().lba_update +=
std::chrono::steady_clock::now() - lba_start;
+ // TODO: For now, we lock mutated extents after delayed ool writes
+ // and lba mappings updating, this is ok because:
+ // 1. at present, only lba/backref nodes might be modified by
+ // no_conflict trans;
+ // 2. only ool lba/backref extents' persistence needs to be sync'd
+ //
+ // In the future, when no_conflict transactions may also modify
+ // logical extents, we should add something like "lock_logical_mutated_extents"
+ // and invoke it before writing ool extents.
+ auto locker = co_await trans_intr::make_interruptible(
+ lock_mutated_nodes(tref));
+
auto num_extents = allocated_extents.size();
SUBTRACET(seastore_t, "process {} allocated extents", tref, num_extents);
ool_start = std::chrono::steady_clock::now();
co_await trans_intr::make_interruptible(
tref.get_handle().enter(write_pipeline.prepare)
);
+
+ // For conflicting transactions, we can release the lock
+ // now. Because other transactions accessing the same
+ // extents as the current one would be invalidated later
+ // in Cache::prepare_record()
+ locker.release_shared_lock();
+
tref.get_phase_durations().prepare_enter +=
std::chrono::steady_clock::now() - prepare_enter_start;
std::move(record),
tref.get_handle(),
tref.get_src(),
- [this, FNAME, &tref](record_locator_t submit_result) {
+ [&locker, this, FNAME, &tref](record_locator_t submit_result) {
SUBDEBUGT(seastore_t, "committed with {}", tref, submit_result);
auto start_seq = submit_result.write_result.start_seq;
journal->get_trimmer().set_journal_head(start_seq);
tref,
submit_result.record_block_base,
start_seq);
+ locker.release_lock();
journal->get_trimmer().update_journal_tails(
cache->get_oldest_dirty_from().value_or(start_seq),
cache->get_oldest_backref_dirty_from().value_or(start_seq));
return cache->can_drop_backref();
}
+ // use memory addresses as the key for comparing extents,
+ // this makes sure that mutexes of extents are always
+ // acquired in the same order, avoiding dead locks.
+ struct mem_addr_cmp_t {
+ bool operator()(const CachedExtentRef &lhs,
+ const CachedExtentRef &rhs) const {
+ return lhs.get() < rhs.get();
+ }
+ };
+
+ using mutated_extents_set_t =
+ std::set<CachedExtentRef, mem_addr_cmp_t>;
+ struct mutated_extents_locker {
+ mutated_extents_set_t mutated_extents;
+ std::vector<std::unique_lock<seastar::shared_mutex>> unique_locks;
+ std::vector<std::shared_lock<seastar::shared_mutex>> shared_locks;
+
+ mutated_extents_locker(
+ mutated_extents_set_t &&mutated_extents,
+ std::vector<std::unique_lock<seastar::shared_mutex>> &&unique_locks,
+ std::vector<std::shared_lock<seastar::shared_mutex>> &&shared_locks)
+ : mutated_extents(mutated_extents),
+ unique_locks(std::move(unique_locks)),
+ shared_locks(std::move(shared_locks)) {}
+ mutated_extents_locker() = default;
+ mutated_extents_locker(mutated_extents_locker&&) = default;
+
+ void release_lock() {
+ unique_locks.clear();
+ }
+
+ void release_shared_lock() {
+ shared_locks.clear();
+ }
+
+ void release() {
+ unique_locks.clear();
+ shared_locks.clear();
+ }
+ ~mutated_extents_locker() {
+ release();
+ }
+ };
+ seastar::future<mutated_extents_locker> lock_mutated_nodes(
+ Transaction &t);
+
using resolve_cursor_to_mapping_iertr = base_iertr;
resolve_cursor_to_mapping_iertr::future<LBAMapping>
resolve_cursor_to_mapping(