Skip to content

Commit 2960155

Browse files
committed
[manager] address asynchronous Copy review findings
1 parent 32143a4 commit 2960155

9 files changed

Lines changed: 674 additions & 76 deletions

kv_cache_manager/config/registry_manager.cc

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,30 @@ ErrorCode RegistryManager::RemoveStorage(RequestContext *request_context, const
176176
// Async Copy recovery needs that config to recreate the PACE client; the
177177
// old order could lose the only recovery route when Close returned busy or
178178
// failed with in-flight operations.
179+
const auto backend = data_storage_manager_->GetDataStorageBackend(global_unique_name);
180+
if (!backend) {
181+
RETURN_IF_EC_NOT_OK_WITH_LOG_S(WARN, EC_NOENT, "remove storage failed: runtime backend not found");
182+
}
183+
const StorageConfig storage_config = backend->GetStorageConfig();
179184
auto ec = data_storage_manager_->UnRegisterStorage(global_unique_name);
180185
RETURN_IF_EC_NOT_OK_WITH_LOG_S(WARN, ec, "remove storage failed");
181186
ec = LoadAndDelete(kRegistryStorageKey, global_unique_name);
182-
RETURN_IF_EC_NOT_OK_WITH_LOG_S(WARN, ec, "load and delete storage failed after backend close");
187+
if (ec != EC_OK) {
188+
// The durable registry still contains this storage. Restore the runtime
189+
// backend so the current leader matches the state that the next leader
190+
// will recover, and so a retry can make progress instead of failing
191+
// permanently with EC_NOENT.
192+
const auto rollback_ec =
193+
data_storage_manager_->RegisterStorage(request_context, global_unique_name, storage_config);
194+
if (rollback_ec != EC_OK) {
195+
PREFIX_LOG_S(ERROR,
196+
"load and delete storage failed after backend close, and runtime rollback failed, "
197+
"delete ec[%d] rollback ec[%d]",
198+
static_cast<int>(ec),
199+
static_cast<int>(rollback_ec));
200+
}
201+
RETURN_IF_EC_NOT_OK_WITH_LOG_S(WARN, ec, "load and delete storage failed after backend close");
202+
}
183203
PREFIX_LOG_S(INFO, "remove storage OK");
184204
return ec;
185205
}

kv_cache_manager/manager/cache_manager.cc

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -644,12 +644,22 @@ ErrorCode CacheManager::RemoveInstance(RequestContext *request_context,
644644
}
645645
if (!migration_manager_->GetActiveBlockKeysForInstance(instance_id).empty()) {
646646
KVCM_LOG_WARN("[%s] RemoveInstance drain timeout (%dms) for instance %s, "
647-
"proceeding with trim; residual WRITING targets will be orphan-cleaned",
647+
"refusing removal while asynchronous Copy state is still owned",
648648
trace_id.c_str(),
649649
kDrainTimeoutMs,
650650
instance_id.c_str());
651651
}
652652
}
653+
// A quarantined guard is intentionally invisible to the active task
654+
// table, and an accepted physical cleanup may outlive task completion.
655+
// Removing the registry entry here would make those durable guards
656+
// undiscoverable on the next leader and could close their backend while
657+
// it is still being used. Keep the instance registered until every
658+
// active/quarantined/cleanup reference has drained.
659+
if (migration_manager_->HasAsyncCopyInstanceReference(instance_id)) {
660+
PREFIX_LOG(WARN, "remove instance rejected: asynchronous Copy reference remains");
661+
return EC_EXIST;
662+
}
653663
}
654664

655665
auto ec = registry_manager_->RemoveInstance(request_context, instance_group, instance_id);

kv_cache_manager/manager/migration_manager.cc

Lines changed: 325 additions & 57 deletions
Large diffs are not rendered by default.

kv_cache_manager/manager/migration_manager.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ class MigrationManager : public std::enable_shared_from_this<MigrationManager> {
144144
const std::string &operator_name,
145145
const std::string &external_fencing_evidence);
146146
bool HasAsyncCopyStorageReference(const std::string &storage_name) const;
147+
bool HasAsyncCopyInstanceReference(const std::string &instance_id) const;
147148

148149
// ---- Copy 路径 ----
149150
// 同步完成"建目标 location + 提交 copy 任务",copy 字节复制异步执行;
@@ -240,6 +241,7 @@ class MigrationManager : public std::enable_shared_from_this<MigrationManager> {
240241
kTargetWritingExists, // 目标 storage 上存在 WRITING 副本(可能为其他迁移半成品)
241242
kSourceServingNotFound, // 源 storage 上没有 SERVING 副本,无可复制源
242243
kSourceRetrySuppressed, // 尚未被目标覆盖的源均处于失败退避,当前不应再次提交 Copy
244+
kSourcePinnedByGuard, // 源被另一条持久异步 Copy guard 钉住,不能并行派生新迁移
243245
};
244246

