#include "core/internal/payload_manager.h" #include #include #include "platform/synchronized.h" namespace location { namespace nearby { namespace connections { namespace payload_manager { template void eraseOwnedPtrFromMap(std::map >& m, const K& k) { typename std::map >::iterator it = m.find(k); if (it != m.end()) { it->second.destroy(); m.erase(it); } } template class SendPayloadRunnable : public Runnable { public: SendPayloadRunnable(Ptr > payload_manager, Ptr > client_proxy, const std::vector& endpoint_ids, ConstPtr payload) : payload_manager_(payload_manager), client_proxy_(client_proxy), endpoint_ids_(endpoint_ids), payload_(payload) {} void run() override { // If successfully created, pending_payload is owned by // PayloadManager::pending_payloads_ until // PayloadManager::PendingPayloads::stopTrackingPayload() is invoked. Ptr::PendingPayload> pending_payload( createOutgoingPayload(payload_.release(), endpoint_ids_)); if (pending_payload.isNull()) { // TODO(tracyzhou): Add logging. return; } ScopedPtr > payload_header( payload_manager_->createPayloadHeader( ConstifyPtr(pending_payload->getInternalPayload()))); payload_manager_->send_payload_loop_runner_->loop( MakePtr(new LoopCallable(payload_manager_, client_proxy_, pending_payload, payload_header.get()))); } private: class LoopCallable : public Callable { public: LoopCallable( Ptr > payload_manager, Ptr > client_proxy, Ptr::PendingPayload> pending_payload, ConstPtr payload_header) : next_chunk_offset_(0), payload_manager_(payload_manager), client_proxy_(client_proxy), pending_payload_(pending_payload), payload_header_(payload_header) {} ExceptionOr call() override { AvailableAndUnavailableEndpoints available_and_unavailable_endpoints = getAvailableAndUnavailableEndpoints(ConstifyPtr(pending_payload_)); const UnavailableEndpoints& unavailable_endpoints = available_and_unavailable_endpoints.second; // First, handle any non-available endpoints. for (typename UnavailableEndpoints::const_iterator it = unavailable_endpoints.begin(); it != unavailable_endpoints.end(); it++) { Ptr::EndpointInfo> endpoint_info = *it; payload_manager_->handleFinishedOutgoingPayload( client_proxy_, std::vector(1, endpoint_info->getId()), *payload_header_, next_chunk_offset_, PayloadManager::endpointInfoStatusToPayloadStatus( endpoint_info->getStatus())); } // Update the still-active recipients of this payload. const AvailableEndpointIds& available_endpoint_ids = available_and_unavailable_endpoints.first; if (available_endpoint_ids.empty()) { // TODO(tracyzhou): Add logging. return ExceptionOr(false); } // Check if the payload has been cancelled by the client and, if so, // notify the remaining recipients. if (pending_payload_->isLocallyCanceled()) { // TODO(tracyzhou): Add logging. payload_manager_->handleFinishedOutgoingPayload( client_proxy_, available_endpoint_ids, *payload_header_, next_chunk_offset_, proto::connections::PayloadStatus::LOCAL_CANCELLATION); return ExceptionOr(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. for (AvailableEndpointIds::const_iterator it = available_endpoint_ids.begin(); it != available_endpoint_ids.end(); it++) { const string& endpoint_id = *it; pending_payload_->setOffsetForEndpoint(endpoint_id, next_chunk_offset_); } ExceptionOr > next_chunk = pending_payload_->getInternalPayload()->detachNextChunk(); if (!next_chunk.ok()) { if (Exception::IO == next_chunk.exception()) { // TODO(tracyzhou): Add logging. payload_manager_->handleFinishedOutgoingPayload( client_proxy_, available_endpoint_ids, *payload_header_, next_chunk_offset_, proto::connections::PayloadStatus::LOCAL_ERROR); return ExceptionOr(false); } } ScopedPtr > scoped_next_chunk(next_chunk.result()); ScopedPtr > payload_chunk( payload_manager_->createPayloadChunk(next_chunk_offset_, scoped_next_chunk.get())); std::vector failed_endpoint_ids = payload_manager_->endpoint_manager_->sendPayloadChunk( *payload_header_, *payload_chunk, available_endpoint_ids); // Check whether at least one endpoint failed. if (!failed_endpoint_ids.empty()) { payload_manager_->handleFinishedOutgoingPayload( client_proxy_, failed_endpoint_ids, *payload_header_, next_chunk_offset_, proto::connections::PayloadStatus::ENDPOINT_IO_ERROR); } // 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 (std::vector::const_iterator it = available_endpoint_ids.begin(); it != available_endpoint_ids.end(); it++) { const string& endpoint_id = *it; if (std::find(failed_endpoint_ids.begin(), failed_endpoint_ids.end(), endpoint_id) == failed_endpoint_ids.end()) { payload_manager_->handleSuccessfulOutgoingChunk( client_proxy_, endpoint_id, *payload_header_, payload_chunk->flags(), payload_chunk->offset(), payload_chunk->body().size()); } } // TODO(tracyzhou): Add logging. if (scoped_next_chunk.isNull()) { // That was the last chunk, we're outta here. return ExceptionOr(false); } next_chunk_offset_ += scoped_next_chunk->size(); } return ExceptionOr(true); } private: typedef std::vector AvailableEndpointIds; typedef std::vector::EndpointInfo> > UnavailableEndpoints; typedef std::pair AvailableAndUnavailableEndpoints; // Splits the endpoints for this payload by availability. Returns a pair of // lists, with the first being the list of still-available endpoint IDs and // the second the list of EndpointInfos for unavailable endpoints. static AvailableAndUnavailableEndpoints getAvailableAndUnavailableEndpoints( ConstPtr::PendingPayload> pending_payload) { AvailableEndpointIds available_endpoint_ids; UnavailableEndpoints unavailable_endpoints; std::vector::EndpointInfo> > endpoints = pending_payload->getEndpoints(); for (typename std::vector::EndpointInfo> >::const_iterator it = endpoints.begin(); it != endpoints.end(); it++) { Ptr::EndpointInfo> endpoint_info = *it; if (PayloadManager::EndpointInfo::Status::AVAILABLE == endpoint_info->getStatus()) { available_endpoint_ids.push_back(endpoint_info->getId()); } else { unavailable_endpoints.push_back(endpoint_info); } } return std::make_pair(available_endpoint_ids, unavailable_endpoints); } // Keep track of the chunk offset across iterations. std::int64_t next_chunk_offset_; Ptr > payload_manager_; Ptr > client_proxy_; Ptr::PendingPayload> pending_payload_; ConstPtr payload_header_; }; // Creates and starts tracking a PendingPayload for this Payload. Returns null // if unable to create the InternalPayload. Ptr::PendingPayload> createOutgoingPayload( ConstPtr payload, const std::vector& endpoint_ids) { ScopedPtr > scoped_payload(payload); ScopedPtr > internal_payload( payload_manager_->internal_payload_factory_->createOutgoing( scoped_payload.release())); if (internal_payload.isNull()) { return Ptr::PendingPayload>(); } std::int64_t payload_id = internal_payload->getId(); ScopedPtr::PendingPayload> > pending_payload( PayloadManager::PendingPayload::createOutgoing( internal_payload.release(), endpoint_ids)); payload_manager_->pending_payloads_->startTrackingPayload( payload_id, pending_payload.release()); return payload_manager_->pending_payloads_->getPayload(payload_id); } Ptr > payload_manager_; Ptr > client_proxy_; std::vector endpoint_ids_; ScopedPtr > payload_; }; template class ProcessEndpointDisconnectionRunnable : public Runnable { public: ProcessEndpointDisconnectionRunnable( Ptr > payload_manager, Ptr > client_proxy, const string& endpoint_id, Ptr process_disconnection_barrier) : payload_manager_(payload_manager), client_proxy_(client_proxy), endpoint_id_(endpoint_id), process_disconnection_barrier_(process_disconnection_barrier) {} void run() override { std::vector endpoints_to_remove(1, endpoint_id_); // Iterate through all our payloads and look for payloads associated with // this endpoint. std::vector::PendingPayload> > pending = payload_manager_->pending_payloads_->getAllPayloads(); for (typename std::vector::PendingPayload> >::const_iterator it = pending.begin(); it != pending.end(); it++) { Ptr::PendingPayload> pending_payload = *it; Ptr::EndpointInfo> endpoint_info = pending_payload->getEndpoint(endpoint_id_); if (endpoint_info.isNull()) { continue; } // Stop tracking the endpoint for this payload. pending_payload->removeEndpoints(endpoints_to_remove); std::int64_t payload_id = pending_payload->getId(); std::int64_t payload_total_size = pending_payload->getInternalPayload()->getTotalSize(); // If no endpoints are left for this payload, stop tracking it and close // it. if (pending_payload->getEndpoints().empty()) { pending_payload = payload_manager_->pending_payloads_->stopTrackingPayload( pending_payload->getId()); pending_payload->close(); pending_payload.destroy(); } // Create the payload transfer update. PayloadTransferUpdate update( payload_id, PayloadTransferUpdate::Status::FAILURE, payload_total_size, endpoint_info->getOffset()); // Send a client notification of a payload transfer failure. client_proxy_->onPayloadTransferUpdate(endpoint_id_, update); } process_disconnection_barrier_->countDown(); } private: Ptr > payload_manager_; Ptr > client_proxy_; const string endpoint_id_; Ptr process_disconnection_barrier_; }; template class SendClientCallbacksForFinishedOutgoingPayloadRunnable : public Runnable { public: SendClientCallbacksForFinishedOutgoingPayloadRunnable( Ptr > payload_manager, Ptr > client_proxy, const std::vector& finished_endpoint_ids, const PayloadTransferFrame::PayloadHeader& payload_header, std::int64_t num_bytes_successfully_transferred, proto::connections::PayloadStatus status) : payload_manager_(payload_manager), client_proxy_(client_proxy), finished_endpoint_ids_(finished_endpoint_ids), payload_header_(payload_header), num_bytes_successfully_transferred_(num_bytes_successfully_transferred), status_(status) {} void run() override { // Make sure we're still tracking this payload. Ptr::PendingPayload> pending_payload = payload_manager_->pending_payloads_->getPayload(payload_header_.id()); if (pending_payload.isNull()) { return; } PayloadTransferUpdate update( payload_header_.id(), PayloadManager::payloadStatusToTransferUpdateStatus(status_), payload_header_.total_size(), num_bytes_successfully_transferred_); for (std::vector::const_iterator it = finished_endpoint_ids_.begin(); it != finished_endpoint_ids_.end(); it++) { const string& endpoint_id = *it; // Skip sending notifications if we have stopped tracking this endpoint. if (pending_payload->getEndpoint(endpoint_id).isNull()) { continue; } // Notify the client. client_proxy_->onPayloadTransferUpdate(endpoint_id, update); } // Remove these endpoints from our tracking list for this payload. pending_payload->removeEndpoints(finished_endpoint_ids_); // Close the payload and stop tracking it if no endpoints remain. if (pending_payload->getEndpoints().empty()) { pending_payload = payload_manager_->pending_payloads_->stopTrackingPayload( payload_header_.id()); pending_payload->close(); pending_payload.destroy(); } } private: Ptr > payload_manager_; Ptr > client_proxy_; const std::vector finished_endpoint_ids_; const PayloadTransferFrame::PayloadHeader payload_header_; const std::int64_t num_bytes_successfully_transferred_; const proto::connections::PayloadStatus status_; }; template class SendClientCallbacksForFinishedIncomingPayloadRunnable : public Runnable { public: SendClientCallbacksForFinishedIncomingPayloadRunnable( Ptr > payload_manager, Ptr > client_proxy, const string& endpoint_id, const PayloadTransferFrame::PayloadHeader& payload_header, std::int64_t offset_bytes, proto::connections::PayloadStatus status) : payload_manager_(payload_manager), client_proxy_(client_proxy), endpoint_id_(endpoint_id), payload_header_(payload_header), offset_bytes_(offset_bytes), status_(status) {} void run() override { // Make sure we're still tracking this payload. Ptr::PendingPayload> pending_payload = payload_manager_->pending_payloads_->getPayload(payload_header_.id()); if (pending_payload.isNull()) { return; } // Unless we never started tracking this payload (meaning we failed to even // create the InternalPayload), notify the client (and close it). PayloadTransferUpdate update( payload_header_.id(), PayloadManager::payloadStatusToTransferUpdateStatus(status_), payload_header_.total_size(), offset_bytes_); payload_manager_->notifyClientOfIncomingPayloadTransferUpdate( client_proxy_, endpoint_id_, update, /*done_with_payload=*/true); } private: Ptr > payload_manager_; Ptr > client_proxy_; const string endpoint_id_; const PayloadTransferFrame::PayloadHeader payload_header_; const std::int64_t offset_bytes_; const proto::connections::PayloadStatus status_; }; template class HandleSuccessfulOutgoingChunkRunnable : public Runnable { public: HandleSuccessfulOutgoingChunkRunnable( Ptr > payload_manager, Ptr > client_proxy, const 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) : payload_manager_(payload_manager), client_proxy_(client_proxy), endpoint_id_(endpoint_id), payload_header_(payload_header), payload_chunk_flags_(payload_chunk_flags), payload_chunk_offset_(payload_chunk_offset), payload_chunk_body_size_(payload_chunk_body_size) {} void run() override { // Make sure we're still tracking this payload and its associated endpoint. Ptr::PendingPayload> pending_payload = payload_manager_->pending_payloads_->getPayload(payload_header_.id()); if (pending_payload.isNull() || pending_payload->getEndpoint(endpoint_id_).isNull()) { return; } // nearby:google3-begin // TODO(reznor): The fact that we've sent total_size bytes (which we will // always know 1 frame before we get the SUCCESS frame), also tells us this // is the last chunk - should we add those smarts, or just be simple and // always have the last IN_PROGRESS have the same numbers as the following // SUCCESS? I prefer the simplicity, but it'll look stupid if we send all // the bytes and then remain hanging because the remote device disconnected // at just that point, so at least consider injecting the smarts. // TODO(reznor): Should we check whether payload_header.total_size == // payload_chunk.offset? // nearby:google3-end bool is_last_chunk = (payload_chunk_flags_ & PayloadTransferFrame::PayloadChunk::LAST_CHUNK) != 0; PayloadTransferUpdate update( payload_header_.id(), is_last_chunk ? PayloadTransferUpdate::Status::SUCCESS : PayloadTransferUpdate::Status::IN_PROGRESS, payload_header_.total_size(), is_last_chunk ? payload_chunk_offset_ : payload_chunk_offset_ + payload_chunk_body_size_); // Notify the client. client_proxy_->onPayloadTransferUpdate(endpoint_id_, update); if (is_last_chunk) { // Stop tracking this endpoint. pending_payload->removeEndpoints(std::vector(1, endpoint_id_)); // Close the payload and stop tracking it if no endpoints remain. if (pending_payload->getEndpoints().empty()) { pending_payload = payload_manager_->pending_payloads_->stopTrackingPayload( payload_header_.id()); pending_payload->close(); pending_payload.destroy(); } } } private: Ptr > payload_manager_; Ptr > client_proxy_; const string endpoint_id_; const PayloadTransferFrame::PayloadHeader payload_header_; const std::int32_t payload_chunk_flags_; const std::int64_t payload_chunk_offset_; const std::int64_t payload_chunk_body_size_; }; template class HandleSuccessfulIncomingChunkRunnable : public Runnable { public: HandleSuccessfulIncomingChunkRunnable( Ptr > payload_manager, Ptr > client_proxy, const 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) : payload_manager_(payload_manager), client_proxy_(client_proxy), endpoint_id_(endpoint_id), payload_header_(payload_header), payload_chunk_flags_(payload_chunk_flags), payload_chunk_offset_(payload_chunk_offset), payload_chunk_body_size_(payload_chunk_body_size) {} void run() override { // Make sure we're still tracking this payload. Ptr::PendingPayload> pending_payload = payload_manager_->pending_payloads_->getPayload(payload_header_.id()); if (pending_payload.isNull()) { return; } // nearby:google3-begin // TODO(reznor): The fact that we've received total_size bytes (which we // will always know 1 frame before we get the SUCCESS frame), also tells us // this is the last chunk - should we add those smarts, or just be simple // and always have the last IN_PROGRESS have the same numbers as the // following SUCCESS? I prefer the simplicity, but it'll look stupid if we // get all the bytes and then remain hanging because the remote device // disconnected at just that point, so at least consider injecting the // smarts. // nearby:google3-end bool is_last_chunk = (payload_chunk_flags_ & PayloadTransferFrame::PayloadChunk::LAST_CHUNK) != 0; PayloadTransferUpdate update( payload_header_.id(), is_last_chunk ? PayloadTransferUpdate::Status::SUCCESS : PayloadTransferUpdate::Status::IN_PROGRESS, payload_header_.total_size(), is_last_chunk ? payload_chunk_offset_ : payload_chunk_offset_ + payload_chunk_body_size_); // Notify the client of this update. payload_manager_->notifyClientOfIncomingPayloadTransferUpdate( client_proxy_, endpoint_id_, update, is_last_chunk); } private: Ptr > payload_manager_; Ptr > client_proxy_; const string endpoint_id_; const PayloadTransferFrame::PayloadHeader payload_header_; const std::int32_t payload_chunk_flags_; const std::int64_t payload_chunk_offset_; const std::int64_t payload_chunk_body_size_; }; template class ProcessDataPacketRunnable : public Runnable { public: ProcessDataPacketRunnable(Ptr > to_client_proxy, const string& from_endpoint_id, ConstPtr payload) : to_client_proxy_(to_client_proxy), from_endpoint_id_(from_endpoint_id), payload_(payload) {} void run() override { to_client_proxy_->onPayloadReceived(from_endpoint_id_, payload_.release()); } private: Ptr > to_client_proxy_; const string from_endpoint_id_; ScopedPtr > payload_; }; } // namespace payload_manager template PayloadManager::PayloadManager( Ptr > endpoint_manager) : internal_payload_factory_(new InternalPayloadFactory()), send_payload_loop_runner_(new LoopRunner("sendPayload")), pending_payloads_(new PendingPayloads()), bytes_payload_executor_(Platform::createSingleThreadExecutor()), file_payload_executor_(Platform::createSingleThreadExecutor()), stream_payload_executor_(Platform::createSingleThreadExecutor()), payload_status_update_executor_(Platform::createSingleThreadExecutor()), endpoint_manager_(endpoint_manager) { endpoint_manager_->registerIncomingOfflineFrameProcessor( V1Frame::PAYLOAD_TRANSFER, std::static_pointer_cast< typename EndpointManager::IncomingOfflineFrameProcessor>( self_)); } template PayloadManager::~PayloadManager() { // TODO(reznor): // logger.atDebug().log("Initiating shutdown of PayloadManager"); // Unregister ourselves from the IncomingOfflineFrameProcessors. endpoint_manager_->unregisterIncomingOfflineFrameProcessor( V1Frame::CONNECTION_RESPONSE, std::static_pointer_cast< typename EndpointManager::IncomingOfflineFrameProcessor>( self_)); // Stop all the ongoing Runnables (as gracefully as possible). payload_status_update_executor_->shutdown(); bytes_payload_executor_->shutdown(); file_payload_executor_->shutdown(); stream_payload_executor_->shutdown(); typedef Ptr::PendingPayload> PtrPendingPayload; // Clear our tracked pending payloads. std::vector pending = pending_payloads_->getAllPayloads(); for (typename std::vector::const_iterator it = pending.begin(); it != pending.end(); it++) { PtrPendingPayload pending_payload = pending_payloads_->stopTrackingPayload((*it)->getId()); pending_payload->close(); pending_payload.destroy(); } // TODO(reznor): // logger.atVerbose().log("PayloadManager has shut down."); } template void PayloadManager::sendPayload( Ptr > client_proxy, const std::vector& endpoint_ids, ConstPtr payload) { Ptr send_payload_executor = getOutgoingPayloadExecutor(payload->getType()); // The send_payload_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. ScopedPtr > scoped_payload(payload); if (send_payload_executor.isNull()) { // TODO(tracyzhou): Add logging. 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. enqueueOutgoingPayload( send_payload_executor, MakePtr(new payload_manager::SendPayloadRunnable( self_, client_proxy, endpoint_ids, scoped_payload.release()))); // TODO(tracyzhou): Add logging. } template Status::Value PayloadManager::cancelPayload( Ptr > client_proxy, std::int64_t payload_id) { Ptr::PendingPayload> canceled_payload = pending_payloads_->getPayload(payload_id); if (canceled_payload.isNull()) { // TODO(tracyzhou): Add logging. return Status::PAYLOAD_UNKNOWN; } // Mark the payload as canceled. canceled_payload->markLocallyCanceled(); // TODO(tracyzhou): Add logging. // Return SUCCESS immediately. Remaining cleanup and updates will be sent in // sendPayload() or processIncomingOfflineFrame() return Status::SUCCESS; } template void PayloadManager::processIncomingOfflineFrame( ConstPtr offline_frame, const string& from_endpoint_id, Ptr > to_client_proxy, proto::connections::Medium current_medium) { ScopedPtr > scoped_offline_frame(offline_frame); const PayloadTransferFrame& payload_transfer_frame = scoped_offline_frame->v1().payload_transfer(); switch (payload_transfer_frame.packet_type()) { case PayloadTransferFrame::CONTROL: processControlPacket(to_client_proxy, from_endpoint_id, payload_transfer_frame); break; case PayloadTransferFrame::DATA: processDataPacket(to_client_proxy, from_endpoint_id, payload_transfer_frame); break; default: // TODO(tracyzhou): Add logging. break; } } template void PayloadManager::processEndpointDisconnection( Ptr > client_proxy, const string& endpoint_id, Ptr process_disconnection_barrier) { payload_status_update_executor_->execute(MakePtr( new payload_manager::ProcessEndpointDisconnectionRunnable( self_, client_proxy, endpoint_id, process_disconnection_barrier))); } template proto::connections::PayloadStatus PayloadManager::endpointInfoStatusToPayloadStatus( typename EndpointInfo::Status::Value status) { switch (status) { case EndpointInfo::Status::CANCELED: return proto::connections::PayloadStatus::REMOTE_CANCELLATION; case EndpointInfo::Status::ERROR: return proto::connections::PayloadStatus::REMOTE_ERROR; case EndpointInfo::Status::AVAILABLE: return proto::connections::PayloadStatus::SUCCESS; default: // TODO(tracyzhou): Add logging. return proto::connections::PayloadStatus::UNKNOWN_PAYLOAD_STATUS; } } template proto::connections::PayloadStatus PayloadManager::controlMessageEventToPayloadStatus( PayloadTransferFrame::ControlMessage::EventType event) { switch (event) { case PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR: return proto::connections::PayloadStatus::REMOTE_ERROR; case PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED: return proto::connections::PayloadStatus::REMOTE_CANCELLATION; default: // TODO(tracyzhou): Add logging. return proto::connections::PayloadStatus::UNKNOWN_PAYLOAD_STATUS; } } template PayloadTransferUpdate::Status::Value PayloadManager::payloadStatusToTransferUpdateStatus( proto::connections::PayloadStatus status) { switch (status) { case proto::connections::LOCAL_CANCELLATION: case proto::connections::REMOTE_CANCELLATION: return PayloadTransferUpdate::Status::CANCELED; case proto::connections::SUCCESS: return PayloadTransferUpdate::Status::SUCCESS; default: return PayloadTransferUpdate::Status::FAILURE; } } template Ptr PayloadManager::getOutgoingPayloadExecutor( Payload::Type::Value payload_type) { switch (payload_type) { case Payload::Type::BYTES: return bytes_payload_executor_.get(); case Payload::Type::FILE: return file_payload_executor_.get(); case Payload::Type::STREAM: return stream_payload_executor_.get(); default: return Ptr(); } } template ConstPtr PayloadManager::createPayloadHeader( ConstPtr internal_payload) { ScopedPtr > payload_header( new PayloadTransferFrame::PayloadHeader()); payload_header->set_id(internal_payload->getId()); payload_header->set_type(internal_payload->getType()); payload_header->set_total_size(internal_payload->getTotalSize()); return ConstifyPtr(payload_header.release()); } template ConstPtr PayloadManager::createPayloadChunk( std::int64_t payload_chunk_offset, ConstPtr payload_chunk_body) { ScopedPtr > payload_chunk( new PayloadTransferFrame::PayloadChunk()); payload_chunk->set_offset(payload_chunk_offset); if (!payload_chunk_body.isNull()) { payload_chunk->set_body(payload_chunk_body->getData(), payload_chunk_body->size()); } // This is a null-initialized Integer, so it needs to be initialized to avoid // inadvertent NPEs. payload_chunk->set_flags(0); if (payload_chunk_body.isNull()) { payload_chunk->set_flags(payload_chunk->flags() | PayloadTransferFrame::PayloadChunk::LAST_CHUNK); } return ConstifyPtr(payload_chunk.release()); } template Ptr::PendingPayload> PayloadManager::createIncomingPayload( const PayloadTransferFrame& payload_transfer_frame, const string& endpoint_id) { ScopedPtr > internal_payload( internal_payload_factory_->createIncoming(payload_transfer_frame)); if (internal_payload.isNull()) { return Ptr::PendingPayload>(); } std::int64_t payload_id = internal_payload->getId(); ScopedPtr::PendingPayload> > pending_payload(PendingPayload::createIncoming(internal_payload.release(), endpoint_id)); pending_payloads_->startTrackingPayload(payload_id, pending_payload.release()); return pending_payloads_->getPayload(payload_id); } template void PayloadManager::sendClientCallbacksForFinishedOutgoingPayload( Ptr > client_proxy, const std::vector& finished_endpoint_ids, const PayloadTransferFrame::PayloadHeader& payload_header, std::int64_t num_bytes_successfully_transferred, proto::connections::PayloadStatus status) { payload_status_update_executor_->execute(MakePtr( new payload_manager:: SendClientCallbacksForFinishedOutgoingPayloadRunnable( self_, client_proxy, finished_endpoint_ids, payload_header, num_bytes_successfully_transferred, status))); } template void PayloadManager::sendClientCallbacksForFinishedIncomingPayload( Ptr > client_proxy, const string& endpoint_id, const PayloadTransferFrame::PayloadHeader& payload_header, std::int64_t offset_bytes, proto::connections::PayloadStatus status) { payload_status_update_executor_->execute(MakePtr( new payload_manager:: SendClientCallbacksForFinishedIncomingPayloadRunnable( self_, client_proxy, endpoint_id, payload_header, offset_bytes, status))); } template void PayloadManager::sendControlMessage( const std::vector& 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); } template void PayloadManager::handleFinishedOutgoingPayload( Ptr > client_proxy, const std::vector& finished_endpoint_ids, const PayloadTransferFrame::PayloadHeader& payload_header, std::int64_t num_bytes_successfully_transferred, proto::connections::PayloadStatus status) { sendClientCallbacksForFinishedOutgoingPayload( client_proxy, finished_endpoint_ids, payload_header, num_bytes_successfully_transferred, status); switch (status) { case proto::connections::PayloadStatus::LOCAL_ERROR: sendControlMessage(finished_endpoint_ids, payload_header, num_bytes_successfully_transferred, PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR); break; case proto::connections::PayloadStatus::LOCAL_CANCELLATION: sendControlMessage( finished_endpoint_ids, payload_header, num_bytes_successfully_transferred, PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED); break; case proto::connections::PayloadStatus::ENDPOINT_IO_ERROR: // Unregister these endpoints, since we had an IO error on the physical // connection. for (std::vector::const_iterator it = finished_endpoint_ids.begin(); it != finished_endpoint_ids.end(); it++) { endpoint_manager_->discardEndpoint(client_proxy, *it); } break; case proto::connections::PayloadStatus::REMOTE_ERROR: case proto::connections::PayloadStatus::REMOTE_CANCELLATION: // No special handling needed for these. break; default: // TODO(tracyzhou): Add logging. break; } } template void PayloadManager::handleFinishedIncomingPayload( Ptr > client_proxy, const string& endpoint_id, const PayloadTransferFrame::PayloadHeader& payload_header, std::int64_t offset_bytes, proto::connections::PayloadStatus status) { sendClientCallbacksForFinishedIncomingPayload( client_proxy, endpoint_id, payload_header, offset_bytes, status); switch (status) { case proto::connections::PayloadStatus::LOCAL_ERROR: sendControlMessage(std::vector(1, endpoint_id), payload_header, offset_bytes, PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR); break; case proto::connections::PayloadStatus::LOCAL_CANCELLATION: sendControlMessage( std::vector(1, endpoint_id), payload_header, offset_bytes, PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED); break; default: // TODO(tracyzhou): Add logging. break; } } template void PayloadManager::handleSuccessfulOutgoingChunk( Ptr > client_proxy, const 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) { payload_status_update_executor_->execute(MakePtr( new payload_manager::HandleSuccessfulOutgoingChunkRunnable( self_, client_proxy, endpoint_id, payload_header, payload_chunk_flags, payload_chunk_offset, payload_chunk_body_size))); } template void PayloadManager::handleSuccessfulIncomingChunk( Ptr > client_proxy, const 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) { payload_status_update_executor_->execute(MakePtr( new payload_manager::HandleSuccessfulIncomingChunkRunnable( self_, client_proxy, endpoint_id, payload_header, payload_chunk_flags, payload_chunk_offset, payload_chunk_body_size))); } template void PayloadManager::processDataPacket( Ptr > to_client_proxy, const string& from_endpoint_id, const PayloadTransferFrame& payload_transfer_frame) { const PayloadTransferFrame::PayloadHeader& payload_header = payload_transfer_frame.payload_header(); const PayloadTransferFrame::PayloadChunk& payload_chunk = payload_transfer_frame.payload_chunk(); // TODO(tracyzhou): Add logging. Ptr::PendingPayload> pending_payload; if (payload_chunk.offset() == 0) { pending_payload = createIncomingPayload(payload_transfer_frame, from_endpoint_id); if (pending_payload.isNull()) { // TODO(tracyzhou): Add logging. // Send the error to the remote endpoint. sendControlMessage(std::vector(1, from_endpoint_id), payload_header, payload_chunk.offset(), PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR); return; } // Also, let the client know of this new incoming payload. payload_status_update_executor_->execute( MakePtr(new payload_manager::ProcessDataPacketRunnable( to_client_proxy, from_endpoint_id, pending_payload->getInternalPayload()->releasePayload()))); // TODO(tracyzhou): Add logging. } else { pending_payload = pending_payloads_->getPayload(payload_header.id()); if (pending_payload.isNull()) { // TODO(tracyzhou): Add logging. 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 handleFinishedIncomingPayload( to_client_proxy, from_endpoint_id, payload_header, payload_chunk.offset(), proto::connections::PayloadStatus::LOCAL_CANCELLATION); 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()); Exception::Value attach_next_chunk_exception = pending_payload->getInternalPayload()->attachNextChunk( MakeConstPtr(new ByteArray(payload_chunk.body().data(), payload_chunk.body().size()))); if (Exception::NONE != attach_next_chunk_exception) { if (Exception::IO == attach_next_chunk_exception) { // TODO(tracyzhou): Add logging. handleFinishedIncomingPayload( to_client_proxy, from_endpoint_id, payload_header, payload_chunk.offset(), proto::connections::PayloadStatus::LOCAL_ERROR); return; } } handleSuccessfulIncomingChunk( to_client_proxy, from_endpoint_id, payload_header, payload_chunk.flags(), payload_chunk.offset(), payload_chunk.body().size()); } template void PayloadManager::processControlPacket( Ptr > to_client_proxy, const string& from_endpoint_id, const PayloadTransferFrame& payload_transfer_frame) { const PayloadTransferFrame::PayloadHeader& payload_header = payload_transfer_frame.payload_header(); const PayloadTransferFrame::ControlMessage& control_message = payload_transfer_frame.control_message(); Ptr::PendingPayload> pending_payload = pending_payloads_->getPayload(payload_header.id()); if (pending_payload.isNull()) { // TODO(tracyzhou): Add logging. return; } switch (control_message.event()) { case PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED: if (pending_payload->isIncoming()) { // 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_proxy, from_endpoint_id, payload_header, control_message.offset(), controlMessageEventToPayloadStatus(control_message.event())); } else { // Mark the payload as canceled *for this endpoint*. pending_payload->setEndpointStatusFromControlMessage(from_endpoint_id, control_message); } // TODO(tracyzhou): Add logging. break; case PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR: if (pending_payload->isIncoming()) { handleFinishedIncomingPayload( to_client_proxy, from_endpoint_id, payload_header, control_message.offset(), controlMessageEventToPayloadStatus(control_message.event())); } else { pending_payload->setEndpointStatusFromControlMessage(from_endpoint_id, control_message); } break; default: // TODO(tracyzhou): Add logging. break; } } template void PayloadManager::notifyClientOfIncomingPayloadTransferUpdate( Ptr > client_proxy, const string& endpoint_id, const PayloadTransferUpdate& payload_transfer_update, bool done_with_payload) { client_proxy->onPayloadTransferUpdate(endpoint_id, payload_transfer_update); if (done_with_payload) { // We're done with this payload (either received the last chunk, or had a // failure), so remove it from the incoming payloads that we're tracking. Ptr::PendingPayload> pending_payload = pending_payloads_->stopTrackingPayload( payload_transfer_update.payload_id); pending_payload->close(); pending_payload.destroy(); } } template void PayloadManager::enqueueOutgoingPayload( Ptr executor, Ptr runnable) { executor->execute(runnable); } ///////////////////////////////// EndpointInfo ///////////////////////////////// template PayloadManager::EndpointInfo::EndpointInfo(string id) : id_(id), status_(Status::AVAILABLE), offset_(0) {} template typename PayloadManager::EndpointInfo::Status::Value PayloadManager::EndpointInfo::controlMessageEventToEndpointInfoStatus( PayloadTransferFrame::ControlMessage::EventType event) { switch (event) { case PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR: return Status::ERROR; case PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED: return Status::CANCELED; default: // TODO(tracyzhou): Add logging. return Status::UNKNOWN; } } template string PayloadManager::EndpointInfo::getId() const { return id_; } template typename PayloadManager::EndpointInfo::Status::Value PayloadManager::EndpointInfo::getStatus() const { return status_; } template std::int64_t PayloadManager::EndpointInfo::getOffset() const { return offset_; } template void PayloadManager::EndpointInfo::setStatus( const PayloadTransferFrame::ControlMessage& control_message) { status_ = controlMessageEventToEndpointInfoStatus(control_message.event()); } template void PayloadManager::EndpointInfo::setOffset(std::int64_t offset) { offset_ = offset; } //////////////////////////////// PendingPayload //////////////////////////////// template Ptr::PendingPayload> PayloadManager::PendingPayload::createIncoming( Ptr internal_payload, const string& endpoint_id) { return MakeRefCountedPtr(new PendingPayload( internal_payload, std::vector(1, endpoint_id), true)); } template Ptr::PendingPayload> PayloadManager::PendingPayload::createOutgoing( Ptr internal_payload, const std::vector& endpoint_ids) { return MakeRefCountedPtr( new PendingPayload(internal_payload, endpoint_ids, false)); } template PayloadManager::PendingPayload::PendingPayload( Ptr internal_payload, const std::vector& endpoint_ids, bool is_incoming) : lock_(Platform::createLock()), internal_payload_(internal_payload), is_incoming_(is_incoming), is_locally_cancelled_(Platform::createAtomicBoolean(false)), endpoints_() { for (std::vector::const_iterator it = endpoint_ids.begin(); it != endpoint_ids.end(); it++) { endpoints_.insert(std::make_pair(*it, MakePtr(new EndpointInfo(*it)))); } } template PayloadManager::PendingPayload::~PendingPayload() { for (typename EndpointsMap::iterator it = endpoints_.begin(); it != endpoints_.end(); it++) { it->second.destroy(); } endpoints_.clear(); } template std::int64_t PayloadManager::PendingPayload::getId() { return internal_payload_->getId(); } template Ptr PayloadManager::PendingPayload::getInternalPayload() { return internal_payload_.get(); } template bool PayloadManager::PendingPayload::isLocallyCanceled() { return is_locally_cancelled_->get(); } template void PayloadManager::PendingPayload::markLocallyCanceled() { is_locally_cancelled_->set(true); } template bool PayloadManager::PendingPayload::isIncoming() { return is_incoming_; } template std::vector::EndpointInfo> > PayloadManager::PendingPayload::getEndpoints() const { Synchronized s(lock_.get()); std::vector::EndpointInfo> > result; for (typename EndpointsMap::const_iterator it = endpoints_.begin(); it != endpoints_.end(); it++) { result.push_back(it->second); } return result; } template Ptr::EndpointInfo> PayloadManager::PendingPayload::getEndpoint( const string& endpoint_id) { Synchronized s(lock_.get()); typename EndpointsMap::iterator it = endpoints_.find(endpoint_id); if (it == endpoints_.end()) { return Ptr::EndpointInfo>(); } return it->second; } template void PayloadManager::PendingPayload::removeEndpoints( const std::vector& endpoint_ids_to_remove) { Synchronized s(lock_.get()); for (std::vector::const_iterator it = endpoint_ids_to_remove.begin(); it != endpoint_ids_to_remove.end(); it++) { payload_manager::eraseOwnedPtrFromMap(endpoints_, *it); } } template void PayloadManager::PendingPayload:: setEndpointStatusFromControlMessage( const string& endpoint_id, const PayloadTransferFrame::ControlMessage& control_message) { Synchronized s(lock_.get()); typename EndpointsMap::iterator it = endpoints_.find(endpoint_id); if (it != endpoints_.end()) { it->second->setStatus(control_message); } } template void PayloadManager::PendingPayload::setOffsetForEndpoint( const string& endpoint_id, std::int64_t offset) { Synchronized s(lock_.get()); typename EndpointsMap::iterator it = endpoints_.find(endpoint_id); if (it != endpoints_.end()) { it->second->setOffset(offset); } } template void PayloadManager::PendingPayload::close() { internal_payload_->close(); } /////////////////////////////// PendingPayloads /////////////////////////////// template PayloadManager::PendingPayloads::PendingPayloads() : lock_(Platform::createLock()), pending_payloads_() {} template PayloadManager::PendingPayloads::~PendingPayloads() { for (typename PendingPayloadsMap::iterator it = pending_payloads_.begin(); it != pending_payloads_.end(); it++) { it->second.destroy(); } pending_payloads_.clear(); } template void PayloadManager::PendingPayloads::startTrackingPayload( std::int64_t payload_id, Ptr::PendingPayload> pending_payload) { Synchronized s(lock_.get()); pending_payloads_.insert(std::make_pair(payload_id, pending_payload)); } template Ptr::PendingPayload> PayloadManager::PendingPayloads::stopTrackingPayload( std::int64_t payload_id) { Synchronized s(lock_.get()); typename PendingPayloadsMap::iterator it = pending_payloads_.find(payload_id); if (it == pending_payloads_.end()) { return Ptr::PendingPayload>(); } Ptr::PendingPayload> pending_payload = it->second; pending_payloads_.erase(it); return pending_payload; } template Ptr::PendingPayload> PayloadManager::PendingPayloads::getPayload(std::int64_t payload_id) { Synchronized s(lock_.get()); typename PendingPayloadsMap::iterator it = pending_payloads_.find(payload_id); if (it == pending_payloads_.end()) { return Ptr::PendingPayload>(); } return it->second; } template std::vector::PendingPayload> > PayloadManager::PendingPayloads::getAllPayloads() { Synchronized s(lock_.get()); std::vector::PendingPayload> > result; for (typename PendingPayloadsMap::iterator it = pending_payloads_.begin(); it != pending_payloads_.end(); it++) { result.push_back(it->second); } return result; } } // namespace connections } // namespace nearby } // namespace location