[idd] transport: configure active instances

This commit is contained in:
Geoffrey McRae
2026-08-12 23:26:26 +10:00
parent 4768396180
commit c601468871
12 changed files with 621 additions and 53 deletions

View File

@@ -238,29 +238,34 @@ void CTransportManager::EndCall(Entry& entry,
}
}
bool CTransportManager::Add(BackendId id, const char * name, bool required,
bool primary, CreateFn create)
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 || !id || !name || !create ||
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 == id)
if (m_entries[i]->id == config.id)
return false;
std::unique_ptr<Entry> entry(new (std::nothrow) Entry);
if (!entry || !entry->idleEvent)
return false;
entry->id = id;
entry->name = name;
entry->required = required;
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);
@@ -273,15 +278,17 @@ ITransport::OpenResult CTransportManager::OpenEntry(Entry& entry)
{
std::shared_ptr<ITransport> transport;
CreateFn create = nullptr;
TransportInstance config;
{
CSRWSharedLock entryLock(entry.lock);
transport = entry.transport;
create = entry.create;
config = entry.config;
}
if (!transport)
{
std::unique_ptr<ITransport> created = create();
std::unique_ptr<ITransport> created = create(config);
transport.reset(created.release());
CSRWExclusiveLock entryLock(entry.lock);
entry.transport = transport;
@@ -353,6 +360,8 @@ bool CTransportManager::AddServices(Entry& entry)
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;
@@ -368,6 +377,8 @@ bool CTransportManager::AddServices(Entry& entry)
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;
@@ -380,14 +391,16 @@ bool CTransportManager::AddServices(Entry& entry)
}
if (!transport)
return !primary;
return !required;
const uint64_t now = GetTickCount64();
const bool attach = now >= retryAt;
bool controlRetry = false;
bool frameRetry = false;
bool inputRetry = false;
if (attach && !controlAdded && !controlFailed && !controlAbsent)
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))
@@ -406,7 +419,9 @@ bool CTransportManager::AddServices(Entry& entry)
}
}
if (attach && !frameAdded && !frameAbsent)
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))
@@ -425,7 +440,9 @@ bool CTransportManager::AddServices(Entry& entry)
}
}
if (attach && !inputAdded && !inputFailed && !inputAbsent)
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))
@@ -450,7 +467,11 @@ bool CTransportManager::AddServices(Entry& entry)
entry.serviceRetryAt = now + SERVICE_RETRY_DELAY_MS;
}
return !primary || frameAdded;
const bool servicesReady =
(!(services & TRANSPORT_SERVICE_FRAME) || frameAdded) &&
(!(services & TRANSPORT_SERVICE_CONTROL) || controlAdded) &&
(!(services & TRANSPORT_SERVICE_INPUT) || inputAdded);
return !required || servicesReady;
}
void CTransportManager::HandleServiceFailures()
@@ -650,26 +671,25 @@ void CTransportManager::HandleProcessResult(
bool exposed = false;
bool primary = false;
bool required = false;
const char * name = nullptr;
std::wstring name;
{
CSRWSharedLock entryLock(entry.lock);
exposed = entry.exposed;
primary = entry.primary;
required = entry.required;
name = entry.name;
}
if (primary && exposed)
{
(void)name;
DEBUG_WARN("Transport %s requested a restart while its frame interfaces "
"are active", name);
return;
name = entry.config.kind;
}
RemoveServices(entry);
if (result == ProcessResult::RETRY || !required)
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;
@@ -774,6 +794,9 @@ bool CTransportManager::Initialize()
{
CSRWSharedLock entryLock(entry.lock);
transport = entry.transport;
if (entry.required && entry.state != State::INITIALIZED &&
entry.state != State::READY)
success = false;
}
EndCall(entry, transport);
}
@@ -818,7 +841,7 @@ bool CTransportManager::Setup(size_t alignment)
Entry& entry = *entries[i];
if (!BeginCall(entry, Call::LIFECYCLE, true))
{
if (entry.primary)
if (entry.required)
success = false;
continue;
}
@@ -831,14 +854,18 @@ bool CTransportManager::Setup(size_t alignment)
transport = entry.transport;
}
if ((state == State::INITIALIZED || state == State::READY) &&
!SetupEntry(entry, alignment))
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.primary)
if (entry.required)
success = false;
else if (state == State::FAILED)
ScheduleRetry(entry);

