/** * Looking Glass * Copyright © 2017-2026 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "transport/CTransportManager.h" #include "capture/CFrameGraph.h" #include "CDebug.h" #include "Seq.h" #include #include #include static const uint64_t RETRY_DELAY_MS = 500; static const uint64_t SERVICE_RETRY_DELAY_MS = 250; class CSourceEvents final : public ITransportEvents { private: BackendId m_backend; uint32_t m_epoch; bool m_interactions; CInputHub& m_input; CRecoveryHub& m_recovery; ITransportActions& m_actions; SourceKey Stamp(const SourceKey& source) const { SourceKey stamped = source; stamped.backend = m_backend; stamped.epoch = m_epoch; return stamped; } public: CSourceEvents( BackendId backend, uint32_t epoch, bool interactions, CInputHub& input, CRecoveryHub& recovery, ITransportActions& actions) : m_backend(backend), m_epoch(epoch), m_interactions(interactions), m_input(input), m_recovery(recovery), m_actions(actions) {} InteractionResult OnSetCursorPos( const SourceKey& source, int32_t x, int32_t y) override { if (!m_interactions) return InteractionResult::UNAVAILABLE; SourceKey stamped = Stamp(source); CInputHub::InteractionPermit permit; const InteractionResult result = m_input.CheckInteraction(stamped, permit); if (result != InteractionResult::ACCEPTED) return result; const InteractionResult applied = m_actions.OnSetCursorPos(stamped, x, y); if (applied == InteractionResult::ACCEPTED) m_input.CommitInteraction(stamped, permit); return applied; } InteractionResult OnSetResolution(const SourceKey& source, uint32_t width, uint32_t height) override { if (!m_interactions) return InteractionResult::UNAVAILABLE; SourceKey stamped = Stamp(source); CInputHub::InteractionPermit permit; const InteractionResult result = m_input.CheckInteraction(stamped, permit); if (result != InteractionResult::ACCEPTED) return result; const InteractionResult applied = m_actions.OnSetResolution(stamped, width, height); if (applied == InteractionResult::ACCEPTED) m_input.CommitInteraction(stamped, permit); return applied; } RecoveryAdmission OnRecoveryRequest(const SourceKey& source, uint64_t session, uint32_t serial, bool active) override { RecoveryAction action; bool dispatch = false; const RecoveryAdmission admission = m_recovery.Submit( Stamp(source), session, serial, active, GetTickCount64(), action, dispatch); if (dispatch && !m_actions.OnRecoveryAction(action)) m_recovery.DispatchFailed(action, RPC_S_SERVER_UNAVAILABLE); return admission; } }; CTransportManager::Entry::Entry() { idleEvent = CreateEvent(nullptr, TRUE, TRUE, nullptr); } CTransportManager::Entry::~Entry() { if (idleEvent) CloseHandle(idleEvent); } CTransportManager::CTransportManager() { m_phaseIdle = CreateEvent(nullptr, TRUE, TRUE, nullptr); m_stoppedEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); } CTransportManager::~CTransportManager() { Stop(); if (m_stoppedEvent) CloseHandle(m_stoppedEvent); if (m_phaseIdle) CloseHandle(m_phaseIdle); } unsigned CTransportManager::Entries( Entry * entries[FRAME_MAX_SINKS]) const { CSRWSharedLock managerLock(m_lock); for (unsigned i = 0; i < m_entryCount; ++i) entries[i] = m_entries[i].get(); return m_entryCount; } CTransportManager::Entry * CTransportManager::Primary() const { CSRWSharedLock managerLock(m_lock); return m_primary; } bool CTransportManager::BeginPhase( Phase phase, bool wait, bool stopping) { const DWORD thread = GetCurrentThreadId(); for (;;) { HANDLE idleEvent = nullptr; { CSRWExclusiveLock managerLock(m_lock); if ((!stopping && (m_stopping || m_stopped)) || (stopping && m_stopped)) return false; if (m_phase == Phase::IDLE) { m_phase = phase; m_phaseOwner = thread; ResetEvent(m_phaseIdle); return true; } if (!wait || m_phaseOwner == thread) return false; idleEvent = m_phaseIdle; } if (!idleEvent || WaitForSingleObject(idleEvent, INFINITE) != WAIT_OBJECT_0) return false; } } void CTransportManager::EndPhase() { CSRWExclusiveLock managerLock(m_lock); m_phase = Phase::IDLE; m_phaseOwner = 0; SetEvent(m_phaseIdle); } bool CTransportManager::BeginCall( Entry& entry, Call call, bool wait) { const DWORD thread = GetCurrentThreadId(); for (;;) { HANDLE idleEvent = nullptr; { CSRWExclusiveLock entryLock(entry.lock); if (entry.stopRequested || entry.state == State::STOPPED) return false; if (entry.call == Call::IDLE) { entry.call = call; entry.callOwner = thread; ResetEvent(entry.idleEvent); return true; } if (!wait || entry.callOwner == thread) return false; idleEvent = entry.idleEvent; } if (!idleEvent || WaitForSingleObject(idleEvent, INFINITE) != WAIT_OBJECT_0) return false; } } void CTransportManager::DrainRecovery(Entry& entry, const std::shared_ptr& transport) { static const unsigned MAX_DRAIN = 17; for (unsigned i = 0; i < MAX_DRAIN; ++i) { bool sync = false; BackendId id = 0; uint32_t epoch = 0; { CSRWExclusiveLock entryLock(entry.lock); if (entry.stopRequested || !transport || transport != entry.transport || (entry.state != State::INITIALIZED && entry.state != State::READY)) { entry.syncPending = false; return; } id = entry.id; epoch = entry.epoch; sync = entry.syncPending; entry.syncPending = false; } if (sync) transport->SyncRecovery(); CRecoveryHub::Delivery delivery; if (!m_recovery.TakeDelivery(id, epoch, delivery)) { if (!sync) return; continue; } transport->RecoveryStatus(delivery.source, delivery.session, delivery.serial, delivery.active, delivery.state, delivery.error); } } void CTransportManager::EndCall(Entry& entry, const std::shared_ptr& transport, bool drain) { for (;;) { if (drain) DrainRecovery(entry, transport); CSRWExclusiveLock entryLock(entry.lock); if (drain && entry.syncPending) continue; entry.call = Call::IDLE; entry.callOwner = 0; SetEvent(entry.idleEvent); return; } } void CTransportManager::DetachRecovery(Entry& entry) { BackendId id = 0; uint32_t epoch = 0; bool attached = false; { CSRWExclusiveLock entryLock(entry.lock); id = entry.id; epoch = entry.epoch; attached = entry.recoveryAttached; entry.recoveryAttached = false; entry.syncPending = false; } if (attached) m_recovery.Remove(id, epoch); } bool CTransportManager::Add(TransportInstance config, bool primary, CreateFn create) { CSRWExclusiveLock managerLock(m_lock); if (!m_phaseIdle || !m_stoppedEvent || m_phase != Phase::IDLE || m_started || m_stopping || m_stopped || !config.enabled || !config.id || config.kind.empty() || (config.services & ~TRANSPORT_SERVICE_ALL) || !create || m_entryCount == FRAME_MAX_SINKS || (primary && m_primary)) return false; if (primary && (!config.required || !(config.services & TRANSPORT_SERVICE_FRAME))) return false; for (unsigned i = 0; i < m_entryCount; ++i) if (m_entries[i]->id == config.id) return false; std::unique_ptr entry(new (std::nothrow) Entry); if (!entry || !entry->idleEvent) return false; entry->id = config.id; entry->required = config.required; entry->primary = primary; entry->create = create; entry->config = std::move(config); Entry * raw = entry.get(); m_entries[m_entryCount++] = std::move(entry); if (primary) m_primary = raw; return true; } ITransport::OpenResult CTransportManager::OpenEntry(Entry& entry) { std::shared_ptr transport; CreateFn create = nullptr; TransportInstance config; { CSRWSharedLock entryLock(entry.lock); transport = entry.transport; create = entry.create; config = entry.config; } if (!transport) { std::unique_ptr created = create(config); transport.reset(created.release()); CSRWExclusiveLock entryLock(entry.lock); entry.transport = transport; } if (!transport) { CSRWExclusiveLock entryLock(entry.lock); entry.state = State::FAILED; return OpenResult::FAILURE; } const OpenResult result = transport->Open(); switch (result) { case OpenResult::SUCCESS: { CSRWExclusiveLock entryLock(entry.lock); entry.state = State::OPEN; break; } case OpenResult::RETRY: ScheduleRetry(entry); break; case OpenResult::FAILURE: { CSRWExclusiveLock entryLock(entry.lock); entry.state = State::FAILED; break; } } return result; } bool CTransportManager::InitializeEntry(Entry& entry) { std::shared_ptr transport; BackendId id = 0; uint32_t epoch = 0; uint32_t services = 0; bool required = false; { CSRWSharedLock entryLock(entry.lock); if (entry.state == State::INITIALIZED || entry.state == State::READY) return true; if (entry.state != State::OPEN) return false; transport = entry.transport; id = entry.id; epoch = entry.epoch; services = entry.config.services; required = entry.required; } if (!transport || !transport->Initialize()) { CSRWExclusiveLock entryLock(entry.lock); entry.state = State::FAILED; return false; } std::shared_ptr frameCaps; if (services & TRANSPORT_SERVICE_FRAME) { frameCaps = transport->GetFrameCaps(); if (!frameCaps && required) { CSRWExclusiveLock entryLock(entry.lock); entry.state = State::FAILED; return false; } } const bool hasFrameCaps = frameCaps != nullptr; bool syncNow = false; if (!m_recovery.Attach(id, epoch, syncNow)) { CSRWExclusiveLock entryLock(entry.lock); entry.state = State::FAILED; return false; } { CSRWExclusiveLock entryLock(entry.lock); // The first successfully initialized instance fixes the advertised // capability contract. Recreating an instance must not change the mode // list during a runtime restart. if (!entry.frameCaps) entry.frameCaps = std::move(frameCaps); // A best-effort instance without a capability contract can continue to // provide its other configured services, but must not receive frames. entry.frameAbsent = (services & TRANSPORT_SERVICE_FRAME) && !hasFrameCaps; entry.recoveryAttached = true; entry.syncPending = syncNow; entry.state = State::INITIALIZED; } // Monitor readiness may be published after Attach but before this entry is // visible to SyncRecovery. Claim any synchronization missed in that gap. if (m_recovery.ClaimSync(id, epoch)) { CSRWExclusiveLock entryLock(entry.lock); if (entry.id == id && entry.epoch == epoch && entry.recoveryAttached) entry.syncPending = true; } return true; } bool CTransportManager::AddServices(Entry& entry) { std::shared_ptr transport; BackendId id = 0; uint32_t epoch = 0; bool primary = false; bool required = false; uint32_t services = 0; bool controlAdded = false; bool controlFailed = false; bool controlAbsent = false; bool inputAdded = false; bool inputFailed = false; bool inputAbsent = false; bool frameAdded = false; bool frameAbsent = false; uint64_t retryAt = 0; { CSRWSharedLock entryLock(entry.lock); transport = entry.transport; id = entry.id; epoch = entry.epoch; primary = entry.primary; required = entry.required; services = entry.config.services; controlAdded = entry.controlAdded; controlFailed = entry.controlFailed; controlAbsent = entry.controlAbsent; inputAdded = entry.inputAdded; inputFailed = entry.inputFailed; inputAbsent = entry.inputAbsent; frameAdded = entry.frameAdded; frameAbsent = entry.frameAbsent; retryAt = entry.serviceRetryAt; } if (!transport) return !required; const uint64_t now = GetTickCount64(); const bool attach = now >= retryAt; bool controlRetry = false; bool frameRetry = false; bool inputRetry = false; if (!(services & TRANSPORT_SERVICE_CONTROL)) controlAbsent = true; else if (attach && !controlAdded && !controlFailed && !controlAbsent) { IControlSink * control = transport->Control(); if (control && m_control.Add(id, epoch, *control)) { controlAdded = true; CSRWExclusiveLock entryLock(entry.lock); entry.controlAdded = true; } else if (control) controlRetry = true; else { controlAbsent = true; CSRWExclusiveLock entryLock(entry.lock); entry.controlAbsent = true; } } if (!(services & TRANSPORT_SERVICE_FRAME)) frameAbsent = true; else if (attach && !frameAdded && !frameAbsent) { IFrameSink * frame = transport->FrameSink(); if (frame && m_frames.Bind(id, epoch, primary, *frame)) { CSRWExclusiveLock entryLock(entry.lock); entry.frameAdded = true; frameAdded = true; } else if (frame || primary) frameRetry = true; else { frameAbsent = true; CSRWExclusiveLock entryLock(entry.lock); entry.frameAbsent = true; } } if (!(services & TRANSPORT_SERVICE_INPUT)) inputAbsent = true; else if (attach && !inputAdded && !inputFailed && !inputAbsent) { IInputSource * input = transport->Input(); if (input && m_input.Bind(id, epoch, *input)) { CSRWExclusiveLock entryLock(entry.lock); entry.inputAdded = true; inputAdded = true; } else if (input) inputRetry = true; else { inputAbsent = true; CSRWExclusiveLock entryLock(entry.lock); entry.inputAbsent = true; } } if (attach && (controlRetry || inputRetry || frameRetry)) { CSRWExclusiveLock entryLock(entry.lock); entry.serviceRetryAt = now + SERVICE_RETRY_DELAY_MS; } const bool servicesReady = (!(services & TRANSPORT_SERVICE_FRAME) || frameAdded) && (!(services & TRANSPORT_SERVICE_CONTROL) || controlAdded) && (!(services & TRANSPORT_SERVICE_INPUT) || inputAdded); return !required || servicesReady; } void CTransportManager::HandleServiceFailures() { ControlToken token; while (m_control.TakeFailure(token)) { Entry * entries[FRAME_MAX_SINKS] = {}; const unsigned count = Entries(entries); for (unsigned i = 0; i < count; ++i) { Entry& entry = *entries[i]; bool restart = false; { CSRWExclusiveLock entryLock(entry.lock); if (entry.id != token.backend || entry.epoch != token.epoch || !entry.controlAdded) continue; entry.controlAdded = false; entry.controlFailed = true; restart = !entry.exposed; } m_control.Remove(token.backend, token.epoch); m_input.RevokeInteraction(token.backend, token.epoch); if (restart) { RemoveServices(entry); ScheduleRetry(entry); } break; } } SourceKey source; while (m_input.TakeFailure(source)) { Entry * entries[FRAME_MAX_SINKS] = {}; const unsigned count = Entries(entries); for (unsigned i = 0; i < count; ++i) { Entry& entry = *entries[i]; bool restart = false; { CSRWExclusiveLock entryLock(entry.lock); if (entry.id != source.backend || entry.epoch != source.epoch || !entry.inputAdded) continue; entry.inputAdded = false; entry.inputFailed = true; restart = !entry.exposed; } m_input.Unbind(source.backend, source.epoch); if (restart) { RemoveServices(entry); ScheduleRetry(entry); } break; } } } bool CTransportManager::SetupEntry(Entry& entry, size_t alignment) { std::shared_ptr transport; { CSRWSharedLock entryLock(entry.lock); if (entry.state != State::INITIALIZED && entry.state != State::READY) return false; transport = entry.transport; } bool setupDone = false; { CSRWSharedLock entryLock(entry.lock); setupDone = entry.setupDone; } if (!transport || (!setupDone && !transport->Setup(alignment))) { CSRWExclusiveLock entryLock(entry.lock); entry.state = State::FAILED; return false; } { CSRWExclusiveLock entryLock(entry.lock); entry.setupDone = true; } if (!AddServices(entry)) { CSRWExclusiveLock entryLock(entry.lock); entry.state = State::INITIALIZED; return false; } CSRWExclusiveLock entryLock(entry.lock); entry.state = State::READY; return true; } void CTransportManager::ScheduleRetry(Entry& entry) { CSRWExclusiveLock entryLock(entry.lock); entry.state = State::RETRY; entry.retryAt = GetTickCount64() + RETRY_DELAY_MS; } void CTransportManager::RemoveServices(Entry& entry) { BackendId id = 0; uint32_t epoch = 0; bool frameAdded = false; bool controlAdded = false; bool inputAdded = false; { CSRWExclusiveLock entryLock(entry.lock); id = entry.id; epoch = entry.epoch; frameAdded = entry.frameAdded; controlAdded = entry.controlAdded; inputAdded = entry.inputAdded; entry.frameAdded = false; entry.controlAdded = false; entry.inputAdded = false; } m_input.RevokeInteraction(id, epoch); if (inputAdded) m_input.Unbind(id, epoch); if (controlAdded) m_control.Remove(id, epoch); if (frameAdded) m_frames.Unbind(id, epoch); } void CTransportManager::RetryEntry(Entry& entry, uint64_t now, bool initialized, bool setup, size_t alignment) { std::shared_ptr transport; { CSRWSharedLock entryLock(entry.lock); if (entry.state != State::RETRY || now < entry.retryAt || (entry.primary && entry.exposed)) return; transport = entry.transport; } DetachRecovery(entry); RemoveServices(entry); if (transport) transport->Stop(); { CSRWExclusiveLock entryLock(entry.lock); entry.transport.reset(); entry.directMemory = DirectFrameBufferMemory {}; entry.directMemoryValid = false; entry.setupDone = false; entry.controlFailed = false; entry.controlAbsent = false; entry.inputFailed = false; entry.inputAbsent = false; entry.frameAbsent = false; entry.serviceRetryAt = 0; Seq::Inc(entry.epoch); } if (OpenEntry(entry) != OpenResult::SUCCESS) return; if (initialized && !InitializeEntry(entry)) { ScheduleRetry(entry); return; } if (setup && !SetupEntry(entry, alignment)) { CSRWSharedLock entryLock(entry.lock); if (entry.state == State::FAILED) { entryLock.Unlock(); ScheduleRetry(entry); } } } void CTransportManager::HandleProcessResult( Entry& entry, ProcessResult result) { if (result == ProcessResult::OK) return; bool exposed = false; bool primary = false; std::wstring name; { CSRWSharedLock entryLock(entry.lock); exposed = entry.exposed; primary = entry.primary; name = entry.config.kind; } DetachRecovery(entry); RemoveServices(entry); if (primary && exposed) { DEBUG_WARN("Transport %ls stopped while its frame interfaces are active", name.c_str()); CSRWExclusiveLock entryLock(entry.lock); entry.state = State::FAILED; return; } if (result == ProcessResult::RETRY) { ScheduleRetry(entry); return; } CSRWExclusiveLock entryLock(entry.lock); entry.state = State::FAILED; } void CTransportManager::Expose(Entry& entry) { CSRWExclusiveLock entryLock(entry.lock); entry.exposed = true; entry.exposedTransport = entry.transport; } ITransport::OpenResult CTransportManager::Open() { if (!BeginPhase(Phase::OPEN, true)) return OpenResult::FAILURE; { CSRWExclusiveLock managerLock(m_lock); m_started = true; } Entry * entries[FRAME_MAX_SINKS] = {}; const unsigned count = Entries(entries); OpenResult aggregate = Primary() ? OpenResult::SUCCESS : OpenResult::FAILURE; for (unsigned i = 0; i < count; ++i) { Entry& entry = *entries[i]; if (!BeginCall(entry, Call::LIFECYCLE, true)) { if (entry.required) aggregate = OpenResult::FAILURE; continue; } bool alreadyOpen = false; std::shared_ptr transport; { CSRWSharedLock entryLock(entry.lock); alreadyOpen = entry.state == State::OPEN || entry.state == State::INITIALIZED || entry.state == State::READY; transport = entry.transport; } const OpenResult result = alreadyOpen ? OpenResult::SUCCESS : OpenEntry(entry); { CSRWSharedLock entryLock(entry.lock); transport = entry.transport; } EndCall(entry, transport); if (entry.required && result == OpenResult::FAILURE) aggregate = OpenResult::FAILURE; else if (entry.required && result == OpenResult::RETRY && aggregate == OpenResult::SUCCESS) aggregate = OpenResult::RETRY; } EndPhase(); return aggregate; } bool CTransportManager::Initialize() { if (!BeginPhase(Phase::INITIALIZE, true)) return false; Entry * entries[FRAME_MAX_SINKS] = {}; const unsigned count = Entries(entries); bool success = true; for (unsigned i = 0; i < count; ++i) { Entry& entry = *entries[i]; if (!BeginCall(entry, Call::LIFECYCLE, true)) { if (entry.required) success = false; continue; } State state; std::shared_ptr transport; { CSRWSharedLock entryLock(entry.lock); state = entry.state; transport = entry.transport; } if (state == State::OPEN && !InitializeEntry(entry)) { if (entry.required) success = false; else ScheduleRetry(entry); } { CSRWSharedLock entryLock(entry.lock); transport = entry.transport; if (entry.required && entry.state != State::INITIALIZED && entry.state != State::READY) success = false; } EndCall(entry, transport); } Entry * primary = Primary(); if (success && primary) { CSRWSharedLock entryLock(primary->lock); success = primary->state == State::INITIALIZED || primary->state == State::READY; } else success = false; if (success) { CSRWExclusiveLock managerLock(m_lock); m_initialized = true; } EndPhase(); return success; } bool CTransportManager::Setup(size_t alignment) { if (!BeginPhase(Phase::SETUP, true)) return false; bool initialized = false; { CSRWExclusiveLock managerLock(m_lock); initialized = m_initialized && m_primary; if (initialized) m_alignment = alignment; } Entry * entries[FRAME_MAX_SINKS] = {}; const unsigned count = Entries(entries); bool success = initialized; for (unsigned i = 0; i < count; ++i) { Entry& entry = *entries[i]; if (!BeginCall(entry, Call::LIFECYCLE, true)) { if (entry.required) success = false; continue; } State state; std::shared_ptr transport; { CSRWSharedLock entryLock(entry.lock); state = entry.state; transport = entry.transport; } if (state != State::INITIALIZED && state != State::READY) { if (entry.required) success = false; } else if (!SetupEntry(entry, alignment)) { { CSRWSharedLock entryLock(entry.lock); state = entry.state; } if (entry.required) success = false; else if (state == State::FAILED) ScheduleRetry(entry); } { CSRWSharedLock entryLock(entry.lock); transport = entry.transport; } EndCall(entry, transport); } Entry * primary = Primary(); if (primary) { CSRWSharedLock entryLock(primary->lock); if (primary->state != State::READY || !primary->frameAdded) success = false; } else success = false; if (initialized) { CSRWExclusiveLock managerLock(m_lock); m_setup = true; } EndPhase(); return success; } CfgResult CTransportManager::Cfg( const GraphCfg& cfg, CFrameGraph& graph) { CFrameGraph next; if (!next.Begin(cfg)) return CfgResult::REJECTED; if (!BeginPhase(Phase::CFG, true)) return CfgResult::RETRY; struct Route { Entry * entry = nullptr; std::shared_ptr transport; BackendId id = 0; uint32_t epoch = 0; bool required = false; bool primary = false; bool prepared = false; bool eligible = false; FrameProfile profiles[FRAME_PROFILE_MAX] = {}; unsigned profileCount = 0; }; Route routes[FRAME_MAX_SINKS]; unsigned routeCount = 0; CfgResult result = CfgResult::ACCEPTED; Entry * entries[FRAME_MAX_SINKS] = {}; const unsigned count = Entries(entries); for (unsigned i = 0; i < count; ++i) { Entry& entry = *entries[i]; bool frameService = false; bool required = false; { CSRWSharedLock entryLock(entry.lock); frameService = (entry.config.services & TRANSPORT_SERVICE_FRAME) != 0; required = entry.required; } if (!frameService) continue; if (!BeginCall(entry, Call::CFG, true)) { if (required) { result = CfgResult::RETRY; break; } continue; } Route& route = routes[routeCount++]; route.entry = &entry; State state; bool frameAbsent = false; { CSRWSharedLock entryLock(entry.lock); route.transport = entry.transport; route.id = entry.id; route.epoch = entry.epoch; route.required = entry.required; route.primary = entry.primary; state = entry.state; frameAbsent = entry.frameAbsent; } route.eligible = route.transport && !frameAbsent && (state == State::INITIALIZED || state == State::READY); if (!route.eligible) { if (!route.required && (frameAbsent || state == State::FAILED)) continue; result = frameAbsent ? CfgResult::REJECTED : (state == State::FAILED ? CfgResult::FAILED : CfgResult::RETRY); break; } unsigned profileCount = 0; const FrameProfile * profiles = route.transport->Profiles(profileCount); if (profileCount > FRAME_PROFILE_MAX || (profileCount && !profiles)) { route.eligible = false; if (route.required) { result = CfgResult::FAILED; break; } continue; } route.profileCount = profileCount; for (unsigned profile = 0; profile < profileCount; ++profile) route.profiles[profile] = profiles[profile]; } if (result == CfgResult::ACCEPTED) for (unsigned i = 0; i < routeCount; ++i) { Route& route = routes[i]; if (!route.eligible) continue; CfgResult routeResult = CfgResult::NEXT; for (unsigned profileIndex = 0; profileIndex < route.profileCount; ++profileIndex) { const FrameProfile& profile = route.profiles[profileIndex]; FrameCfg candidate; candidate.mode = cfg.mode; candidate.adapter = cfg.adapter; candidate.width = cfg.width; candidate.height = cfg.height; candidate.profile = profile; if (!next.Can(candidate)) continue; routeResult = route.transport->Probe(candidate); if (routeResult == CfgResult::NEXT) continue; if (routeResult != CfgResult::ACCEPTED) break; routeResult = route.transport->Prepare(candidate); if (routeResult == CfgResult::NEXT) { route.transport->Abort(); continue; } if (routeResult != CfgResult::ACCEPTED) break; if (!next.Add(route.id, route.epoch, route.required, route.primary, candidate)) { route.transport->Abort(); routeResult = CfgResult::FAILED; break; } route.prepared = true; break; } if (route.prepared) continue; route.transport->Abort(); if (routeResult == CfgResult::RETRY) { result = CfgResult::RETRY; break; } if (route.required) { result = routeResult == CfgResult::NEXT ? CfgResult::REJECTED : routeResult; break; } } if (result == CfgResult::ACCEPTED && !next.Seal()) result = CfgResult::FAILED; if (result == CfgResult::ACCEPTED) { for (unsigned i = 0; i < routeCount; ++i) if (routes[i].prepared) routes[i].transport->Commit(); graph = next; } else for (unsigned i = routeCount; i > 0; --i) if (routes[i - 1].prepared) routes[i - 1].transport->Abort(); for (unsigned i = routeCount; i > 0; --i) EndCall(*routes[i - 1].entry, routes[i - 1].transport); EndPhase(); return result; } ITransport::ProcessResult CTransportManager::Process( ITransportActions& actions) { if (!BeginPhase(Phase::PROCESS, false)) { CSRWSharedLock managerLock(m_lock); return m_stopping || m_stopped ? ProcessResult::FAILURE : ProcessResult::OK; } bool initialized = false; bool setup = false; size_t alignment = 0; { CSRWSharedLock managerLock(m_lock); initialized = m_initialized; setup = m_setup; alignment = m_alignment; } const uint64_t now = GetTickCount64(); m_recovery.Tick(now); HandleServiceFailures(); Entry * entries[FRAME_MAX_SINKS] = {}; const unsigned count = Entries(entries); for (unsigned i = 0; i < count; ++i) { Entry& entry = *entries[i]; if (!BeginCall(entry, Call::PROCESS, false)) continue; RetryEntry(entry, now, initialized, setup, alignment); std::shared_ptr transport; BackendId id = 0; uint32_t epoch = 0; bool process = false; { CSRWSharedLock entryLock(entry.lock); transport = entry.transport; id = entry.id; epoch = entry.epoch; process = transport && (entry.state == State::INITIALIZED || entry.state == State::READY); } if (!process) { EndCall(entry, transport); continue; } bool setupDone = false; { CSRWSharedLock entryLock(entry.lock); setupDone = entry.setupDone; } if (setup && !setupDone) { DrainRecovery(entry, transport); if (!transport || !transport->Setup(alignment)) { HandleProcessResult(entry, ProcessResult::FAILURE); EndCall(entry, transport); continue; } CSRWExclusiveLock entryLock(entry.lock); entry.setupDone = true; } if (setup) SetupEntry(entry, alignment); bool interactions = false; { CSRWSharedLock entryLock(entry.lock); interactions = entry.controlAdded; } DrainRecovery(entry, transport); CSourceEvents sourceEvents( id, epoch, interactions, m_input, m_recovery, actions); const ProcessResult result = transport->Process(sourceEvents); DrainRecovery(entry, transport); HandleProcessResult(entry, result); EndCall(entry, transport); } EndPhase(); return ProcessResult::OK; } void CTransportManager::Stop() { const DWORD thread = GetCurrentThreadId(); bool wait = false; { CSRWExclusiveLock managerLock(m_lock); if (m_stopped) return; if (m_stopping) wait = true; else { if (m_phase != Phase::IDLE && m_phaseOwner == thread) return; m_stopping = true; ResetEvent(m_stoppedEvent); } } if (wait) { WaitForSingleObject(m_stoppedEvent, INFINITE); return; } if (!BeginPhase(Phase::STOP, true, true)) return; Entry * entries[FRAME_MAX_SINKS] = {}; const unsigned count = Entries(entries); bool failed = false; for (unsigned i = 0; i < count; ++i) { CSRWExclusiveLock entryLock(entries[i]->lock); entries[i]->stopRequested = true; entries[i]->syncPending = false; } for (unsigned i = 0; i < count; ++i) { for (;;) { HANDLE idleEvent = nullptr; { CSRWSharedLock entryLock(entries[i]->lock); if (entries[i]->call == Call::IDLE) break; idleEvent = entries[i]->idleEvent; } if (!idleEvent || WaitForSingleObject(idleEvent, INFINITE) != WAIT_OBJECT_0) { failed = true; break; } } } if (failed) { for (unsigned i = 0; i < count; ++i) { CSRWExclusiveLock entryLock(entries[i]->lock); entries[i]->stopRequested = false; } CSRWExclusiveLock managerLock(m_lock); m_stopping = false; m_phase = Phase::IDLE; m_phaseOwner = 0; SetEvent(m_phaseIdle); SetEvent(m_stoppedEvent); return; } for (unsigned i = count; i > 0; --i) { DetachRecovery(*entries[i - 1]); RemoveServices(*entries[i - 1]); } m_input.Stop(); for (unsigned i = count; i > 0; --i) { Entry& entry = *entries[i - 1]; std::shared_ptr transport; State state; { CSRWSharedLock entryLock(entry.lock); transport = entry.transport; state = entry.state; } if (transport && state != State::STOPPED) transport->Stop(); { CSRWExclusiveLock entryLock(entry.lock); entry.state = State::STOPPED; } } { CSRWExclusiveLock managerLock(m_lock); m_initialized = false; m_setup = false; m_stopped = true; m_phase = Phase::IDLE; m_phaseOwner = 0; SetEvent(m_phaseIdle); SetEvent(m_stoppedEvent); } } void CTransportManager::SyncRecovery() { { CSRWSharedLock managerLock(m_lock); if (m_stopping || m_stopped) return; } m_recovery.MarkMonitorReady(); Entry * entries[FRAME_MAX_SINKS] = {}; const unsigned count = Entries(entries); for (unsigned i = 0; i < count; ++i) { Entry& entry = *entries[i]; std::shared_ptr transport; bool call = false; { CSRWExclusiveLock entryLock(entry.lock); if (entry.stopRequested || !entry.transport || !entry.recoveryAttached || (entry.state != State::INITIALIZED && entry.state != State::READY)) continue; if (!m_recovery.ClaimSync(entry.id, entry.epoch)) continue; if (entry.call != Call::IDLE) { entry.syncPending = true; continue; } entry.call = Call::RECOVERY; entry.callOwner = GetCurrentThreadId(); ResetEvent(entry.idleEvent); transport = entry.transport; call = true; } if (call) transport->SyncRecovery(); EndCall(entry, transport); } } void CTransportManager::RecoveryStatus(uint64_t route, uint64_t session, uint32_t serial, bool active, Recovery state, uint32_t error) { RecoveryAction action; action.route = route; action.session = session; action.serial = serial; action.active = active; m_recovery.Complete(action, state, error); } bool CTransportManager::CanUseMode(const FrameMode& mode, uint32_t * requiredSizeMiB) const { if (requiredSizeMiB) *requiredSizeMiB = 0; std::shared_ptr caps[FRAME_MAX_SINKS]; unsigned capsCount = 0; Entry * entries[FRAME_MAX_SINKS] = {}; const unsigned count = Entries(entries); for (unsigned i = 0; i < count; ++i) { Entry& entry = *entries[i]; CSRWSharedLock entryLock(entry.lock); if (!entry.required || !(entry.config.services & TRANSPORT_SERVICE_FRAME)) continue; if (!entry.frameCaps) return false; caps[capsCount++] = entry.frameCaps; } if (!capsCount) return false; bool supported = true; for (unsigned i = 0; i < capsCount; ++i) { uint32_t hint = 0; if (caps[i]->CanUseMode(mode, capsCount == 1 ? &hint : nullptr)) continue; supported = false; if (requiredSizeMiB && capsCount == 1) *requiredSizeMiB = hint; } return supported; } DirectFrameBufferMemory CTransportManager::GetDirectMemory() const { CTransportManager * manager = const_cast(this); { CSRWSharedLock managerLock(m_lock); if (m_stopping || m_stopped) return DirectFrameBufferMemory {}; } Entry * primary = manager->Primary(); if (!primary) return DirectFrameBufferMemory {}; { CSRWSharedLock entryLock(primary->lock); if (primary->directMemoryValid) return primary->directMemory; } if (!manager->BeginPhase(Phase::ACCESS, true)) return DirectFrameBufferMemory {}; { CSRWSharedLock entryLock(primary->lock); if (primary->directMemoryValid) { const DirectFrameBufferMemory memory = primary->directMemory; manager->EndPhase(); return memory; } } if (!manager->BeginCall(*primary, Call::ACCESS, true)) { manager->EndPhase(); return DirectFrameBufferMemory {}; } std::shared_ptr transport; State state; { CSRWSharedLock entryLock(primary->lock); transport = primary->transport; state = primary->state; } DirectFrameBufferMemory memory; if (transport && state != State::FAILED && state != State::STOPPED) memory = transport->GetDirectMemory(); { CSRWExclusiveLock entryLock(primary->lock); primary->directMemory = memory; primary->directMemoryValid = true; primary->exposed = true; primary->exposedTransport = transport; } manager->EndCall(*primary, transport); manager->EndPhase(); return memory; } IFrameTransport& CTransportManager::Frames() { Entry * primary = Primary(); if (!primary) return m_frames; if (BeginPhase(Phase::ACCESS, true)) { Expose(*primary); EndPhase(); } return m_frames; } IControlTransport& CTransportManager::Control() { return m_control; } IInputTransport& CTransportManager::Input() { return m_input; }