mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 14:46:12 -04:00
1855 lines
74 KiB
C++
1855 lines
74 KiB
C++
// Copyright 2021-2023 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include "connections/implementation/payload_manager.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "absl/container/flat_hash_map.h"
|
|
#include "absl/functional/any_invocable.h"
|
|
#include "absl/functional/bind_front.h"
|
|
#include "absl/strings/str_cat.h"
|
|
#include "absl/strings/str_format.h"
|
|
#include "absl/time/time.h"
|
|
#include "connections/implementation/analytics/packet_meta_data.h"
|
|
#include "connections/implementation/analytics/throughput_recorder.h"
|
|
#include "connections/implementation/client_proxy.h"
|
|
#include "connections/implementation/endpoint_channel_manager.h"
|
|
#include "connections/implementation/endpoint_manager.h"
|
|
#include "connections/implementation/flags/nearby_connections_feature_flags.h"
|
|
#include "connections/implementation/internal_payload.h"
|
|
#include "connections/implementation/internal_payload_factory.h"
|
|
#include "connections/implementation/proto/offline_wire_formats.pb.h"
|
|
#include "connections/listeners.h"
|
|
#include "connections/medium_selector.h"
|
|
#include "connections/payload.h"
|
|
#include "connections/payload_type.h"
|
|
#include "connections/status.h"
|
|
#include "internal/flags/nearby_flags.h"
|
|
#include "internal/platform/byte_array.h"
|
|
#include "internal/platform/count_down_latch.h"
|
|
#include "internal/platform/exception.h"
|
|
#include "internal/platform/expected.h"
|
|
#include "internal/platform/feature_flags.h"
|
|
#include "internal/platform/implementation/system_clock.h"
|
|
#include "internal/platform/logging.h"
|
|
#include "internal/platform/mutex_lock.h"
|
|
#include "internal/platform/single_thread_executor.h"
|
|
#include "proto/connections_enums.pb.h"
|
|
|
|
namespace nearby {
|
|
namespace connections {
|
|
|
|
namespace {
|
|
using ::location::nearby::connections::OfflineFrame;
|
|
using ::location::nearby::connections::PayloadTransferFrame;
|
|
using ::location::nearby::connections::V1Frame;
|
|
using ::location::nearby::proto::connections::Medium;
|
|
using ::location::nearby::proto::connections::OperationResultCode;
|
|
using ::location::nearby::proto::connections::PayloadStatus;
|
|
using PacketMetaData = ::nearby::analytics::PacketMetaData;
|
|
using ::nearby::analytics::ThroughputRecorderContainer;
|
|
using PayloadDirection = ::nearby::connections::PayloadDirection;
|
|
|
|
constexpr absl::Duration kMinTransferUpdateInterval = absl::Milliseconds(50);
|
|
} // namespace
|
|
|
|
bool PayloadManager::SendPayloadLoop(
|
|
ClientProxy* client, PendingPayload& pending_payload,
|
|
PayloadTransferFrame::PayloadHeader& payload_header,
|
|
std::int64_t& next_chunk_offset, size_t resume_offset, int index) {
|
|
// in lieu of structured binding:
|
|
auto pair = GetAvailableAndUnavailableEndpoints(pending_payload);
|
|
const EndpointIds& available_endpoint_ids =
|
|
EndpointsToEndpointIds(pair.first);
|
|
const Endpoints& unavailable_endpoints = pair.second;
|
|
PacketMetaData packet_meta_data;
|
|
|
|
// First, handle any non-available endpoints.
|
|
for (const auto& endpoint : unavailable_endpoints) {
|
|
HandleFinishedOutgoingPayload(
|
|
client, {endpoint->id}, payload_header, next_chunk_offset,
|
|
EndpointInfoStatusToOperationResultCode(endpoint->status.Get()),
|
|
EndpointInfoStatusToPayloadStatus(endpoint->status.Get()));
|
|
}
|
|
|
|
// Update the still-active recipients of this payload.
|
|
if (available_endpoint_ids.empty()) {
|
|
VLOG(1) << "PayloadManager short-circuiting payload_id="
|
|
<< pending_payload.GetInternalPayload()->GetId()
|
|
<< " after sending " << next_chunk_offset
|
|
<< " bytes because none of the endpoints are available anymore.";
|
|
return false;
|
|
}
|
|
|
|
// Check if the payload has been cancelled by the client and, if so,
|
|
// notify the remaining recipients.
|
|
if (pending_payload.IsLocallyCanceled()) {
|
|
VLOG(1) << "Aborting send of payload_id="
|
|
<< pending_payload.GetInternalPayload()->GetId() << " at offset "
|
|
<< next_chunk_offset << " since it is marked canceled.";
|
|
HandleFinishedOutgoingPayload(
|
|
client, available_endpoint_ids, payload_header, next_chunk_offset,
|
|
OperationResultCode::CLIENT_CANCELLATION_LOCAL_CANCEL_PAYLOAD,
|
|
PayloadStatus::LOCAL_CANCELLATION);
|
|
return false;
|
|
}
|
|
|
|
// Update the current offsets for all endpoints still active for this
|
|
// payload. For the sake of accuracy, we update the pending payload here
|
|
// because it's after all payload terminating events are handled, but
|
|
// right before we actually start detaching the next chunk.
|
|
if (next_chunk_offset == 0 && resume_offset > 0) {
|
|
ExceptionOr<size_t> real_offset =
|
|
pending_payload.GetInternalPayload()->SkipToOffset(resume_offset);
|
|
if (!real_offset.ok()) {
|
|
// Stop sending since it may cause remote file merging failed.
|
|
LOG(WARNING) << "PayloadManager failed to skip offset " << resume_offset
|
|
<< " on payload_id "
|
|
<< pending_payload.GetInternalPayload()->GetId();
|
|
HandleFinishedOutgoingPayload(client, available_endpoint_ids,
|
|
payload_header, next_chunk_offset,
|
|
OperationResultCode::IO_FILE_READING_ERROR,
|
|
PayloadStatus::LOCAL_ERROR);
|
|
return false;
|
|
}
|
|
VLOG(1) << "PayloadManager successfully skipped " << real_offset.GetResult()
|
|
<< " bytes on payload_id "
|
|
<< pending_payload.GetInternalPayload()->GetId();
|
|
next_chunk_offset = real_offset.GetResult();
|
|
}
|
|
for (const auto& endpoint_id : available_endpoint_ids) {
|
|
pending_payload.SetOffsetForEndpoint(endpoint_id, next_chunk_offset);
|
|
}
|
|
|
|
// This will block if there is no data to transfer.
|
|
// It will resume when new data arrives, or if Close() is called.
|
|
int chunk_size = GetOptimalChunkSize(available_endpoint_ids);
|
|
packet_meta_data.StartFileIo();
|
|
ByteArray next_chunk =
|
|
pending_payload.GetInternalPayload()->DetachNextChunk(chunk_size);
|
|
packet_meta_data.StopFileIo();
|
|
if (shutdown_.Get()) return false;
|
|
// Save chunk size. We'll need it after we move next_chunk.
|
|
auto next_chunk_size = next_chunk.size();
|
|
if (!next_chunk_size &&
|
|
pending_payload.GetInternalPayload()->GetTotalSize() > 0 &&
|
|
pending_payload.GetInternalPayload()->GetTotalSize() <
|
|
next_chunk_offset) {
|
|
VLOG(1) << "Payload xfer failed: payload_id="
|
|
<< pending_payload.GetInternalPayload()->GetId();
|
|
HandleFinishedOutgoingPayload(
|
|
client, available_endpoint_ids, payload_header, next_chunk_offset,
|
|
OperationResultCode::IO_FILE_READING_ERROR, PayloadStatus::LOCAL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
// Only need to handle outgoing data chunk offset, because the offset will be
|
|
// used to decide if the received chunk is the initial payload chunk.
|
|
// In other cases, the offset should only be used in both side logs when error
|
|
// happened.
|
|
PayloadTransferFrame::PayloadChunk payload_chunk(CreatePayloadChunk(
|
|
next_chunk_offset - resume_offset, std::move(next_chunk), index));
|
|
const EndpointIds& failed_endpoint_ids = endpoint_manager_->SendPayloadChunk(
|
|
payload_header, payload_chunk, available_endpoint_ids, packet_meta_data);
|
|
// Check whether at least one endpoint failed.
|
|
if (!failed_endpoint_ids.empty()) {
|
|
VLOG(1) << "Payload xfer: endpoints failed: payload_id="
|
|
<< payload_header.id() << "; endpoint_ids={"
|
|
<< ToString(failed_endpoint_ids) << "}",
|
|
HandleFinishedOutgoingPayload(
|
|
client, failed_endpoint_ids, payload_header, next_chunk_offset,
|
|
OperationResultCode::CONNECTIVITY_GENERIC_WRITING_CHANNEL_IO_ERROR,
|
|
PayloadStatus::ENDPOINT_IO_ERROR);
|
|
}
|
|
bool is_last_chunk = IsLastChunk(payload_chunk);
|
|
// Check whether at least one endpoint succeeded -- if they all failed,
|
|
// we'll just go right back to the top of the loop and break out when
|
|
// availableEndpointIds is re-synced and found to be empty at that point.
|
|
if (failed_endpoint_ids.size() < available_endpoint_ids.size()) {
|
|
for (const auto& endpoint_id : available_endpoint_ids) {
|
|
if (std::find(failed_endpoint_ids.begin(), failed_endpoint_ids.end(),
|
|
endpoint_id) == failed_endpoint_ids.end()) {
|
|
const bool should_wait_for_received_ack =
|
|
is_last_chunk && client->IsPayloadReceivedAckEnabled(endpoint_id);
|
|
if (should_wait_for_received_ack) {
|
|
if (!WaitForReceivedAck(client, endpoint_id, pending_payload,
|
|
payload_header, next_chunk_offset,
|
|
is_last_chunk)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
HandleSuccessfulOutgoingChunk(
|
|
client, endpoint_id, payload_header, payload_chunk.flags(),
|
|
payload_chunk.offset(), payload_chunk.body().size());
|
|
}
|
|
}
|
|
VLOG(1) << "PayloadManager done sending chunk at offset "
|
|
<< next_chunk_offset << " of payload_id="
|
|
<< pending_payload.GetInternalPayload()->GetId();
|
|
next_chunk_offset += next_chunk_size;
|
|
|
|
if (!next_chunk_size) {
|
|
// That was the last chunk, we're outta here.
|
|
VLOG(1) << "Payload xfer done: payload_id="
|
|
<< pending_payload.GetInternalPayload()->GetId()
|
|
<< "; size=" << next_chunk_offset;
|
|
ThroughputRecorderContainer::GetInstance()
|
|
.GetTPRecorder(pending_payload.GetInternalPayload()->GetId(),
|
|
PayloadDirection::OUTGOING_PAYLOAD)
|
|
->MarkAsSuccess();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::pair<PayloadManager::Endpoints, PayloadManager::Endpoints>
|
|
PayloadManager::GetAvailableAndUnavailableEndpoints(
|
|
const PendingPayload& pending_payload) {
|
|
Endpoints available;
|
|
Endpoints unavailable;
|
|
for (auto* endpoint_info : pending_payload.GetEndpoints()) {
|
|
if (endpoint_info->status.Get() ==
|
|
PayloadManager::EndpointInfo::Status::kAvailable) {
|
|
available.push_back(endpoint_info);
|
|
} else {
|
|
unavailable.push_back(endpoint_info);
|
|
}
|
|
}
|
|
return std::make_pair(std::move(available), std::move(unavailable));
|
|
}
|
|
|
|
PayloadManager::EndpointIds PayloadManager::EndpointsToEndpointIds(
|
|
const Endpoints& endpoints) {
|
|
EndpointIds endpoint_ids;
|
|
endpoint_ids.reserve(endpoints.size());
|
|
for (const auto& item : endpoints) {
|
|
if (item) {
|
|
endpoint_ids.emplace_back(item->id);
|
|
}
|
|
}
|
|
return endpoint_ids;
|
|
}
|
|
|
|
std::string PayloadManager::ToString(const Endpoints& endpoints) {
|
|
std::string endpoints_string = absl::StrCat(endpoints.size(), ": ");
|
|
bool first = true;
|
|
for (const auto& item : endpoints) {
|
|
if (first) {
|
|
absl::StrAppend(&endpoints_string, item->id);
|
|
first = false;
|
|
} else {
|
|
absl::StrAppend(&endpoints_string, ", ", item->id);
|
|
}
|
|
}
|
|
return endpoints_string;
|
|
}
|
|
|
|
std::string PayloadManager::ToString(const EndpointIds& endpoint_ids) {
|
|
std::string endpoints_string = absl::StrCat(endpoint_ids.size(), ": ");
|
|
bool first = true;
|
|
for (const auto& id : endpoint_ids) {
|
|
if (first) {
|
|
absl::StrAppend(&endpoints_string, id);
|
|
first = false;
|
|
} else {
|
|
absl::StrAppend(&endpoints_string, ", ", id);
|
|
}
|
|
}
|
|
return endpoints_string;
|
|
}
|
|
|
|
std::string PayloadManager::ToString(PayloadType type) {
|
|
switch (type) {
|
|
case PayloadType::kBytes:
|
|
return std::string("Bytes");
|
|
case PayloadType::kStream:
|
|
return std::string("Stream");
|
|
case PayloadType::kFile:
|
|
return std::string("File");
|
|
case PayloadType::kUnknown:
|
|
return std::string("Unknown");
|
|
}
|
|
}
|
|
|
|
std::string PayloadManager::ToString(EndpointInfo::Status status) {
|
|
switch (status) {
|
|
case EndpointInfo::Status::kAvailable:
|
|
return std::string("Available");
|
|
case EndpointInfo::Status::kCanceled:
|
|
return std::string("Cancelled");
|
|
case EndpointInfo::Status::kError:
|
|
return std::string("Error");
|
|
case EndpointInfo::Status::kUnknown:
|
|
return std::string("Unknown");
|
|
}
|
|
}
|
|
|
|
// Creates and starts tracking a PendingPayload for this Payload.
|
|
Payload::Id PayloadManager::CreateOutgoingPayload(
|
|
Payload payload, const EndpointIds& endpoint_ids) {
|
|
ErrorOr<std::unique_ptr<InternalPayload>> result =
|
|
CreateOutgoingInternalPayload(std::move(payload));
|
|
if (result.has_error()) {
|
|
LOG(ERROR) << "Failed to create outgoing internal payload: "
|
|
<< result.error().operation_result_code().value();
|
|
return Payload::Id();
|
|
}
|
|
std::unique_ptr<InternalPayload> internal_payload = std::move(result.value());
|
|
Payload::Id payload_id = internal_payload->GetId();
|
|
VLOG(1) << "CreateOutgoingPayload: payload_id=" << payload_id;
|
|
MutexLock lock(&mutex_);
|
|
pending_payloads_.StartTrackingPayload(
|
|
payload_id,
|
|
std::make_unique<PendingPayload>(
|
|
std::move(internal_payload), endpoint_ids,
|
|
/*is_incoming=*/false,
|
|
absl::bind_front(&PayloadManager::OnPendingPayloadDestroy, this)));
|
|
|
|
return payload_id;
|
|
}
|
|
|
|
PayloadManager::PayloadManager(EndpointManager& endpoint_manager)
|
|
: endpoint_manager_(&endpoint_manager) {
|
|
endpoint_manager_->RegisterFrameProcessor(V1Frame::PAYLOAD_TRANSFER, this);
|
|
custom_save_path_ = "";
|
|
}
|
|
|
|
void PayloadManager::CancelAllPayloads() {
|
|
VLOG(1) << "PayloadManager: canceling payloads; self=" << this;
|
|
{
|
|
MutexLock lock(&mutex_);
|
|
int pending_outgoing_payloads = 0;
|
|
pending_payloads_.ForEachPayload([&](PendingPayload* pending) {
|
|
if (!pending->IsIncoming()) pending_outgoing_payloads++;
|
|
pending->MarkLocallyCanceled();
|
|
pending->Close(); // To unblock the sender thread, if there is no data.
|
|
});
|
|
|
|
if (pending_outgoing_payloads) {
|
|
shutdown_barrier_ =
|
|
std::make_unique<CountDownLatch>(pending_outgoing_payloads);
|
|
}
|
|
}
|
|
if (shutdown_barrier_) {
|
|
VLOG(1) << "PayloadManager: waiting for pending outgoing "
|
|
"payloads; self="
|
|
<< this;
|
|
shutdown_barrier_->Await();
|
|
}
|
|
}
|
|
|
|
void PayloadManager::DisconnectFromEndpointManager() {
|
|
if (shutdown_.Set(true)) return;
|
|
// Unregister ourselves from the FrameProcessors.
|
|
endpoint_manager_->UnregisterFrameProcessor(V1Frame::PAYLOAD_TRANSFER, this);
|
|
}
|
|
|
|
PayloadManager::~PayloadManager() {
|
|
VLOG(1) << "PayloadManager: going down; self=" << this;
|
|
ThroughputRecorderContainer::GetInstance().Shutdown();
|
|
DisconnectFromEndpointManager();
|
|
CancelAllPayloads();
|
|
VLOG(1) << "PayloadManager: turn down payload executors; self=" << this;
|
|
bytes_payload_executor_.Shutdown();
|
|
stream_payload_executor_.Shutdown();
|
|
file_payload_executor_.Shutdown();
|
|
send_payload_ack_executor_.Shutdown();
|
|
|
|
CountDownLatch stop_latch(1);
|
|
// Clear our tracked pending payloads.
|
|
RunOnStatusUpdateThread(
|
|
"~payload-manager",
|
|
[this, &stop_latch]() RUN_ON_PAYLOAD_STATUS_UPDATE_THREAD() {
|
|
VLOG(1) << "PayloadManager: stop tracking payloads; self=" << this;
|
|
MutexLock lock(&mutex_);
|
|
pending_payloads_.StopTrackingAllPayloads();
|
|
stop_latch.CountDown();
|
|
});
|
|
stop_latch.Await();
|
|
|
|
VLOG(1) << "PayloadManager: turn down notification executor; self=" << this;
|
|
// Stop all the ongoing Runnables (as gracefully as possible).
|
|
payload_status_update_executor_.Shutdown();
|
|
|
|
VLOG(1) << "PayloadManager: down; self=" << this;
|
|
}
|
|
|
|
bool PayloadManager::NotifyShutdown() {
|
|
MutexLock lock(&mutex_);
|
|
if (!shutdown_.Get()) return false;
|
|
if (!shutdown_barrier_) return false;
|
|
VLOG(1) << "PayloadManager [shutdown mode]";
|
|
shutdown_barrier_->CountDown();
|
|
return true;
|
|
}
|
|
|
|
void PayloadManager::SendPayload(ClientProxy* client,
|
|
const EndpointIds& endpoint_ids,
|
|
Payload payload) {
|
|
if (shutdown_.Get()) return;
|
|
VLOG(1) << "SendPayload: endpoint_ids={" << ToString(endpoint_ids) << "}";
|
|
// Before transfer to internal payload, retrieves the Payload size for
|
|
// analytics.
|
|
std::int64_t payload_total_size;
|
|
switch (payload.GetType()) {
|
|
case connections::PayloadType::kBytes:
|
|
payload_total_size = payload.AsBytes().size();
|
|
break;
|
|
case connections::PayloadType::kFile:
|
|
payload_total_size = payload.AsFile()->GetTotalSize();
|
|
break;
|
|
case connections::PayloadType::kStream:
|
|
case connections::PayloadType::kUnknown:
|
|
payload_total_size = -1;
|
|
break;
|
|
}
|
|
|
|
auto executor = GetOutgoingPayloadExecutor(payload.GetType());
|
|
// The |executor| will be null if the payload is of a type we cannot work
|
|
// with. This should never be reached since the ServiceControllerRouter has
|
|
// already checked whether or not we can work with this Payload type.
|
|
if (!executor) {
|
|
RecordInvalidPayloadAnalytics(
|
|
client, endpoint_ids, payload.GetId(), payload.GetType(),
|
|
payload.GetOffset(), payload_total_size,
|
|
OperationResultCode::NEARBY_GENERIC_OUTGOING_PAYLOAD_CREATION_FAILURE);
|
|
VLOG(1) << "PayloadManager failed to determine the right executor for "
|
|
"outgoing payload_id="
|
|
<< payload.GetId()
|
|
<< ", payload_type=" << ToString(payload.GetType());
|
|
return;
|
|
}
|
|
|
|
// Each payload is sent in FCFS order within each Payload type, blocking any
|
|
// other payload of the same type from even starting until this one is
|
|
// completely done with. If we ever want to provide isolation across
|
|
// ClientProxy objects this will need to be significantly re-architected.
|
|
PayloadType payload_type = payload.GetType();
|
|
size_t resume_offset =
|
|
FeatureFlags::GetInstance().GetFlags().enable_send_payload_offset
|
|
? payload.GetOffset()
|
|
: 0;
|
|
|
|
Payload::Id payload_id =
|
|
CreateOutgoingPayload(std::move(payload), endpoint_ids);
|
|
executor->Execute("send-payload", [this, client, endpoint_ids, payload_id,
|
|
payload_type, resume_offset,
|
|
payload_total_size]() {
|
|
if (shutdown_.Get()) return;
|
|
PendingPayloadHandle pending_payload = GetPayload(payload_id);
|
|
if (!pending_payload) {
|
|
RecordInvalidPayloadAnalytics(
|
|
client, endpoint_ids, payload_id, payload_type, resume_offset,
|
|
payload_total_size,
|
|
OperationResultCode::
|
|
NEARBY_GENERIC_OUTGOING_PAYLOAD_CREATION_FAILURE);
|
|
VLOG(1) << "PayloadManager failed to create InternalPayload for outgoing "
|
|
"payload_id="
|
|
<< payload_id << ", payload_type=" << ToString(payload_type)
|
|
<< ", aborting sendPayload().";
|
|
return;
|
|
}
|
|
auto* internal_payload = pending_payload->GetInternalPayload();
|
|
if (!internal_payload) return;
|
|
|
|
RecordPayloadStartedAnalytics(client, endpoint_ids, payload_id,
|
|
payload_type, resume_offset,
|
|
internal_payload->GetTotalSize());
|
|
|
|
PayloadTransferFrame::PayloadHeader payload_header{
|
|
CreatePayloadHeader(*internal_payload, resume_offset)};
|
|
|
|
bool should_continue = true;
|
|
std::int64_t next_chunk_offset = 0;
|
|
int index = 0;
|
|
|
|
ThroughputRecorderContainer::GetInstance()
|
|
.GetTPRecorder(payload_id, PayloadDirection::OUTGOING_PAYLOAD)
|
|
->Start(payload_type, PayloadDirection::OUTGOING_PAYLOAD);
|
|
while (should_continue && !shutdown_.Get()) {
|
|
should_continue =
|
|
SendPayloadLoop(client, *pending_payload, payload_header,
|
|
next_chunk_offset, resume_offset, index);
|
|
index++;
|
|
}
|
|
|
|
RunOnStatusUpdateThread("destroy-payload",
|
|
[this, payload_id]()
|
|
RUN_ON_PAYLOAD_STATUS_UPDATE_THREAD() {
|
|
DestroyPendingPayload(payload_id);
|
|
});
|
|
});
|
|
VLOG(1) << "PayloadManager: xfer scheduled: self=" << this
|
|
<< "; payload_id=" << payload_id
|
|
<< ", payload_type=" << ToString(payload_type);
|
|
}
|
|
|
|
PayloadManager::PendingPayloadHandle PayloadManager::GetPayload(
|
|
Payload::Id payload_id) const {
|
|
return pending_payloads_.GetPayload(payload_id);
|
|
}
|
|
|
|
Status PayloadManager::CancelPayload(ClientProxy* client,
|
|
Payload::Id payload_id) {
|
|
PendingPayloadHandle canceled_payload = GetPayload(payload_id);
|
|
if (!canceled_payload) {
|
|
VLOG(1) << "Client requested cancel for unknown payload_id=" << payload_id
|
|
<< ", ignoring.";
|
|
return {Status::kPayloadUnknown};
|
|
}
|
|
|
|
// Mark the payload as canceled.
|
|
canceled_payload->MarkLocallyCanceled();
|
|
VLOG(1) << "Cancelling "
|
|
<< (canceled_payload->IsIncoming() ? "incoming" : "outgoing")
|
|
<< " payload_id=" << payload_id << " at request of client.";
|
|
|
|
// Return SUCCESS immediately. Remaining cleanup and updates will be sent
|
|
// in SendPayload() or OnIncomingFrame()
|
|
return {Status::kSuccess};
|
|
}
|
|
|
|
// @EndpointManagerDataPool
|
|
void PayloadManager::OnIncomingFrame(OfflineFrame& offline_frame,
|
|
const std::string& from_endpoint_id,
|
|
ClientProxy* to_client,
|
|
Medium current_medium,
|
|
PacketMetaData& packet_meta_data) {
|
|
PayloadTransferFrame& frame =
|
|
*offline_frame.mutable_v1()->mutable_payload_transfer();
|
|
|
|
// Block any payload before the connection been accepted by both sides
|
|
// to prevent unauthorized transfer.
|
|
if (!to_client->IsConnectedToEndpoint(from_endpoint_id)) {
|
|
if (frame.packet_type() == PayloadTransferFrame::DATA) {
|
|
PendingPayloadHandle pending_payload =
|
|
pending_payloads_.GetPayload(frame.payload_header().id());
|
|
bool is_last = IsLastChunk(frame.payload_chunk());
|
|
// If payload need to be ack'd receiving, then send back the ACK frame.
|
|
if (pending_payload && is_last &&
|
|
IsPayloadReceivedAckEnabled(to_client, from_endpoint_id,
|
|
*pending_payload)) {
|
|
SendPayloadReceivedAck(to_client, *pending_payload, from_endpoint_id,
|
|
is_last);
|
|
}
|
|
}
|
|
VLOG(1) << "PayloadManager skipped process payloads before PCP connected, "
|
|
<< frame.payload_header().id();
|
|
return;
|
|
}
|
|
|
|
switch (frame.packet_type()) {
|
|
case PayloadTransferFrame::CONTROL:
|
|
VLOG(1) << "PayloadManager::OnIncomingFrame [CONTROL]: self=" << this
|
|
<< "; endpoint_id=" << from_endpoint_id;
|
|
ProcessControlPacket(to_client, from_endpoint_id, frame);
|
|
break;
|
|
case PayloadTransferFrame::DATA:
|
|
ProcessDataPacket(to_client, from_endpoint_id, frame, current_medium,
|
|
packet_meta_data);
|
|
break;
|
|
case PayloadTransferFrame::PAYLOAD_ACK:
|
|
VLOG(1) << "[safe-to-disconnect][PAYLOAD_RECEIVED_ACK] sender "
|
|
"received payload ack from "
|
|
<< from_endpoint_id;
|
|
ProcessPayloadAckPacket(from_endpoint_id, frame);
|
|
break;
|
|
default:
|
|
LOG(WARNING) << "PayloadManager: invalid frame; remote endpoint: self="
|
|
<< this << "; endpoint_id=" << from_endpoint_id;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void PayloadManager::OnEndpointDisconnect(ClientProxy* client,
|
|
const std::string& service_id,
|
|
const std::string& endpoint_id,
|
|
CountDownLatch barrier,
|
|
DisconnectionReason reason) {
|
|
if (shutdown_.Get()) {
|
|
barrier.CountDown();
|
|
return;
|
|
}
|
|
RunOnStatusUpdateThread(
|
|
"payload-manager-on-disconnect",
|
|
[this, client, endpoint_id, barrier,
|
|
reason]() RUN_ON_PAYLOAD_STATUS_UPDATE_THREAD() mutable {
|
|
// Iterate through all our payloads and look for payloads associated
|
|
// with this endpoint.
|
|
MutexLock lock(&mutex_);
|
|
pending_payloads_.ForEachPayload([&](PendingPayload* pending_payload) {
|
|
auto endpoint_info = pending_payload->GetEndpoint(endpoint_id);
|
|
if (!endpoint_info) return;
|
|
std::int64_t endpoint_offset = endpoint_info->offset;
|
|
// Stop tracking the endpoint for this payload.
|
|
pending_payload->RemoveEndpoints({endpoint_id});
|
|
// |endpoint_info| is longer valid after calling
|
|
// RemoveEndpoints.
|
|
endpoint_info = nullptr;
|
|
|
|
std::int64_t payload_total_size =
|
|
pending_payload->GetInternalPayload()->GetTotalSize();
|
|
|
|
// If no endpoints are left for this payload, close it.
|
|
if (pending_payload->GetEndpoints().empty()) {
|
|
pending_payload->Close();
|
|
}
|
|
// Create the payload transfer update.
|
|
PayloadProgressInfo update{pending_payload->GetId(),
|
|
PayloadProgressInfo::Status::kFailure,
|
|
payload_total_size, endpoint_offset};
|
|
|
|
// Send a client notification of a payload transfer failure.
|
|
client->OnPayloadProgress(endpoint_id, update);
|
|
|
|
PayloadStatus payload_status;
|
|
OperationResultCode operation_result_code;
|
|
switch (reason) {
|
|
case DisconnectionReason::LOCAL_DISCONNECTION:
|
|
payload_status = PayloadStatus::LOCAL_CLIENT_DISCONNECTION;
|
|
operation_result_code =
|
|
OperationResultCode::CLIENT_CANCELLATION_LOCAL_DISCONNECT;
|
|
break;
|
|
case DisconnectionReason::REMOTE_DISCONNECTION:
|
|
payload_status = PayloadStatus::REMOTE_CLIENT_DISCONNECTION;
|
|
operation_result_code =
|
|
OperationResultCode::CLIENT_CANCELLATION_REMOTE_DISCONNECT;
|
|
break;
|
|
case DisconnectionReason::IO_ERROR:
|
|
default:
|
|
payload_status = PayloadStatus::ENDPOINT_IO_ERROR;
|
|
operation_result_code =
|
|
client->GetAnalyticsRecorder()
|
|
.GetChannelIoErrorResultCodeFromMedium(
|
|
client->GetConnectedMedium(endpoint_id));
|
|
break;
|
|
}
|
|
|
|
if (pending_payload->IsIncoming()) {
|
|
client->GetAnalyticsRecorder().OnIncomingPayloadDone(
|
|
endpoint_id, pending_payload->GetId(), payload_status,
|
|
operation_result_code);
|
|
} else {
|
|
client->GetAnalyticsRecorder().OnOutgoingPayloadDone(
|
|
endpoint_id, pending_payload->GetId(), payload_status,
|
|
operation_result_code);
|
|
}
|
|
});
|
|
|
|
barrier.CountDown();
|
|
});
|
|
}
|
|
|
|
PayloadStatus PayloadManager::EndpointInfoStatusToPayloadStatus(
|
|
EndpointInfo::Status status) {
|
|
switch (status) {
|
|
case EndpointInfo::Status::kCanceled:
|
|
return PayloadStatus::REMOTE_CANCELLATION;
|
|
case EndpointInfo::Status::kError:
|
|
return PayloadStatus::REMOTE_ERROR;
|
|
case EndpointInfo::Status::kAvailable:
|
|
return PayloadStatus::SUCCESS;
|
|
default:
|
|
VLOG(1) << "PayloadManager: Unknown PayloadStatus";
|
|
return PayloadStatus::UNKNOWN_PAYLOAD_STATUS;
|
|
}
|
|
}
|
|
|
|
OperationResultCode PayloadManager::EndpointInfoStatusToOperationResultCode(
|
|
EndpointInfo::Status status) {
|
|
switch (status) {
|
|
case EndpointInfo::Status::kCanceled:
|
|
return OperationResultCode::CLIENT_CANCELLATION_REMOTE_IN_CANCELED_STATE;
|
|
case EndpointInfo::Status::kError:
|
|
return OperationResultCode::NEARBY_GENERIC_REMOTE_ENDPOINT_STATUS_ERROR;
|
|
case EndpointInfo::Status::kAvailable:
|
|
return OperationResultCode::DETAIL_SUCCESS;
|
|
default:
|
|
VLOG(1) << "PayloadManager: Unknown PayloadStatus";
|
|
return OperationResultCode::DETAIL_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
PayloadStatus PayloadManager::ControlMessageEventToPayloadStatus(
|
|
PayloadTransferFrame::ControlMessage::EventType event) {
|
|
switch (event) {
|
|
case PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR:
|
|
return PayloadStatus::REMOTE_ERROR;
|
|
case PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED:
|
|
return PayloadStatus::REMOTE_CANCELLATION;
|
|
default:
|
|
VLOG(1) << "PayloadManager: unknown event=" << event;
|
|
return PayloadStatus::UNKNOWN_PAYLOAD_STATUS;
|
|
}
|
|
}
|
|
|
|
OperationResultCode PayloadManager::ControlMessageEventToOperationResultCode(
|
|
PayloadTransferFrame::ControlMessage::EventType event) {
|
|
switch (event) {
|
|
case PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR:
|
|
return OperationResultCode::NEARBY_GENERIC_REMOTE_REPORT_PAYLOADS_ERROR;
|
|
case PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED:
|
|
return OperationResultCode::CLIENT_CANCELLATION_REMOTE_CANCEL_PAYLOAD;
|
|
default:
|
|
VLOG(1) << "PayloadManager: unknown event=" << event;
|
|
return OperationResultCode::DETAIL_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
PayloadProgressInfo::Status PayloadManager::PayloadStatusToTransferUpdateStatus(
|
|
PayloadStatus status) {
|
|
switch (status) {
|
|
case PayloadStatus::LOCAL_CANCELLATION:
|
|
case PayloadStatus::REMOTE_CANCELLATION:
|
|
return PayloadProgressInfo::Status::kCanceled;
|
|
case PayloadStatus::SUCCESS:
|
|
return PayloadProgressInfo::Status::kSuccess;
|
|
default:
|
|
return PayloadProgressInfo::Status::kFailure;
|
|
}
|
|
}
|
|
|
|
SingleThreadExecutor* PayloadManager::GetOutgoingPayloadExecutor(
|
|
PayloadType payload_type) {
|
|
switch (payload_type) {
|
|
case PayloadType::kBytes:
|
|
return &bytes_payload_executor_;
|
|
case PayloadType::kFile:
|
|
return &file_payload_executor_;
|
|
case PayloadType::kStream:
|
|
return &stream_payload_executor_;
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
int PayloadManager::GetOptimalChunkSize(EndpointIds endpoint_ids) {
|
|
int minChunkSize = std::numeric_limits<int>::max();
|
|
for (const auto& endpoint_id : endpoint_ids) {
|
|
minChunkSize = std::min(
|
|
minChunkSize, endpoint_manager_->GetMaxTransmitPacketSize(endpoint_id));
|
|
}
|
|
return minChunkSize;
|
|
}
|
|
|
|
PayloadTransferFrame::PayloadHeader PayloadManager::CreatePayloadHeader(
|
|
const InternalPayload& internal_payload, size_t offset) {
|
|
PayloadTransferFrame::PayloadHeader payload_header;
|
|
size_t payload_size = internal_payload.GetTotalSize();
|
|
|
|
payload_header.set_id(internal_payload.GetId());
|
|
payload_header.set_type(internal_payload.GetType());
|
|
if (internal_payload.GetType() ==
|
|
nearby::connections::PayloadTransferFrame::PayloadTransferFrame::
|
|
PayloadHeader::FILE) {
|
|
payload_header.set_file_name(internal_payload.GetFileName());
|
|
payload_header.set_parent_folder(internal_payload.GetParentFolder());
|
|
payload_header.set_last_modified_timestamp_millis(
|
|
absl::ToUnixMillis(internal_payload.GetLastModifiedTime()));
|
|
}
|
|
payload_header.set_total_size(payload_size ==
|
|
InternalPayload::kIndeterminateSize
|
|
? InternalPayload::kIndeterminateSize
|
|
: payload_size - offset);
|
|
return payload_header;
|
|
}
|
|
|
|
PayloadTransferFrame::PayloadChunk PayloadManager::CreatePayloadChunk(
|
|
std::int64_t payload_chunk_offset, ByteArray payload_chunk_body,
|
|
int index) {
|
|
PayloadTransferFrame::PayloadChunk payload_chunk;
|
|
|
|
payload_chunk.set_offset(payload_chunk_offset);
|
|
payload_chunk.set_flags(0);
|
|
if (!payload_chunk_body.Empty()) {
|
|
payload_chunk.set_body(std::string(std::move(payload_chunk_body)));
|
|
} else {
|
|
payload_chunk.set_flags(payload_chunk.flags() |
|
|
PayloadTransferFrame::PayloadChunk::LAST_CHUNK);
|
|
}
|
|
payload_chunk.set_index(index);
|
|
|
|
return payload_chunk;
|
|
}
|
|
|
|
ErrorOr<PayloadManager::PendingPayloadHandle>
|
|
PayloadManager::CreateIncomingPayload(const PayloadTransferFrame& frame,
|
|
const std::string& endpoint_id,
|
|
const std::string& save_path) {
|
|
ErrorOr<std::unique_ptr<InternalPayload>> result =
|
|
CreateIncomingInternalPayload(frame, save_path.empty()
|
|
? custom_save_path_
|
|
: save_path);
|
|
if (result.has_error()) {
|
|
return {result.error()};
|
|
}
|
|
std::unique_ptr<InternalPayload> internal_payload = std::move(result.value());
|
|
Payload::Id payload_id = internal_payload->GetId();
|
|
VLOG(1) << "CreateIncomingPayload: payload_id=" << payload_id;
|
|
pending_payloads_.StartTrackingPayload(
|
|
payload_id,
|
|
std::make_unique<PendingPayload>(
|
|
std::move(internal_payload), EndpointIds{endpoint_id}, true,
|
|
absl::bind_front(&PayloadManager::OnPendingPayloadDestroy, this)));
|
|
return {pending_payloads_.GetPayload(payload_id)};
|
|
}
|
|
|
|
void PayloadManager::OnPendingPayloadDestroy(const PendingPayload* payload) {
|
|
VLOG(1) << "PayloadManager: destroying " << payload->ToString()
|
|
<< " self=" << this;
|
|
ThroughputRecorderContainer::GetInstance().StopTPRecorder(
|
|
payload->GetId(), payload->IsIncoming()
|
|
? PayloadDirection::INCOMING_PAYLOAD
|
|
: PayloadDirection::OUTGOING_PAYLOAD);
|
|
if (payload->IsIncoming()) return;
|
|
RunOnStatusUpdateThread(
|
|
"~PendingPayload",
|
|
[this]() RUN_ON_PAYLOAD_STATUS_UPDATE_THREAD() { NotifyShutdown(); });
|
|
}
|
|
|
|
void PayloadManager::SendClientCallbacksForFinishedOutgoingPayload(
|
|
ClientProxy* client, const EndpointIds& finished_endpoint_ids,
|
|
const PayloadTransferFrame::PayloadHeader& payload_header,
|
|
std::int64_t num_bytes_successfully_transferred, PayloadStatus status,
|
|
OperationResultCode operation_result_code) {
|
|
RunOnStatusUpdateThread(
|
|
"outgoing-payload-callbacks",
|
|
[this, client, finished_endpoint_ids, payload_header,
|
|
num_bytes_successfully_transferred, status,
|
|
operation_result_code]() RUN_ON_PAYLOAD_STATUS_UPDATE_THREAD() {
|
|
// Make sure we're still tracking this payload.
|
|
PendingPayloadHandle pending_payload = GetPayload(payload_header.id());
|
|
if (!pending_payload) {
|
|
return;
|
|
}
|
|
|
|
PayloadProgressInfo update{
|
|
payload_header.id(),
|
|
PayloadManager::PayloadStatusToTransferUpdateStatus(status),
|
|
payload_header.total_size(), num_bytes_successfully_transferred};
|
|
for (const auto& endpoint_id : finished_endpoint_ids) {
|
|
// Skip sending notifications if we have stopped tracking this
|
|
// endpoint.
|
|
if (!pending_payload->GetEndpoint(endpoint_id)) {
|
|
continue;
|
|
}
|
|
|
|
// Notify the client.
|
|
client->OnPayloadProgress(endpoint_id, update);
|
|
|
|
// Mark this payload as done for analytics.
|
|
client->GetAnalyticsRecorder().OnOutgoingPayloadDone(
|
|
endpoint_id, payload_header.id(), status,
|
|
(operation_result_code == OperationResultCode::DETAIL_UNKNOWN &&
|
|
status == PayloadStatus::ENDPOINT_IO_ERROR)
|
|
? client->GetAnalyticsRecorder()
|
|
.GetChannelIoErrorResultCodeFromMedium(
|
|
client->GetConnectedMedium(endpoint_id))
|
|
: operation_result_code);
|
|
}
|
|
|
|
// Remove these endpoints from our tracking list for this payload.
|
|
pending_payload->RemoveEndpoints(finished_endpoint_ids);
|
|
|
|
// Close the payload if no endpoints remain.
|
|
if (pending_payload->GetEndpoints().empty()) {
|
|
pending_payload->Close();
|
|
}
|
|
});
|
|
}
|
|
|
|
void PayloadManager::SendClientCallbacksForFinishedIncomingPayload(
|
|
ClientProxy* client, const std::string& endpoint_id,
|
|
const PayloadTransferFrame::PayloadHeader& payload_header,
|
|
std::int64_t offset_bytes, PayloadStatus status,
|
|
OperationResultCode operation_result_code) {
|
|
RunOnStatusUpdateThread(
|
|
"incoming-payload-callbacks",
|
|
[this, client, endpoint_id, payload_header, offset_bytes, status,
|
|
operation_result_code]() RUN_ON_PAYLOAD_STATUS_UPDATE_THREAD() {
|
|
// Make sure we're still tracking this payload.
|
|
PendingPayloadHandle pending_payload = GetPayload(payload_header.id());
|
|
if (!pending_payload) {
|
|
return;
|
|
}
|
|
|
|
// Unless we never started tracking this payload (meaning we
|
|
// failed to even create the InternalPayload), notify the client
|
|
// (and close it).
|
|
PayloadProgressInfo update{
|
|
payload_header.id(),
|
|
PayloadManager::PayloadStatusToTransferUpdateStatus(status),
|
|
payload_header.total_size(), offset_bytes};
|
|
NotifyClientOfIncomingPayloadProgressInfo(client, endpoint_id, update);
|
|
DestroyPendingPayload(payload_header.id());
|
|
|
|
// Analyze
|
|
client->GetAnalyticsRecorder().OnIncomingPayloadDone(
|
|
endpoint_id, payload_header.id(), status, operation_result_code);
|
|
});
|
|
}
|
|
|
|
void PayloadManager::SendControlMessage(
|
|
const EndpointIds& endpoint_ids,
|
|
const PayloadTransferFrame::PayloadHeader& payload_header,
|
|
std::int64_t num_bytes_successfully_transferred,
|
|
PayloadTransferFrame::ControlMessage::EventType event_type) {
|
|
PayloadTransferFrame::ControlMessage control_message;
|
|
control_message.set_event(event_type);
|
|
control_message.set_offset(num_bytes_successfully_transferred);
|
|
|
|
endpoint_manager_->SendControlMessage(payload_header, control_message,
|
|
endpoint_ids);
|
|
}
|
|
|
|
void PayloadManager::SendPayloadReceivedAck(ClientProxy* client,
|
|
PendingPayload& pending_payload,
|
|
const std::string& endpoint_id,
|
|
bool is_last_chunk) {
|
|
if (!is_last_chunk ||
|
|
!IsPayloadReceivedAckEnabled(client, endpoint_id, pending_payload)) {
|
|
return;
|
|
}
|
|
|
|
send_payload_ack_executor_.Execute(
|
|
"send_payload_ack", [this, &pending_payload, endpoint_id]() {
|
|
endpoint_manager_->SendPayloadAck(pending_payload.GetId(),
|
|
{endpoint_id});
|
|
VLOG(1) << "[safe-to-disconnect] Send "
|
|
"PAYLOAD_RECEIVED_ACK frame to: "
|
|
<< endpoint_id << " done";
|
|
});
|
|
// Send the PAYLOAD_RECEIVED_ACK to the remote endpoint for the sender asap.
|
|
VLOG(1) << "[safe-to-disconnect] " << pending_payload.GetId()
|
|
<< " isLastChunk, receiver send ack to " << endpoint_id;
|
|
}
|
|
|
|
bool PayloadManager::WaitForReceivedAck(
|
|
ClientProxy* client, const std::string& endpoint_id,
|
|
PendingPayload& pending_payload,
|
|
const PayloadTransferFrame::PayloadHeader& payload_header,
|
|
std::int64_t payload_chunk_offset, bool is_last_chunk) {
|
|
if (!is_last_chunk ||
|
|
!IsPayloadReceivedAckEnabled(client, endpoint_id, pending_payload)) {
|
|
return true;
|
|
}
|
|
|
|
VLOG(1) << "[safe-to-disconnect] Last Chunk, sender wait for "
|
|
"PAYLOAD_RECEIVED_ACK frame from: "
|
|
<< endpoint_id;
|
|
while (true) {
|
|
PendingPayloadHandle latest_pending_payload =
|
|
GetPayload(payload_header.id());
|
|
// Make sure we're still tracking this payload and its associated endpoint.
|
|
if (!latest_pending_payload) {
|
|
VLOG(1) << "[safe-to-disconnect] short-circuiting "
|
|
"latest_pending_payload is null for "
|
|
<< payload_header.id() << ", stop wait ack.";
|
|
return false;
|
|
}
|
|
|
|
auto* endpoint_info = latest_pending_payload->GetEndpoint(endpoint_id);
|
|
if (endpoint_info == nullptr) {
|
|
VLOG(1) << "[safe-to-disconnect] short-circuiting "
|
|
"endpointInfo is null for "
|
|
<< payload_header.id() << ", stop wait ack.";
|
|
return false;
|
|
}
|
|
|
|
// Local payload cancellation
|
|
if (latest_pending_payload->IsLocallyCanceled()) {
|
|
HandleFinishedOutgoingPayload(
|
|
client, {endpoint_id}, payload_header, payload_chunk_offset,
|
|
OperationResultCode::CLIENT_CANCELLATION_LOCAL_CANCEL_PAYLOAD,
|
|
PayloadStatus::LOCAL_CANCELLATION);
|
|
VLOG(1) << "[safe-to-disconnect] short-circuiting local "
|
|
"payload cancellation for "
|
|
<< payload_header.id() << ", stop wait ack.";
|
|
return false;
|
|
}
|
|
// Remote payload cancellation, etc
|
|
if (!endpoint_info->IsEndpointAvailable(client,
|
|
endpoint_info->status.Get())) {
|
|
HandleFinishedOutgoingPayload(
|
|
client, {endpoint_id}, payload_header, payload_chunk_offset,
|
|
OperationResultCode::CLIENT_CANCELLATION_REMOTE_CANCEL_PAYLOAD,
|
|
EndpointInfoStatusToPayloadStatus(endpoint_info->status.Get()));
|
|
VLOG(1) << "[safe-to-disconnect] short-circuiting remote "
|
|
"payload cancellation for "
|
|
<< payload_header.id() << ", stop wait ack.";
|
|
return false;
|
|
}
|
|
{
|
|
MutexLock lock(&endpoint_info->payload_received_ack_mutex);
|
|
if (endpoint_info->is_payload_received_ack) {
|
|
VLOG(1) << "[safe-to-disconnect][PAYLOAD_RECEIVED_ACK] sender already"
|
|
" received payload ack from "
|
|
<< endpoint_id << ", stop wait PAYLOAD_RECEIVED_ACK.";
|
|
endpoint_info->is_payload_received_ack = false;
|
|
return true;
|
|
}
|
|
Exception wait_exception = endpoint_info->payload_received_ack_cond.Wait(
|
|
FeatureFlags::GetInstance()
|
|
.GetFlags()
|
|
.wait_payload_received_ack_millis);
|
|
if (!wait_exception.Ok()) {
|
|
endpoint_info->is_payload_received_ack = false;
|
|
VLOG(1) << "[safe-to-disconnect][PAYLOAD_RECEIVED_ACK] sender wait for "
|
|
"received payload ack from "
|
|
<< endpoint_id
|
|
<< " end with exception: " << wait_exception.value;
|
|
return false;
|
|
}
|
|
if (endpoint_info->is_payload_received_ack) {
|
|
VLOG(1) << "[safe-to-disconnect][PAYLOAD_RECEIVED_ACK] Received "
|
|
"notification that sender "
|
|
"received payload ack from "
|
|
<< endpoint_id;
|
|
endpoint_info->is_payload_received_ack = false;
|
|
return true;
|
|
} else {
|
|
VLOG(1) << "[safe-to-disconnect][PAYLOAD_RECEIVED_ACK] sender doesn't"
|
|
" received payload ack from "
|
|
<< endpoint_id << ", end with timeout.";
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool PayloadManager::IsPayloadReceivedAckEnabled(
|
|
ClientProxy* client, const std::string& endpoint_id,
|
|
PendingPayload& pending_payload) {
|
|
return NearbyFlags::GetInstance().GetBoolFlag(
|
|
config_package_nearby::nearby_connections_feature::
|
|
kEnablePayloadReceivedAck) &&
|
|
client->IsPayloadReceivedAckEnabled(endpoint_id) &&
|
|
(pending_payload.GetInternalPayload()->GetType() !=
|
|
nearby::connections::PayloadTransferFrame::PayloadTransferFrame::
|
|
PayloadHeader::BYTES);
|
|
}
|
|
|
|
void PayloadManager::HandleFinishedOutgoingPayload(
|
|
ClientProxy* client, const EndpointIds& finished_endpoint_ids,
|
|
const PayloadTransferFrame::PayloadHeader& payload_header,
|
|
std::int64_t num_bytes_successfully_transferred,
|
|
OperationResultCode operation_result_code, PayloadStatus status) {
|
|
// This call will destroy a pending payload.
|
|
SendClientCallbacksForFinishedOutgoingPayload(
|
|
client, finished_endpoint_ids, payload_header,
|
|
num_bytes_successfully_transferred, status, operation_result_code);
|
|
|
|
switch (status) {
|
|
case PayloadStatus::LOCAL_ERROR:
|
|
SendControlMessage(finished_endpoint_ids, payload_header,
|
|
num_bytes_successfully_transferred,
|
|
PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR);
|
|
break;
|
|
case PayloadStatus::LOCAL_CANCELLATION:
|
|
VLOG(1) << "Sending PAYLOAD_CANCEL to receiver side; payload_id="
|
|
<< payload_header.id();
|
|
SendControlMessage(
|
|
finished_endpoint_ids, payload_header,
|
|
num_bytes_successfully_transferred,
|
|
PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED);
|
|
break;
|
|
case PayloadStatus::ENDPOINT_IO_ERROR:
|
|
// Unregister these endpoints, since we had an IO error on the physical
|
|
// connection.
|
|
for (const auto& endpoint_id : finished_endpoint_ids) {
|
|
endpoint_manager_->DiscardEndpoint(client, endpoint_id,
|
|
DisconnectionReason::IO_ERROR);
|
|
}
|
|
break;
|
|
case PayloadStatus::REMOTE_ERROR:
|
|
case PayloadStatus::REMOTE_CANCELLATION:
|
|
// No special handling needed for these.
|
|
break;
|
|
default:
|
|
VLOG(1) << "PayloadManager: Unhandled finished outgoing payload with "
|
|
"payload_status="
|
|
<< status;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void PayloadManager::HandleFinishedIncomingPayload(
|
|
ClientProxy* client, const std::string& endpoint_id,
|
|
const PayloadTransferFrame::PayloadHeader& payload_header,
|
|
std::int64_t offset_bytes, PayloadStatus status,
|
|
OperationResultCode operation_result_code) {
|
|
SendClientCallbacksForFinishedIncomingPayload(client, endpoint_id,
|
|
payload_header, offset_bytes,
|
|
status, operation_result_code);
|
|
|
|
switch (status) {
|
|
case PayloadStatus::LOCAL_ERROR:
|
|
SendControlMessage({endpoint_id}, payload_header, offset_bytes,
|
|
PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR);
|
|
break;
|
|
case PayloadStatus::LOCAL_CANCELLATION:
|
|
SendControlMessage(
|
|
{endpoint_id}, payload_header, offset_bytes,
|
|
PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED);
|
|
break;
|
|
default:
|
|
VLOG(1) << "Unhandled finished incoming payload_id="
|
|
<< payload_header.id() << " with payload_status=" << status;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void PayloadManager::HandleSuccessfulOutgoingChunk(
|
|
ClientProxy* client, const std::string& endpoint_id,
|
|
const PayloadTransferFrame::PayloadHeader& payload_header,
|
|
std::int32_t payload_chunk_flags, std::int64_t payload_chunk_offset,
|
|
std::int64_t payload_chunk_body_size) {
|
|
if (NearbyFlags::GetInstance().GetBoolFlag(
|
|
config_package_nearby::nearby_connections_feature::
|
|
kEnablePayloadManagerToSkipChunkUpdate)) {
|
|
MutexLock lock(&chunk_update_mutex_);
|
|
++outgoing_chunk_update_count_;
|
|
}
|
|
RunOnStatusUpdateThread(
|
|
"outgoing-chunk-success",
|
|
[this, client, endpoint_id, payload_header, payload_chunk_flags,
|
|
payload_chunk_offset,
|
|
payload_chunk_body_size]() RUN_ON_PAYLOAD_STATUS_UPDATE_THREAD() {
|
|
// Make sure we're still tracking this payload and its associated
|
|
// endpoint.
|
|
bool is_last_chunk =
|
|
(payload_chunk_flags &
|
|
PayloadTransferFrame::PayloadChunk::LAST_CHUNK) != 0;
|
|
|
|
if (NearbyFlags::GetInstance().GetBoolFlag(
|
|
config_package_nearby::nearby_connections_feature::
|
|
kEnablePayloadManagerToSkipChunkUpdate)) {
|
|
MutexLock lock(&chunk_update_mutex_);
|
|
--outgoing_chunk_update_count_;
|
|
if (payload_header.has_type() &&
|
|
payload_header.type() ==
|
|
PayloadTransferFrame::PayloadTransferFrame::PayloadHeader::
|
|
FILE) {
|
|
if (outgoing_chunk_update_count_ > 0 && !is_last_chunk &&
|
|
payload_chunk_offset != 0) {
|
|
VLOG(1) << "Skip the outgoing chunk update with offset="
|
|
<< payload_chunk_offset;
|
|
client->GetAnalyticsRecorder().OnPayloadChunkSent(
|
|
endpoint_id, payload_header.id(), payload_chunk_body_size);
|
|
return;
|
|
}
|
|
|
|
absl::Time current_time = SystemClock::ElapsedRealtime();
|
|
if (!is_last_chunk && payload_chunk_offset != 0 &&
|
|
current_time - last_outgoing_chunk_update_time_ <
|
|
kMinTransferUpdateInterval) {
|
|
VLOG(1) << "Skip the outgoing chunk update with offset="
|
|
<< payload_chunk_offset
|
|
<< " because it's too close to the previous update.";
|
|
client->GetAnalyticsRecorder().OnPayloadChunkSent(
|
|
endpoint_id, payload_header.id(), payload_chunk_body_size);
|
|
return;
|
|
}
|
|
|
|
last_outgoing_chunk_update_time_ = current_time;
|
|
}
|
|
}
|
|
|
|
PendingPayloadHandle pending_payload = GetPayload(payload_header.id());
|
|
if (!pending_payload || !pending_payload->GetEndpoint(endpoint_id)) {
|
|
VLOG(1) << "HandleSuccessfulOutgoingChunk: endpoint not found: "
|
|
"endpoint_id="
|
|
<< endpoint_id;
|
|
return;
|
|
}
|
|
|
|
PayloadProgressInfo update{
|
|
payload_header.id(),
|
|
is_last_chunk ? PayloadProgressInfo::Status::kSuccess
|
|
: PayloadProgressInfo::Status::kInProgress,
|
|
payload_header.total_size(),
|
|
is_last_chunk ? payload_chunk_offset
|
|
: payload_chunk_offset + payload_chunk_body_size};
|
|
|
|
// Notify the client.
|
|
client->OnPayloadProgress(endpoint_id, update);
|
|
|
|
if (is_last_chunk) {
|
|
client->GetAnalyticsRecorder().OnOutgoingPayloadDone(
|
|
endpoint_id, payload_header.id(), PayloadStatus::SUCCESS,
|
|
OperationResultCode::DETAIL_SUCCESS);
|
|
|
|
// Stop tracking this endpoint.
|
|
pending_payload->RemoveEndpoints({endpoint_id});
|
|
|
|
// Close the payload if no endpoints remain.
|
|
if (pending_payload->GetEndpoints().empty()) {
|
|
pending_payload->Close();
|
|
}
|
|
} else {
|
|
client->GetAnalyticsRecorder().OnPayloadChunkSent(
|
|
endpoint_id, payload_header.id(), payload_chunk_body_size);
|
|
}
|
|
});
|
|
}
|
|
|
|
// @PayloadManagerStatusUpdateThread
|
|
void PayloadManager::DestroyPendingPayload(Payload::Id payload_id) {
|
|
pending_payloads_.StopTrackingPayload(payload_id);
|
|
}
|
|
|
|
void PayloadManager::HandleSuccessfulIncomingChunk(
|
|
ClientProxy* client, const std::string& endpoint_id,
|
|
const PayloadTransferFrame::PayloadHeader& payload_header,
|
|
std::int32_t payload_chunk_flags, std::int64_t payload_chunk_offset,
|
|
std::int64_t payload_chunk_body_size) {
|
|
if (NearbyFlags::GetInstance().GetBoolFlag(
|
|
config_package_nearby::nearby_connections_feature::
|
|
kEnablePayloadManagerToSkipChunkUpdate)) {
|
|
MutexLock lock(&chunk_update_mutex_);
|
|
++incoming_chunk_update_count_;
|
|
}
|
|
RunOnStatusUpdateThread(
|
|
"incoming-chunk-success",
|
|
[this, client, endpoint_id, payload_header, payload_chunk_flags,
|
|
payload_chunk_offset,
|
|
payload_chunk_body_size]() RUN_ON_PAYLOAD_STATUS_UPDATE_THREAD() {
|
|
// Make sure we're still tracking this payload.
|
|
bool is_last_chunk =
|
|
(payload_chunk_flags &
|
|
PayloadTransferFrame::PayloadChunk::LAST_CHUNK) != 0;
|
|
|
|
if (NearbyFlags::GetInstance().GetBoolFlag(
|
|
config_package_nearby::nearby_connections_feature::
|
|
kEnablePayloadManagerToSkipChunkUpdate)) {
|
|
MutexLock lock(&chunk_update_mutex_);
|
|
--incoming_chunk_update_count_;
|
|
if (payload_header.has_type() &&
|
|
payload_header.type() ==
|
|
PayloadTransferFrame::PayloadTransferFrame::PayloadHeader::
|
|
FILE) {
|
|
if (incoming_chunk_update_count_ > 0 && !is_last_chunk &&
|
|
payload_chunk_offset != 0) {
|
|
VLOG(1) << "Skip the incoming chunk update with offset="
|
|
<< payload_chunk_offset;
|
|
client->GetAnalyticsRecorder().OnPayloadChunkReceived(
|
|
endpoint_id, payload_header.id(), payload_chunk_body_size);
|
|
return;
|
|
}
|
|
|
|
absl::Time current_time = SystemClock::ElapsedRealtime();
|
|
if (!is_last_chunk && payload_chunk_offset != 0 &&
|
|
current_time - last_incoming_chunk_update_time_ <
|
|
kMinTransferUpdateInterval) {
|
|
VLOG(1) << "Skip the incoming chunk update with offset="
|
|
<< payload_chunk_offset
|
|
<< " because it's too close to the previous update.";
|
|
client->GetAnalyticsRecorder().OnPayloadChunkSent(
|
|
endpoint_id, payload_header.id(), payload_chunk_body_size);
|
|
return;
|
|
}
|
|
|
|
last_incoming_chunk_update_time_ = current_time;
|
|
}
|
|
}
|
|
|
|
PendingPayloadHandle pending_payload = GetPayload(payload_header.id());
|
|
if (!pending_payload) {
|
|
return;
|
|
}
|
|
|
|
PayloadProgressInfo update{
|
|
payload_header.id(),
|
|
is_last_chunk ? PayloadProgressInfo::Status::kSuccess
|
|
: PayloadProgressInfo::Status::kInProgress,
|
|
payload_header.total_size(),
|
|
is_last_chunk ? payload_chunk_offset
|
|
: payload_chunk_offset + payload_chunk_body_size};
|
|
|
|
// Notify the client of this update.
|
|
NotifyClientOfIncomingPayloadProgressInfo(client, endpoint_id, update);
|
|
|
|
// Analyze the success.
|
|
if (is_last_chunk) {
|
|
DestroyPendingPayload(payload_header.id());
|
|
client->GetAnalyticsRecorder().OnIncomingPayloadDone(
|
|
endpoint_id, payload_header.id(), PayloadStatus::SUCCESS,
|
|
OperationResultCode::DETAIL_SUCCESS);
|
|
} else {
|
|
client->GetAnalyticsRecorder().OnPayloadChunkReceived(
|
|
endpoint_id, payload_header.id(), payload_chunk_body_size);
|
|
}
|
|
});
|
|
}
|
|
|
|
// @EndpointManagerDataPool
|
|
void PayloadManager::ProcessDataPacket(
|
|
ClientProxy* to_client, const std::string& from_endpoint_id,
|
|
PayloadTransferFrame& payload_transfer_frame, Medium medium,
|
|
PacketMetaData& packet_meta_data) {
|
|
PayloadTransferFrame::PayloadHeader& payload_header =
|
|
*payload_transfer_frame.mutable_payload_header();
|
|
PayloadTransferFrame::PayloadChunk& payload_chunk =
|
|
*payload_transfer_frame.mutable_payload_chunk();
|
|
VLOG(1) << "PayloadManager got data OfflineFrame for payload_id="
|
|
<< payload_header.id() << " from endpoint_id=" << from_endpoint_id
|
|
<< " at offset " << payload_chunk.offset();
|
|
// We explicitly deny payloads with ID 0.
|
|
if (payload_header.id() == 0) {
|
|
LOG(WARNING) << "Denying payload with ID 0 for endpoint_id="
|
|
<< from_endpoint_id << ", aborting receipt.";
|
|
// Send the error to the remote endpoint.
|
|
SendControlMessage({from_endpoint_id}, payload_header,
|
|
payload_chunk.offset(),
|
|
PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR);
|
|
return;
|
|
}
|
|
Payload::Id payload_id = payload_header.id();
|
|
PendingPayloadHandle pending_payload;
|
|
if (payload_chunk.offset() == 0) {
|
|
ThroughputRecorderContainer::GetInstance()
|
|
.GetTPRecorder(payload_id, PayloadDirection::INCOMING_PAYLOAD)
|
|
->Start((PayloadType)payload_header.type(),
|
|
PayloadDirection::INCOMING_PAYLOAD);
|
|
packet_meta_data.Reset();
|
|
RunOnStatusUpdateThread(
|
|
"process-data-packet", [to_client, from_endpoint_id, payload_header,
|
|
this]() RUN_ON_PAYLOAD_STATUS_UPDATE_THREAD() {
|
|
// This is the first chunk of a new incoming
|
|
// payload. Start the analysis.
|
|
to_client->GetAnalyticsRecorder().OnIncomingPayloadStarted(
|
|
from_endpoint_id, payload_header.id(),
|
|
FramePayloadTypeToPayloadType(payload_header.type()),
|
|
payload_header.total_size());
|
|
});
|
|
|
|
ErrorOr<PendingPayloadHandle> result =
|
|
CreateIncomingPayload(payload_transfer_frame, from_endpoint_id,
|
|
to_client->GetSavePath(from_endpoint_id));
|
|
if (result.has_error()) {
|
|
LOG(WARNING) << "PayloadManager failed to create InternalPayload from "
|
|
"PayloadTransferFrame with payload_id="
|
|
<< payload_header.id() << " and type "
|
|
<< payload_header.type() << ", aborting receipt.";
|
|
|
|
// Analyticize.
|
|
OperationResultCode operation_result_code =
|
|
result.error().operation_result_code().value();
|
|
RunOnStatusUpdateThread(
|
|
"process-data-packet",
|
|
[to_client, from_endpoint_id, payload_header, operation_result_code]()
|
|
RUN_ON_PAYLOAD_STATUS_UPDATE_THREAD() {
|
|
to_client->GetAnalyticsRecorder().OnIncomingPayloadDone(
|
|
from_endpoint_id, payload_header.id(),
|
|
PayloadStatus::LOCAL_ERROR, operation_result_code);
|
|
});
|
|
|
|
// Send the error to the remote endpoint.
|
|
SendControlMessage({from_endpoint_id}, payload_header,
|
|
payload_chunk.offset(),
|
|
PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR);
|
|
return;
|
|
} else {
|
|
pending_payload = std::move(result.value());
|
|
}
|
|
// Also, let the client know of this new incoming payload.
|
|
RunOnStatusUpdateThread(
|
|
"process-data-packet",
|
|
[to_client, from_endpoint_id,
|
|
pending_payload = GetPayload(payload_id)]()
|
|
RUN_ON_PAYLOAD_STATUS_UPDATE_THREAD() {
|
|
if (!pending_payload) return;
|
|
VLOG(1) << "PayloadManager received new payload_id="
|
|
<< pending_payload->GetInternalPayload()->GetId()
|
|
<< " from endpoint_id=" << from_endpoint_id;
|
|
to_client->OnPayload(
|
|
from_endpoint_id,
|
|
pending_payload->GetInternalPayload()->ReleasePayload());
|
|
});
|
|
} else {
|
|
pending_payload = GetPayload(payload_header.id());
|
|
}
|
|
|
|
if (!pending_payload) {
|
|
LOG(WARNING) << "ProcessDataPacket: [missing] endpoint_id="
|
|
<< from_endpoint_id << "; payload_id=" << payload_header.id();
|
|
return;
|
|
}
|
|
if (pending_payload->IsLocallyCanceled()) {
|
|
// This incoming payload was canceled by the client. Drop this frame and
|
|
// do all the cleanup. See go/nc-cancel-payload
|
|
VLOG(1) << "ProcessDataPacket: [cancel] endpoint_id=" << from_endpoint_id
|
|
<< "; payload_id=" << pending_payload->GetId();
|
|
HandleFinishedIncomingPayload(
|
|
to_client, from_endpoint_id, payload_header, payload_chunk.offset(),
|
|
PayloadStatus::LOCAL_CANCELLATION,
|
|
OperationResultCode::CLIENT_CANCELLATION_LOCAL_CANCEL_PAYLOAD);
|
|
return;
|
|
}
|
|
|
|
// Update the offset for this payload. An endpoint disconnection might occur
|
|
// from another thread and we would need to know the current offset to
|
|
// report back to the client. For the sake of accuracy, we update the
|
|
// pending payload here because it's after all payload terminating events
|
|
// are handled, but right before we actually start attaching the next chunk.
|
|
pending_payload->SetOffsetForEndpoint(from_endpoint_id,
|
|
payload_chunk.offset());
|
|
|
|
// Save size of packet before we move it.
|
|
std::int64_t payload_body_size = payload_chunk.body().size();
|
|
|
|
packet_meta_data.StartFileIo();
|
|
if (pending_payload->GetInternalPayload()
|
|
->AttachNextChunk(payload_chunk.body())
|
|
.Raised()) {
|
|
LOG(ERROR) << "ProcessDataPacket: [data: error] endpoint_id="
|
|
<< from_endpoint_id
|
|
<< "; payload_id=" << pending_payload->GetId();
|
|
HandleFinishedIncomingPayload(
|
|
to_client, from_endpoint_id, payload_header, payload_chunk.offset(),
|
|
PayloadStatus::LOCAL_ERROR, OperationResultCode::IO_FILE_WRITING_ERROR);
|
|
return;
|
|
}
|
|
packet_meta_data.StopFileIo();
|
|
bool is_last_chunk = (payload_chunk.flags() &
|
|
PayloadTransferFrame::PayloadChunk::LAST_CHUNK) != 0;
|
|
SendPayloadReceivedAck(to_client, *pending_payload, from_endpoint_id,
|
|
is_last_chunk);
|
|
|
|
HandleSuccessfulIncomingChunk(to_client, from_endpoint_id, payload_header,
|
|
payload_chunk.flags(), payload_chunk.offset(),
|
|
payload_body_size);
|
|
|
|
ThroughputRecorderContainer::GetInstance()
|
|
.GetTPRecorder(payload_header.id(), PayloadDirection::INCOMING_PAYLOAD)
|
|
->OnFrameReceived(medium, packet_meta_data);
|
|
if (is_last_chunk) {
|
|
ThroughputRecorderContainer::GetInstance()
|
|
.GetTPRecorder(payload_header.id(), PayloadDirection::INCOMING_PAYLOAD)
|
|
->MarkAsSuccess();
|
|
}
|
|
}
|
|
|
|
// @EndpointManagerDataPool
|
|
void PayloadManager::ProcessControlPacket(
|
|
ClientProxy* to_client, const std::string& from_endpoint_id,
|
|
PayloadTransferFrame& payload_transfer_frame) {
|
|
const PayloadTransferFrame::PayloadHeader& payload_header =
|
|
payload_transfer_frame.payload_header();
|
|
const PayloadTransferFrame::ControlMessage& control_message =
|
|
payload_transfer_frame.control_message();
|
|
PendingPayloadHandle pending_payload = GetPayload(payload_header.id());
|
|
if (!pending_payload) {
|
|
VLOG(1) << "Got ControlMessage for unknown payload_id="
|
|
<< payload_header.id() << ", ignoring: " << control_message.event();
|
|
return;
|
|
}
|
|
|
|
switch (control_message.event()) {
|
|
case PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED:
|
|
if (pending_payload->IsIncoming()) {
|
|
VLOG(1) << "Incoming PAYLOAD_CANCELED: from endpoint_id="
|
|
<< from_endpoint_id << "; self=" << this;
|
|
// No need to mark the pending payload as cancelled, since this is a
|
|
// remote cancellation for an incoming payload -- we handle everything
|
|
// inline here.
|
|
HandleFinishedIncomingPayload(
|
|
to_client, from_endpoint_id, payload_header,
|
|
control_message.offset(),
|
|
ControlMessageEventToPayloadStatus(control_message.event()),
|
|
ControlMessageEventToOperationResultCode(control_message.event()));
|
|
} else {
|
|
VLOG(1) << "Outgoing PAYLOAD_CANCELED: from endpoint_id="
|
|
<< from_endpoint_id << "; self=" << this;
|
|
// Mark the payload as canceled *for this endpoint*.
|
|
pending_payload->SetEndpointStatusFromControlMessage(from_endpoint_id,
|
|
control_message);
|
|
}
|
|
VLOG(1) << "Marked "
|
|
<< (pending_payload->IsIncoming() ? "incoming" : "outgoing")
|
|
<< " payload_id="
|
|
<< pending_payload->GetInternalPayload()->GetId()
|
|
<< " as canceled at request of endpoint_id=" << from_endpoint_id;
|
|
break;
|
|
case PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR:
|
|
if (pending_payload->IsIncoming()) {
|
|
HandleFinishedIncomingPayload(
|
|
to_client, from_endpoint_id, payload_header,
|
|
control_message.offset(),
|
|
ControlMessageEventToPayloadStatus(control_message.event()),
|
|
ControlMessageEventToOperationResultCode(control_message.event()));
|
|
} else {
|
|
pending_payload->SetEndpointStatusFromControlMessage(from_endpoint_id,
|
|
control_message);
|
|
}
|
|
break;
|
|
default:
|
|
VLOG(1) << "Unhandled control message " << control_message.event()
|
|
<< " for payload_id="
|
|
<< pending_payload->GetInternalPayload()->GetId();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void PayloadManager::ProcessPayloadAckPacket(
|
|
const std::string& from_endpoint_id,
|
|
PayloadTransferFrame& payload_transfer_frame) {
|
|
auto payload_header = payload_transfer_frame.payload_header();
|
|
PendingPayloadHandle pending_payload = GetPayload(payload_header.id());
|
|
if (!pending_payload) {
|
|
VLOG(1) << "[safe-to-disconnect][PAYLOAD_RECEIVED_ACK] "
|
|
"short-circuiting got payload "
|
|
"ack for unknown payload "
|
|
<< payload_header.id() << ", ignoring";
|
|
return;
|
|
}
|
|
if (pending_payload->IsIncoming()) {
|
|
VLOG(1) << "[safe-to-disconnect][PAYLOAD_RECEIVED_ACK] "
|
|
"short-circuiting got Payload "
|
|
"ack for incoming payload "
|
|
<< payload_header.id() << ", ignoring";
|
|
}
|
|
VLOG(1)
|
|
<< "[safe-to-disconnect][PAYLOAD_RECEIVED_ACK] sender received payload "
|
|
<< payload_header.id() << " ack from " << from_endpoint_id;
|
|
pending_payload->MarkReceivedAckFromEndpoint(from_endpoint_id);
|
|
}
|
|
|
|
// @PayloadManagerStatusUpdateThread
|
|
void PayloadManager::NotifyClientOfIncomingPayloadProgressInfo(
|
|
ClientProxy* client, const std::string& endpoint_id,
|
|
const PayloadProgressInfo& payload_transfer_update) {
|
|
client->OnPayloadProgress(endpoint_id, payload_transfer_update);
|
|
}
|
|
|
|
void PayloadManager::RecordPayloadStartedAnalytics(
|
|
ClientProxy* client, const EndpointIds& endpoint_ids,
|
|
std::int64_t payload_id, PayloadType payload_type, std::int64_t offset,
|
|
std::int64_t total_size) {
|
|
client->GetAnalyticsRecorder().OnOutgoingPayloadStarted(
|
|
endpoint_ids, payload_id, payload_type,
|
|
total_size == -1 ? -1 : total_size - offset);
|
|
}
|
|
|
|
void PayloadManager::RecordInvalidPayloadAnalytics(
|
|
ClientProxy* client, const EndpointIds& endpoint_ids,
|
|
std::int64_t payload_id, PayloadType payload_type, std::int64_t offset,
|
|
std::int64_t total_size, OperationResultCode operation_result_code) {
|
|
RecordPayloadStartedAnalytics(client, endpoint_ids, payload_id, payload_type,
|
|
offset, total_size);
|
|
|
|
for (const auto& endpoint_id : endpoint_ids) {
|
|
client->GetAnalyticsRecorder().OnOutgoingPayloadDone(
|
|
endpoint_id, payload_id, PayloadStatus::LOCAL_ERROR,
|
|
operation_result_code);
|
|
}
|
|
}
|
|
|
|
PayloadType PayloadManager::FramePayloadTypeToPayloadType(
|
|
PayloadTransferFrame::PayloadHeader::PayloadType type) {
|
|
switch (type) {
|
|
case PayloadTransferFrame::PayloadHeader::BYTES:
|
|
return connections::PayloadType::kBytes;
|
|
case PayloadTransferFrame::PayloadHeader::FILE:
|
|
return connections::PayloadType::kFile;
|
|
case PayloadTransferFrame::PayloadHeader::STREAM:
|
|
return connections::PayloadType::kStream;
|
|
default:
|
|
return connections::PayloadType::kUnknown;
|
|
}
|
|
}
|
|
|
|
void PayloadManager::SetCustomSavePath(ClientProxy* client,
|
|
const std::string& path) {
|
|
custom_save_path_ = path;
|
|
}
|
|
|
|
///////////////////////////////// EndpointInfo
|
|
////////////////////////////////////
|
|
|
|
PayloadManager::EndpointInfo::Status
|
|
PayloadManager::EndpointInfo::ControlMessageEventToEndpointInfoStatus(
|
|
PayloadTransferFrame::ControlMessage::EventType event) {
|
|
switch (event) {
|
|
case PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR:
|
|
return Status::kError;
|
|
case PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED:
|
|
return Status::kCanceled;
|
|
default:
|
|
VLOG(1) << "Unknown EndpointInfo.Status for ControlMessage.EventType "
|
|
<< event;
|
|
return Status::kUnknown;
|
|
}
|
|
}
|
|
|
|
void PayloadManager::EndpointInfo::SetStatusFromControlMessage(
|
|
const PayloadTransferFrame::ControlMessage& control_message) {
|
|
status.Set(ControlMessageEventToEndpointInfoStatus(control_message.event()));
|
|
VLOG(1) << "Marked endpoint " << id << " with status "
|
|
<< ToString(status.Get()) << " based on OOB ControlMessage";
|
|
}
|
|
|
|
void PayloadManager::EndpointInfo::MarkReceivedAckFromEndpoint() {
|
|
MutexLock lock(&payload_received_ack_mutex);
|
|
is_payload_received_ack = true;
|
|
payload_received_ack_cond.Notify();
|
|
}
|
|
|
|
bool PayloadManager::EndpointInfo::IsEndpointAvailable(
|
|
ClientProxy* clientProxy, EndpointInfo::Status status) {
|
|
// Pending endpointIds would be removed from the payload after
|
|
// onPayloadTransferUpdate, but there is the racing problem that gets the
|
|
// available endpoints before update. Here force to remove those endpoints
|
|
// (b/227419433).
|
|
bool is_pending_endpoint = false;
|
|
if (clientProxy->HasPendingConnectionToEndpoint(id)) {
|
|
is_pending_endpoint = true;
|
|
}
|
|
return (status == EndpointInfo::Status::kAvailable) && !is_pending_endpoint;
|
|
}
|
|
|
|
//////////////////////////////// PendingPayload ////////////////////////////////
|
|
|
|
PayloadManager::PendingPayload::PendingPayload(
|
|
std::unique_ptr<InternalPayload> internal_payload,
|
|
const EndpointIds& endpoint_ids, bool is_incoming,
|
|
DestroyCallback destroy_callback)
|
|
: is_incoming_(is_incoming),
|
|
internal_payload_(std::move(internal_payload)),
|
|
destroy_callback_(std::move(destroy_callback)) {
|
|
// Initially we mark all endpoints as available.
|
|
// Later on some may become canceled, some may experience data transfer
|
|
// failures. Any of these situations will cause endpoint to be marked as
|
|
// unavailable.
|
|
for (const auto& id : endpoint_ids) {
|
|
EndpointInfo endpoint_info;
|
|
endpoint_info.id = id;
|
|
endpoint_info.status.Set(EndpointInfo::Status::kAvailable);
|
|
|
|
endpoints_.emplace(id, std::move(endpoint_info));
|
|
}
|
|
}
|
|
|
|
Payload::Id PayloadManager::PendingPayload::GetId() const {
|
|
return internal_payload_->GetId();
|
|
}
|
|
|
|
InternalPayload* PayloadManager::PendingPayload::GetInternalPayload() {
|
|
return internal_payload_.get();
|
|
}
|
|
|
|
bool PayloadManager::PendingPayload::IsLocallyCanceled() const {
|
|
return is_locally_canceled_.Get();
|
|
}
|
|
|
|
void PayloadManager::PendingPayload::MarkLocallyCanceled() {
|
|
is_locally_canceled_.Set(true);
|
|
}
|
|
|
|
void PayloadManager::PendingPayload::MarkReceivedAckFromEndpoint(
|
|
const std::string& from_endpoint_id) {
|
|
auto info = GetEndpoint(from_endpoint_id);
|
|
if (!info) return;
|
|
info->MarkReceivedAckFromEndpoint();
|
|
}
|
|
|
|
bool PayloadManager::PendingPayload::IsIncoming() const { return is_incoming_; }
|
|
|
|
std::vector<const PayloadManager::EndpointInfo*>
|
|
PayloadManager::PendingPayload::GetEndpoints() const {
|
|
MutexLock lock(&mutex_);
|
|
|
|
std::vector<const EndpointInfo*> result;
|
|
for (const auto& item : endpoints_) {
|
|
result.push_back(&item.second);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
PayloadManager::EndpointInfo* PayloadManager::PendingPayload::GetEndpoint(
|
|
const std::string& endpoint_id) {
|
|
MutexLock lock(&mutex_);
|
|
|
|
auto it = endpoints_.find(endpoint_id);
|
|
if (it == endpoints_.end()) {
|
|
return {};
|
|
}
|
|
|
|
return &it->second;
|
|
}
|
|
|
|
void PayloadManager::PendingPayload::RemoveEndpoints(
|
|
const EndpointIds& endpoint_ids) {
|
|
MutexLock lock(&mutex_);
|
|
|
|
for (const auto& id : endpoint_ids) {
|
|
endpoints_.erase(id);
|
|
}
|
|
}
|
|
|
|
void PayloadManager::PendingPayload::SetEndpointStatusFromControlMessage(
|
|
const std::string& endpoint_id,
|
|
const PayloadTransferFrame::ControlMessage& control_message) {
|
|
MutexLock lock(&mutex_);
|
|
|
|
auto item = endpoints_.find(endpoint_id);
|
|
if (item != endpoints_.end()) {
|
|
item->second.SetStatusFromControlMessage(control_message);
|
|
}
|
|
}
|
|
|
|
void PayloadManager::PendingPayload::SetOffsetForEndpoint(
|
|
const std::string& endpoint_id, std::int64_t offset) {
|
|
MutexLock lock(&mutex_);
|
|
|
|
auto item = endpoints_.find(endpoint_id);
|
|
if (item != endpoints_.end()) {
|
|
item->second.offset = offset;
|
|
}
|
|
}
|
|
|
|
void PayloadManager::PendingPayload::Close() {
|
|
bool was_closed = is_closed_.Set(true);
|
|
if (was_closed) return;
|
|
if (internal_payload_) internal_payload_->Close();
|
|
}
|
|
|
|
void PayloadManager::RunOnStatusUpdateThread(
|
|
const std::string& name, absl::AnyInvocable<void()> runnable) {
|
|
payload_status_update_executor_.Execute(name, std::move(runnable));
|
|
}
|
|
|
|
/////////////////////////////// PendingPayloads ////////////////////////////////
|
|
|
|
void PayloadManager::PendingPayloads::StartTrackingPayload(
|
|
Payload::Id payload_id, std::unique_ptr<PendingPayload> pending_payload) {
|
|
MutexLock lock(&mutex_);
|
|
|
|
// If the |payload_id| is being re-used, always prefer the newer payload.
|
|
Remove(pending_payloads_.find(payload_id));
|
|
VLOG(1) << "StartTrackingPayload: " << pending_payload->ToString();
|
|
pending_payload->IncRefCount();
|
|
pending_payloads_[payload_id] = std::move(pending_payload);
|
|
}
|
|
|
|
void PayloadManager::PendingPayloads::StopTrackingPayload(
|
|
Payload::Id payload_id) {
|
|
MutexLock lock(&mutex_);
|
|
VLOG(1) << "StopTrackingPayload " << payload_id;
|
|
Remove(pending_payloads_.find(payload_id));
|
|
}
|
|
|
|
void PayloadManager::PendingPayloads::Remove(
|
|
absl::flat_hash_map<Payload::Id, std::unique_ptr<PendingPayload>>::iterator
|
|
it) {
|
|
if (it != pending_payloads_.end()) {
|
|
int refcount = it->second->DecRefCount();
|
|
if (refcount == 0) {
|
|
// Nobody is using the payload, we can remove it.
|
|
VLOG(1) << "Erase payload " << it->second->ToString();
|
|
pending_payloads_.erase(it);
|
|
} else {
|
|
// Someone is still using the payload. Move it to the garbage bin. The
|
|
// payload will be removed when they release it.
|
|
VLOG(1) << "Bin payload " << it->second->ToString();
|
|
payload_garbage_bin_.push_back(
|
|
std::move(pending_payloads_.extract(it).mapped()));
|
|
}
|
|
}
|
|
}
|
|
|
|
PayloadManager::PendingPayloadHandle
|
|
PayloadManager::PendingPayloads::GetPayload(Payload::Id payload_id) const {
|
|
MutexLock lock(&mutex_);
|
|
|
|
auto item = pending_payloads_.find(payload_id);
|
|
if (item == pending_payloads_.end()) {
|
|
return PendingPayloadHandle();
|
|
}
|
|
PendingPayload* payload = item->second.get();
|
|
payload->IncRefCount();
|
|
return PendingPayloadHandle(
|
|
payload, absl::bind_front(&PendingPayloads::Release,
|
|
const_cast<PendingPayloads*>(this)));
|
|
}
|
|
|
|
void PayloadManager::PendingPayloads::StopTrackingAllPayloads() {
|
|
MutexLock lock(&mutex_);
|
|
|
|
for (auto it = pending_payloads_.begin(); it != pending_payloads_.end();) {
|
|
Remove(it++);
|
|
}
|
|
}
|
|
|
|
void PayloadManager::PendingPayloads::ForEachPayload(
|
|
absl::AnyInvocable<void(PendingPayload*)> callback) {
|
|
MutexLock lock(&mutex_);
|
|
|
|
for (const auto& item : pending_payloads_) {
|
|
callback(item.second.get());
|
|
}
|
|
}
|
|
|
|
void PayloadManager::PendingPayloads::Release(PendingPayload* payload) {
|
|
// Called when `PendingPayloadHandle` is destroyed.
|
|
MutexLock lock(&mutex_);
|
|
VLOG(1) << __func__ << " " << payload->ToString();
|
|
auto it = pending_payloads_.find(payload->GetId());
|
|
if (it != pending_payloads_.end() && it->second.get() == payload) {
|
|
// The payload is still tracked.
|
|
payload->DecRefCount();
|
|
return;
|
|
}
|
|
auto bin_it =
|
|
std::find_if(payload_garbage_bin_.begin(), payload_garbage_bin_.end(),
|
|
[payload](auto& item) { return item.get() == payload; });
|
|
if (bin_it != payload_garbage_bin_.end()) {
|
|
int refcount = payload->DecRefCount();
|
|
if (refcount == 0) {
|
|
// The payload is not tracked and it was the last reference.
|
|
payload_garbage_bin_.erase(bin_it);
|
|
}
|
|
}
|
|
}
|
|
|
|
PayloadManager::PendingPayloadHandle::PendingPayloadHandle(
|
|
PendingPayload* payload, DestroyCallback destroy_callback)
|
|
: payload_(payload), destroy_callback_(std::move(destroy_callback)) {}
|
|
|
|
PayloadManager::PendingPayloadHandle::~PendingPayloadHandle() {
|
|
if (destroy_callback_) {
|
|
std::move(destroy_callback_)(payload_);
|
|
}
|
|
}
|
|
|
|
std::string PayloadManager::PendingPayload::ToString() const {
|
|
return absl::StrFormat("Payload(%s, %d)",
|
|
IsIncoming() ? "incoming" : "outgoing", GetId());
|
|
}
|
|
|
|
} // namespace connections
|
|
} // namespace nearby
|