View File

@@ -25,13 +25,18 @@
#include "transport/CFrameHub.h"
#include "transport/CInputHub.h"
#include "transport/ITransport.h"
#include "transport/TransportConfig.h"
#include <memory>
static_assert(TRANSPORT_MAX_INSTANCES == FRAME_MAX_SINKS,
"The transport and frame limits must match");
class CTransportManager final
{
public:
using CreateFn = std::unique_ptr<ITransport> (*)();
using CreateFn = std::unique_ptr<ITransport> (*)(
const TransportInstance& config);
using OpenResult = ITransport::OpenResult;
using ProcessResult = ITransport::ProcessResult;
using Recovery = ITransport::Recovery;
@@ -90,7 +95,7 @@ private:
DWORD callOwner = 0;
bool stopRequested = false;
BackendId id = 0;
const char * name = nullptr;
TransportInstance config;
bool required = false;
bool primary = false;
CreateFn create = nullptr;
@@ -166,8 +171,7 @@ public:
CTransportManager(const CTransportManager&) = delete;
CTransportManager& operator=(const CTransportManager&) = delete;
bool Add(BackendId id, const char * name, bool required, bool primary,
CreateFn create);
bool Add(TransportInstance config, bool primary, CreateFn create);
OpenResult Open();
bool Initialize();

View File

@@ -22,6 +22,7 @@
#include "transport/DirectFrameBufferMemory.h"
#include "transport/FrameMemoryLimits.h"
#include "transport/TransportConfig.h"
#include <stddef.h>
#include <stdint.h>
@@ -30,8 +31,6 @@ class IControlSink;
class IFrameSink;
class IInputSource;
using BackendId = uint32_t;
struct SourceKey
{
BackendId backend = 0;

View File

@@ -0,0 +1,373 @@
/**
* 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/TransportConfig.h"
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <wctype.h>
namespace
{
enum Field : unsigned
{
FIELD_ID = 1U << 0,
FIELD_KIND = 1U << 1,
FIELD_ENABLED = 1U << 2,
FIELD_REQUIRED = 1U << 3,
FIELD_SERVICES = 1U << 4,
FIELD_PRIORITY = 1U << 5,
FIELD_SETTINGS = 1U << 6,
};
std::wstring Trim(const std::wstring& value)
{
size_t begin = 0;
size_t end = value.size();
while (begin < end && iswspace(value[begin]))
++begin;
while (end > begin && iswspace(value[end - 1]))
--end;
return value.substr(begin, end - begin);
}
bool Equal(const std::wstring& left, const wchar_t * right)
{
if (!right)
return false;
size_t index = 0;
for (; index < left.size() && right[index]; ++index)
if (towlower(left[index]) != towlower(right[index]))
return false;
return index == left.size() && !right[index];
}
bool ParseBool(const std::wstring& value, bool& result)
{
if (Equal(value, L"true") || value == L"1")
{
result = true;
return true;
}
if (Equal(value, L"false") || value == L"0")
{
result = false;
return true;
}
return false;
}
bool ParseId(const std::wstring& value, BackendId& result)
{
if (value.empty() || value[0] == L'-')
return false;
wchar_t * end = nullptr;
errno = 0;
const unsigned long long parsed = wcstoull(value.c_str(), &end, 10);
if (errno == ERANGE || !end || *end || !parsed || parsed > UINT32_MAX)
return false;
result = static_cast<BackendId>(parsed);
return true;
}
bool ParsePriority(const std::wstring& value, int32_t& result)
{
if (value.empty())
return false;
wchar_t * end = nullptr;
errno = 0;
const long long parsed = wcstoll(value.c_str(), &end, 10);
if (errno == ERANGE || !end || *end || parsed < INT32_MIN ||
parsed > INT32_MAX)
return false;
result = static_cast<int32_t>(parsed);
return true;
}
bool ParseKind(const std::wstring& value, std::wstring& result)
{
if (value.empty() || value.size() > 32)
return false;
for (wchar_t character : value)
if (!iswalnum(character) && character != L'_' && character != L'-')
return false;
result = value;
return true;
}
bool AddService(const std::wstring& name, uint32_t& services)
{
uint32_t service = 0;
if (Equal(name, L"frame"))
service = TRANSPORT_SERVICE_FRAME;
else if (Equal(name, L"control"))
service = TRANSPORT_SERVICE_CONTROL;
else if (Equal(name, L"input"))
service = TRANSPORT_SERVICE_INPUT;
else
return false;
if (services & service)
return false;
services |= service;
return true;
}
bool ParseServices(const std::wstring& value, uint32_t& services)
{
if (Equal(value, L"all"))
{
services = TRANSPORT_SERVICE_ALL;
return true;
}
if (Equal(value, L"none"))
{
services = 0;
return true;
}
services = 0;
size_t begin = 0;
while (begin < value.size())
{
const size_t separator = value.find(L',', begin);
const size_t end = separator == std::wstring::npos ?
value.size() : separator;
if (!AddService(Trim(value.substr(begin, end - begin)), services))
return false;
if (separator == std::wstring::npos)
return true;
begin = separator + 1;
}
return false;
}
bool SetField(const std::wstring& key, const std::wstring& value,
TransportInstance& instance, unsigned& fields)
{
unsigned field = 0;
bool valid = false;
if (Equal(key, L"id"))
{
field = FIELD_ID;
valid = ParseId(Trim(value), instance.id);
}
else if (Equal(key, L"kind"))
{
field = FIELD_KIND;
valid = ParseKind(Trim(value), instance.kind);
}
else if (Equal(key, L"enabled"))
{
field = FIELD_ENABLED;
valid = ParseBool(Trim(value), instance.enabled);
}
else if (Equal(key, L"required"))
{
field = FIELD_REQUIRED;
valid = ParseBool(Trim(value), instance.required);
}
else if (Equal(key, L"services"))
{
field = FIELD_SERVICES;
valid = ParseServices(Trim(value), instance.services);
}
else if (Equal(key, L"priority"))
{
field = FIELD_PRIORITY;
valid = ParsePriority(Trim(value), instance.priority);
}
if (!field || (fields & field) || !valid)
return false;
fields |= field;
return true;
}
bool ParseInstance(const std::wstring& source,
TransportInstance& instance)
{
const std::wstring& line = source;
if (Trim(line).empty() || line.size() > 4096)
return false;
unsigned fields = 0;
size_t begin = 0;
while (begin < line.size())
{
const size_t separator = line.find(L';', begin);
const size_t end = separator == std::wstring::npos ?
line.size() : separator;
const std::wstring field = line.substr(begin, end - begin);
const size_t equals = field.find(L'=');
if (equals == std::wstring::npos)
return false;
const std::wstring key = Trim(field.substr(0, equals));
if (Equal(key, L"settings"))
{
if (fields & FIELD_SETTINGS)
return false;
instance.settings = line.substr(begin + equals + 1);
fields |= FIELD_SETTINGS;
begin = line.size();
break;
}
if (!SetField(key, field.substr(equals + 1), instance, fields))
return false;
if (separator == std::wstring::npos)
break;
begin = separator + 1;
if (begin == line.size())
return false;
}
return (fields & (FIELD_ID | FIELD_KIND)) ==
(FIELD_ID | FIELD_KIND);
}
int FindKind(const std::wstring& name,
const TransportKind * kinds, unsigned kindCount)
{
for (unsigned i = 0; i < kindCount; ++i)
if (kinds[i].name && Equal(name, kinds[i].name))
return static_cast<int>(i);
return -1;
}
bool TryResolve(const TransportInstances& instances,
const TransportKind * kinds, unsigned kindCount,
ResolvedTransportInstances& resolved)
{
if (!kinds || !kindCount || instances.empty() ||
instances.size() > TRANSPORT_MAX_INSTANCES)
return false;
ResolvedTransportInstances result;
int primary = -1;
for (const TransportInstance& instance : instances)
{
if (!instance.id || instance.kind.empty() ||
(instance.services & ~TRANSPORT_SERVICE_ALL))
return false;
for (const TransportInstance& other : instances)
if (&other != &instance && other.id == instance.id)
return false;
const int kindIndex = FindKind(instance.kind, kinds, kindCount);
if (kindIndex < 0 || !kinds[kindIndex].activeLimit)
return false;
if (!instance.enabled)
continue;
unsigned active = 0;
for (const ResolvedTransportInstance& existing : result)
if (existing.kindIndex == static_cast<unsigned>(kindIndex))
++active;
if (active == kinds[kindIndex].activeLimit ||
result.size() == TRANSPORT_MAX_INSTANCES)
return false;
ResolvedTransportInstance selected;
selected.config = instance;
selected.kindIndex = static_cast<unsigned>(kindIndex);
if (instance.required &&
(instance.services & TRANSPORT_SERVICE_FRAME) &&
(primary < 0 || instance.priority >
result[primary].config.priority))
primary = static_cast<int>(result.size());
result.push_back(selected);
}
if (primary < 0)
return false;
result[primary].primary = true;
resolved.swap(result);
return true;
}
}
TransportInstances DefaultTransportInstances()
{
TransportInstance instance;
instance.id = 1;
instance.kind = L"LGMP";
instance.enabled = true;
instance.required = true;
instance.services = TRANSPORT_SERVICE_ALL;
instance.priority = 0;
TransportInstances instances;
instances.push_back(instance);
return instances;
}
bool ParseTransportInstances(const std::vector<std::wstring>& entries,
TransportInstances& instances)
{
TransportInstances parsed;
if (entries.empty() || entries.size() > TRANSPORT_MAX_INSTANCES)
return false;
bool hasRequiredFrame = false;
for (const std::wstring& entry : entries)
{
TransportInstance instance;
if (!ParseInstance(entry, instance))
return false;
for (const TransportInstance& existing : parsed)
if (existing.id == instance.id)
return false;
if (instance.enabled && instance.required &&
(instance.services & TRANSPORT_SERVICE_FRAME))
hasRequiredFrame = true;
parsed.push_back(instance);
}
if (!hasRequiredFrame)
return false;
instances.swap(parsed);
return true;
}
bool ResolveTransportInstances(const TransportInstances& configured,
const TransportKind * kinds, unsigned kindCount,
ResolvedTransportInstances& resolved, bool& usedDefaults)
{
usedDefaults = false;
if (TryResolve(configured, kinds, kindCount, resolved))
return true;
usedDefaults = true;
const TransportInstances defaults = DefaultTransportInstances();
if (TryResolve(defaults, kinds, kindCount, resolved))
return true;
resolved.clear();
return false;
}

View File

@@ -0,0 +1,74 @@
/**
* 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
*/
#pragma once
#include <stdint.h>
#include <string>
#include <vector>
using BackendId = uint32_t;
enum TransportService : uint32_t
{
TRANSPORT_SERVICE_FRAME = 1U << 0,
TRANSPORT_SERVICE_CONTROL = 1U << 1,
TRANSPORT_SERVICE_INPUT = 1U << 2,
TRANSPORT_SERVICE_ALL = TRANSPORT_SERVICE_FRAME |
TRANSPORT_SERVICE_CONTROL | TRANSPORT_SERVICE_INPUT,
};
struct TransportInstance
{
BackendId id = 0;
std::wstring kind;
bool enabled = true;
bool required = false;
uint32_t services = TRANSPORT_SERVICE_ALL;
int32_t priority = 0;
std::wstring settings;
};
using TransportInstances = std::vector<TransportInstance>;
static const unsigned TRANSPORT_MAX_INSTANCES = 8;
struct TransportKind
{
const wchar_t * name;
unsigned activeLimit;
};
struct ResolvedTransportInstance
{
TransportInstance config;
unsigned kindIndex = 0;
bool primary = false;
};
using ResolvedTransportInstances =
std::vector<ResolvedTransportInstance>;
TransportInstances DefaultTransportInstances();
bool ParseTransportInstances(const std::vector<std::wstring>& entries,
TransportInstances& instances);
bool ResolveTransportInstances(const TransportInstances& configured,
const TransportKind * kinds, unsigned kindCount,
ResolvedTransportInstances& resolved, bool& usedDefaults);

View File

@@ -20,21 +20,66 @@
#include "transport/TransportFactory.h"
#include "CDebug.h"
#include "config/CSettings.h"
#include "transport/CTransportManager.h"
#include "transport/lgmp/CLGMPTransport.h"
#include <new>
static std::unique_ptr<ITransport> CreateLGMP()
namespace
{
return std::unique_ptr<ITransport>(new (std::nothrow) CLGMPTransport());
struct Provider
{
CTransportManager::CreateFn create;
};
std::unique_ptr<ITransport> CreateLGMP(
const TransportInstance& config)
{
return std::unique_ptr<ITransport>(
new (std::nothrow) CLGMPTransport(config));
}
const TransportKind KINDS[] =
{
{ L"LGMP", 1 },
};
const Provider PROVIDERS[] =
{
{ CreateLGMP },
};
static_assert(ARRAYSIZE(KINDS) == ARRAYSIZE(PROVIDERS),
"Every transport kind must have a provider");
std::unique_ptr<CTransportManager> Build(
const ResolvedTransportInstances& instances)
{
std::unique_ptr<CTransportManager> manager(
new (std::nothrow) CTransportManager());
if (!manager)
return std::unique_ptr<CTransportManager>();
for (const ResolvedTransportInstance& instance : instances)
if (!manager->Add(instance.config, instance.primary,
PROVIDERS[instance.kindIndex].create))
return std::unique_ptr<CTransportManager>();
return manager;
}
}
std::unique_ptr<CTransportManager> CreateTransport()
{
std::unique_ptr<CTransportManager> manager(
new (std::nothrow) CTransportManager());
if (!manager || !manager->Add(1, "LGMP", true, true, CreateLGMP))
TransportInstances instances = g_settings.LoadTransportInstances();
ResolvedTransportInstances resolved;
bool usedDefaults = false;
if (!ResolveTransportInstances(instances, KINDS, ARRAYSIZE(KINDS),
resolved, usedDefaults))
return std::unique_ptr<CTransportManager>();
return manager;
if (usedDefaults)
DEBUG_WARN("Unsupported transport instance configuration; using defaults");
return Build(resolved);
}

View File

@@ -48,7 +48,8 @@ static bool TranslateFrameScheduleFlags(
return true;
}
CLGMPTransport::CLGMPTransport() :
CLGMPTransport::CLGMPTransport(const TransportInstance& config) :
m_config(config),
m_control(m_host),
m_frames(m_host, m_ivshmem),
m_input(m_host)

View File

@@ -33,6 +33,7 @@
class CLGMPTransport final : public ITransport
{
private:
TransportInstance m_config;
// Keep this declaration order. Destruction must release frame and control
// allocations before the LGMP host and its IVSHMEM mapping are destroyed.
CIVSHMEM m_ivshmem;
@@ -44,7 +45,7 @@ private:
std::atomic<bool> m_ready = false;
public:
CLGMPTransport();
explicit CLGMPTransport(const TransportInstance& config);
~CLGMPTransport() override = default;
CLGMPTransport(const CLGMPTransport&) = delete;