BeginAppUploadBatch
| 1420 | |
| 1421 | // BeginAppUploadBatch |
| 1422 | RpcResult HandleBeginBatch(uint32_t appId, const std::vector<PB::Field>& reqBody) { |
| 1423 | uint64_t batchId = BatchTracker_NextId(); |
| 1424 | uint32_t accountId = 0; |
| 1425 | if (!RequireAccountId("BeginAppUploadBatch", appId, accountId)) { |
| 1426 | // Fail early: error skips CompleteBatch, preventing orphaned local blobs. |
| 1427 | return RpcResult(PB::Writer(), kEResultFail); |
| 1428 | } |
| 1429 | |
| 1430 | // If a previous batch's cloud publish is still in-flight, wait for it so |
| 1431 | // FetchCloudStateForServe sees the fresh CN. Typically a no-op (single batch) |
| 1432 | // or instant (publish already landed between batches). |
| 1433 | CloudStorage::WaitForPendingPublish(accountId, appId); |
| 1434 | |
| 1435 | // The CN returned here is what Steam records as synced and what we must publish |
| 1436 | // at CompleteBatch -- they have to match, or Steam re-downloads what it just |
| 1437 | // uploaded. Assign max(local, cloud)+1, strictly above whatever the cloud holds. |
| 1438 | uint64_t localCN = LocalStorage::GetChangeNumber(accountId, appId); |
| 1439 | uint64_t cloudCN = 0; |
| 1440 | if (CloudStorage::IsCloudActive()) { |
| 1441 | // Runs on Steam's BeginAppUploadBatch thread; time it (serve-cached, usually |
| 1442 | // fast, but a cache miss does a live fetch that could stall the main loop). |
| 1443 | auto tbb = std::chrono::steady_clock::now(); |
| 1444 | auto cloud = CloudStorage::FetchCloudStateForServe(accountId, appId); |
| 1445 | auto bbMs = std::chrono::duration_cast<std::chrono::milliseconds>( |
| 1446 | std::chrono::steady_clock::now() - tbb).count(); |
| 1447 | if (bbMs > 500) |
| 1448 | LOG("[NS] BeginBatch app=%u: FetchCloudStateForServe took %lldms " |
| 1449 | "(on Steam's thread)", appId, (long long)bbMs); |
| 1450 | if (cloud.status == CloudStorage::StateFetchStatus::Ok) { |
| 1451 | cloudCN = cloud.state.cn; |
| 1452 | } else if (cloud.status != CloudStorage::StateFetchStatus::NotFound) { |
| 1453 | // Cloud CN unknown: assigning localCN+1 could regress the cloud CN. |
| 1454 | // Fail the batch; Steam retries once the fetch resolves. |
| 1455 | LOG("[NS] BeginBatch app=%u: cloud CN unverifiable (status=%d) -- failing batch " |
| 1456 | "to avoid regressive CN; Steam will retry", appId, |
| 1457 | static_cast<int>(cloud.status)); |
| 1458 | return RpcResult(PB::Writer(), kEResultFail); |
| 1459 | } |
| 1460 | } |
| 1461 | uint64_t assignedCN = (std::max)(localCN, cloudCN) + 1; |
| 1462 | uint64_t appBuildId = 0; |
| 1463 | PrepareBatchCanonicalTokens(accountId, appId); |
| 1464 | PendingOpsJournal::RecordUploadBatchStart(accountId, appId); |
| 1465 | |
| 1466 | int uploadCount = 0, deleteCount = 0; |
| 1467 | for (auto& f : reqBody) { |
| 1468 | if (f.fieldNum == 3 && f.wireType == PB::LengthDelimited) { |
| 1469 | std::string name(reinterpret_cast<const char*>(f.data), f.dataLen); |
| 1470 | LOG("[NS-BATCH] upload: %s", SanitizeForLog(name).c_str()); |
| 1471 | TryCaptureRootToken(accountId, appId, |
| 1472 | CanonicalizeUploadRootToken(accountId, appId, StripRootToken(name), ExtractRootToken(name))); |
| 1473 | ++uploadCount; |
| 1474 | } |
| 1475 | if (f.fieldNum == 4 && f.wireType == PB::LengthDelimited) { |
| 1476 | std::string name(reinterpret_cast<const char*>(f.data), f.dataLen); |
| 1477 | LOG("[NS-BATCH] delete: %s", SanitizeForLog(name).c_str()); |
| 1478 | TryCaptureRootToken(accountId, appId, |
| 1479 | CanonicalizeUploadRootToken(accountId, appId, StripRootToken(name), ExtractRootToken(name))); |
no test coverage detected