245247
struct CopyAdmission {
@@ -455,6 +457,7 @@ class MigrationManager : public std::enable_shared_from_this<MigrationManager> {
455457
struct PendingCopy {
456458
std::string instance_id;
457459
int64_t block_key = 0;
460+
size_t expected_items = 0;
458461
std::future<PlanExecuteResult> future;
459462
bool native_async = false;
460463
std::future<AsyncCopyRemoteSubmitResult> remote_submit_future;
@@ -463,6 +466,16 @@ class MigrationManager : public std::enable_shared_from_this<MigrationManager> {
463466
std::chrono::steady_clock::time_point completion_retry_after{};
464467
};
465468

469+
// An async Copy is not fully cleaned up when its target/source metadata is
470+
// merely queued for deletion. Keep a reference until the executor finishes
471+
// the physical delete so RemoveStorage cannot close the backend underneath
472+
// the cleanup task.
473+
struct PendingLocationCleanup {
474+
std::string instance_id;
475+
std::string storage_name;
476+
std::future<PlanExecuteResult> future;
477+
};
478+
466479
struct ExpiringMark {
467480
int64_t deadline_ms = 0;
468481
std::string instance_id;
@@ -517,10 +530,15 @@ class MigrationManager : public std::enable_shared_from_this<MigrationManager> {
517530
bool ResumePendingGuardFinalization(const CopyTaskContext &ctx);
518531
bool PersistRemoteAsyncCopyAcceptance(const std::string &instance_id,
519532
int64_t block_key,
533+
size_t expected_items,
520534
const AsyncCopyRemoteSubmitResult &remote_result,
521535
CopyTaskContext &out_ctx,
522536
bool &out_cancel_requested);
523537
void SubmitPreparedTargetLocationDelete(const CopyTaskContext &ctx);
538+
void TrackLocationCleanup(const std::string &instance_id,
539+
const std::string &storage_name,
540+
std::future<PlanExecuteResult> future);
541+
void ProcessCompletedLocationCleanups();
524542
bool ReserveAsyncCopyCredit(const MigrationRequest &request, uint64_t total_bytes);
525543
void ReleaseAsyncCopyCredit(const CopyTaskContext &ctx);
526544
void MoveAsyncCopyCreditToQuarantine(const CopyTaskContext &ctx, const std::string &reason);
@@ -682,6 +700,8 @@ class MigrationManager : public std::enable_shared_from_this<MigrationManager> {
682700
std::mutex pending_mutex_;
683701
std::condition_variable pending_cv_;
684702
std::deque<PendingCopy> pending_copies_;
703+
mutable std::mutex pending_cleanup_mutex_;
704+
std::deque<PendingLocationCleanup> pending_location_cleanups_;
685705

686706
std::mutex mark_expiry_mutex_;
687707
std::priority_queue<ExpiringMark, std::vector<ExpiringMark>, ExpiringMarkGreater> mark_expiry_queue_;

kv_cache_manager/manager/schedule_plan_executor.cc

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -732,13 +732,12 @@ SchedulePlanExecutor::PrepareDeleteTaskImpl(const std::string &instance_id,
732732
if (admission_result.actual_task.block_keys.empty()) {
733733
return admission_result;
734734
}
735-
if (!indexer->Sync(admission_result.actual_task.block_keys)) {
736-
admission_result.result = MakeErrorResult(
737-
EC_ERROR,
738-
StringUtil::FormatString(
739-
"Sync failed or timed out for prepared location delete, instance[%s]", instance_id.c_str()));
740-
return admission_result;
741-
}
735+
// prepared_deleting is used only after the caller has durably changed
736+
// the exact location to CLS_DELETING and cleared its Copy guard. There
737+
// is no metadata mutation to flush here. A second Sync could fail after
738+
// the authoritative transition already succeeded and strand a
739+
// guard-free CLS_DELETING location without ever scheduling its physical
740+
// cleanup.
742741
admission_result.needs_physical_delete = true;
743742
return admission_result;
744743
}

kv_cache_manager/manager/schedule_plan_executor.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ class SchedulePlanExecutor {
146146
ErrorCode RequestCancelAsyncCopy(const std::string &storage_name, const std::string &operation_id);
147147
AsyncDeleteSubmitResult SubmitAsync(const CacheMetaDelRequest &task);
148148
AsyncDeleteSubmitResult SubmitAsync(const CacheLocationDelRequest &task);
149+
std::future<PlanExecuteResult> SubmitLocationDelete(const CacheLocationDelRequest &task,
150+
ScheduleTaskClass task_class);
149151

150152
bool SubmitNonBlocking(const CacheMetaDelRequest &req, ScheduleTaskClass task_class = ScheduleTaskClass::kSystem);
151153
bool SubmitNonBlocking(const CacheLocationDelRequest &req,
@@ -211,8 +213,6 @@ class SchedulePlanExecutor {
211213
AsyncDeleteSubmitResult SubmitDeleteTaskAsync(std::chrono::microseconds delay,
212214
std::function<LocationDelAdmissionResult()> prepare);
213215
std::future<PlanExecuteResult> SubmitMetaDelete(const CacheMetaDelRequest &task, ScheduleTaskClass task_class);
214-
std::future<PlanExecuteResult> SubmitLocationDelete(const CacheLocationDelRequest &task,
215-
ScheduleTaskClass task_class);
216216
PlanExecuteResult DoLocationDelTask(const CacheLocationDelRequest &task);
217217
void DoCopyTask(const std::shared_ptr<std::promise<PlanExecuteResult>> &promise,
218218
const CacheLocationCopyRequest &task);

0 commit comments

Comments
 (0)