[client] providers: handle fallback disconnects safely

This commit is contained in:
Geoffrey McRae
2026-08-11 15:06:45 +10:00
parent 96a5cf4ce8
commit 5cf5dcfe05
6 changed files with 135 additions and 104 deletions

View File

@@ -29,6 +29,8 @@ void lgInput_init(void);
void lgInput_free(void); void lgInput_free(void);
void lgInput_setFallback(const LG_InputOps * ops, void * opaque); void lgInput_setFallback(const LG_InputOps * ops, void * opaque);
/* Drop functions remove a dead endpoint without invoking it. */
void lgInput_dropFallback(void);
void lgInput_setTransport(const LG_InputOps * ops, void * opaque); void lgInput_setTransport(const LG_InputOps * ops, void * opaque);
void lgInput_dropTransport(void); void lgInput_dropTransport(void);
void lgInput_useTransport(bool enable); void lgInput_useTransport(bool enable);

View File

@@ -4335,12 +4335,7 @@ void lgAudio_setFallback(const LG_AudioOps * ops, void * opaque)
setBinding(&audio.fallback, ops, opaque, fallbackStatusChanged); setBinding(&audio.fallback, ops, opaque, fallbackStatusChanged);
} }
void lgAudio_setTransport(const LG_AudioOps * ops, void * opaque) static void dropBinding(AudioBinding * target)
{
setBinding(&audio.transport, ops, opaque, transportStatusChanged);
}
void lgAudio_dropTransport(void)
{ {
LG_LOCK(audio.bindingLock); LG_LOCK(audio.bindingLock);
if (!atomic_load_explicit(&audio.ready, memory_order_acquire)) if (!atomic_load_explicit(&audio.ready, memory_order_acquire))
@@ -4351,16 +4346,27 @@ void lgAudio_dropTransport(void)
LG_LOCK(audio.providerLock); LG_LOCK(audio.providerLock);
LG_LOCK_EXCLUSIVE(audio.activeLock); LG_LOCK_EXCLUSIVE(audio.activeLock);
const AudioBinding old = audio.transport; const bool wasActive = bindingActiveNL(target);
const bool wasActive = bindingActiveNL(&audio.transport); *target = (AudioBinding) { 0 };
audio.transport = (AudioBinding) { 0 };
LG_UNLOCK_EXCLUSIVE(audio.activeLock); LG_UNLOCK_EXCLUSIVE(audio.activeLock);
updateActive(wasActive); updateActive(wasActive);
LG_UNLOCK(audio.providerLock); LG_UNLOCK(audio.providerLock);
if (old.ops && old.ops->setStatusListener)
old.ops->setStatusListener(old.opaque, NULL, NULL);
LG_UNLOCK(audio.bindingLock); LG_UNLOCK(audio.bindingLock);
} }
void lgAudio_dropFallback(void)
{
dropBinding(&audio.fallback);
}
void lgAudio_setTransport(const LG_AudioOps * ops, void * opaque)
{
setBinding(&audio.transport, ops, opaque, transportStatusChanged);
}
void lgAudio_dropTransport(void)
{
dropBinding(&audio.transport);
}
#endif #endif

View File

@@ -29,6 +29,8 @@ void lgAudio_init(void);
void lgAudio_free(void); void lgAudio_free(void);
void lgAudio_setFallback(const LG_AudioOps * ops, void * opaque); void lgAudio_setFallback(const LG_AudioOps * ops, void * opaque);
/* Drop functions remove a dead endpoint without invoking it. */
void lgAudio_dropFallback(void);
void lgAudio_setTransport(const LG_AudioOps * ops, void * opaque); void lgAudio_setTransport(const LG_AudioOps * ops, void * opaque);
void lgAudio_dropTransport(void); void lgAudio_dropTransport(void);
@@ -42,6 +44,7 @@ static inline void lgAudio_init(void) {}
static inline void lgAudio_free(void) {} static inline void lgAudio_free(void) {}
static inline void lgAudio_setFallback( static inline void lgAudio_setFallback(
const LG_AudioOps * ops, void * opaque) {} const LG_AudioOps * ops, void * opaque) {}
static inline void lgAudio_dropFallback(void) {}
static inline void lgAudio_setTransport( static inline void lgAudio_setTransport(
const LG_AudioOps * ops, void * opaque) {} const LG_AudioOps * ops, void * opaque) {}
static inline void lgAudio_dropTransport(void) {} static inline void lgAudio_dropTransport(void) {}

View File

@@ -482,8 +482,8 @@ static const LG_ClipboardEventOps eventOps =
.request = eventRequest, .request = eventRequest,
}; };
/* providerLock must be held. dropActive suppresses remote release when the /* providerLock must be held. dropActive suppresses all calls into an endpoint
* endpoint has already disappeared; detach must always quiesce local events. */ * which has already disappeared. */
static void updateActive(bool dropActive) static void updateActive(bool dropActive)
{ {
for (;;) for (;;)
@@ -501,9 +501,9 @@ static void updateActive(bool dropActive)
clipboard.active = (ClipboardBinding) { 0 }; clipboard.active = (ClipboardBinding) { 0 };
LG_UNLOCK_EXCLUSIVE(clipboard.activeLock); LG_UNLOCK_EXCLUSIVE(clipboard.activeLock);
if (old.ops) if (old.ops && !dropActive)
{ {
if (!dropActive && !old.ops->release(old.opaque)) if (!old.ops->release(old.opaque))
DEBUG_WARN("Failed to release Clipboard provider: %s", DEBUG_WARN("Failed to release Clipboard provider: %s",
old.ops->name); old.ops->name);
old.ops->detach(old.opaque); old.ops->detach(old.opaque);
@@ -687,6 +687,24 @@ void lgClipboard_setFallback(const LG_ClipboardOps * ops, void * opaque)
ops, opaque, fallbackStatusChanged); ops, opaque, fallbackStatusChanged);
} }
static void dropBinding(ClipboardBinding * target)
{
LG_LOCK(clipboard.registrationLock);
LG_LOCK(clipboard.providerLock);
LG_LOCK_EXCLUSIVE(clipboard.activeLock);
const bool wasActive = bindingActiveNL(target);
*target = (ClipboardBinding) { 0 };
LG_UNLOCK_EXCLUSIVE(clipboard.activeLock);
updateActive(wasActive);
LG_UNLOCK(clipboard.providerLock);
LG_UNLOCK(clipboard.registrationLock);
}
void lgClipboard_dropFallback(void)
{
dropBinding(&clipboard.fallback);
}
void lgClipboard_setTransport(const LG_ClipboardOps * ops, void * opaque) void lgClipboard_setTransport(const LG_ClipboardOps * ops, void * opaque)
{ {
setBinding(&clipboard.transport, setBinding(&clipboard.transport,
@@ -695,22 +713,7 @@ void lgClipboard_setTransport(const LG_ClipboardOps * ops, void * opaque)
void lgClipboard_dropTransport(void) void lgClipboard_dropTransport(void)
{ {
LG_LOCK(clipboard.registrationLock); dropBinding(&clipboard.transport);
LG_LOCK(clipboard.providerLock);
const ClipboardBinding old = clipboard.transport;
LG_UNLOCK(clipboard.providerLock);
if (old.ops && old.ops->setStatusListener)
old.ops->setStatusListener(old.opaque, NULL, NULL);
LG_LOCK(clipboard.providerLock);
LG_LOCK_EXCLUSIVE(clipboard.activeLock);
const bool wasActive = bindingActiveNL(&clipboard.transport);
clipboard.transport = (ClipboardBinding) { 0 };
LG_UNLOCK_EXCLUSIVE(clipboard.activeLock);
updateActive(wasActive);
LG_UNLOCK(clipboard.providerLock);
LG_UNLOCK(clipboard.registrationLock);
} }
void lgClipboard_release(void) void lgClipboard_release(void)

View File

@@ -28,6 +28,8 @@ void lgClipboard_free(void);
void lgClipboard_setLocalAvailable(bool available); void lgClipboard_setLocalAvailable(bool available);
void lgClipboard_setFallback(const LG_ClipboardOps * ops, void * opaque); void lgClipboard_setFallback(const LG_ClipboardOps * ops, void * opaque);
/* Drop functions remove a dead endpoint without invoking it. */
void lgClipboard_dropFallback(void);
void lgClipboard_setTransport(const LG_ClipboardOps * ops, void * opaque); void lgClipboard_setTransport(const LG_ClipboardOps * ops, void * opaque);
void lgClipboard_dropTransport(void); void lgClipboard_dropTransport(void);

View File

@@ -32,12 +32,14 @@ struct InputBinding
void * opaque; void * opaque;
bool available; bool available;
bool mouseAbsolute; bool mouseAbsolute;
uint32_t generation; uint32_t epoch;
}; };
static struct static struct
{ {
LG_Lock bindingLock;
LG_RWLock activeLock; LG_RWLock activeLock;
uint32_t nextBindingEpoch;
struct InputBinding fallback; struct InputBinding fallback;
struct InputBinding transport; struct InputBinding transport;
@@ -66,6 +68,14 @@ static struct InputBinding makeBinding(const LG_InputOps * ops,
void * opaque) void * opaque)
{ {
const bool available = ops && !ops->setStatusListener; const bool available = ops && !ops->setStatusListener;
uint32_t epoch = 0;
if (ops)
{
epoch = ++l_input.nextBindingEpoch;
if (!epoch)
epoch = ++l_input.nextBindingEpoch;
}
return (struct InputBinding) return (struct InputBinding)
{ {
.ops = ops, .ops = ops,
@@ -73,10 +83,18 @@ static struct InputBinding makeBinding(const LG_InputOps * ops,
.available = available, .available = available,
.mouseAbsolute = available && ops->mousePosition && .mouseAbsolute = available && ops->mousePosition &&
ops->supports(opaque, LG_INPUT_SUPPORT_MOUSE_ABSOLUTE), ops->supports(opaque, LG_INPUT_SUPPORT_MOUSE_ABSOLUTE),
.generation = 0, .epoch = epoch,
}; };
} }
static bool bindingEqual(const struct InputBinding * a,
const struct InputBinding * b)
{
return a->ops == b->ops &&
a->opaque == b->opaque &&
a->epoch == b->epoch;
}
static void releaseKeysNL(void) static void releaseKeysNL(void)
{ {
for (int key = 0; key < KEY_MAX; ++key) for (int key = 0; key < KEY_MAX; ++key)
@@ -114,7 +132,7 @@ static void clearStateNL(void)
atomic_store_explicit(&l_input.buttons, 0, memory_order_relaxed); atomic_store_explicit(&l_input.buttons, 0, memory_order_relaxed);
} }
static void updateActiveNL(void) static void updateActiveNL(bool dropActive)
{ {
const struct InputBinding next = const struct InputBinding next =
l_input.useTransport && l_input.transport.available ? l_input.useTransport && l_input.transport.available ?
@@ -122,13 +140,15 @@ static void updateActiveNL(void)
l_input.fallback.available ? l_input.fallback : l_input.fallback.available ? l_input.fallback :
(struct InputBinding) { 0 }; (struct InputBinding) { 0 };
if (next.ops == l_input.active.ops && if (bindingEqual(&next, &l_input.active))
next.opaque == l_input.active.opaque)
{ {
l_input.active = next; l_input.active = next;
return; return;
} }
if (dropActive)
clearStateNL();
else
resetActiveNL(); resetActiveNL();
l_input.active = next; l_input.active = next;
@@ -145,9 +165,7 @@ static void updateActiveNL(void)
static void updateStatusNL(struct InputBinding * binding, static void updateStatusNL(struct InputBinding * binding,
const LG_InputStatus * status) const LG_InputStatus * status)
{ {
const bool wasActive = const bool wasActive = bindingEqual(&l_input.active, binding);
l_input.active.ops == binding->ops &&
l_input.active.opaque == binding->opaque;
if (wasActive && !status->available) if (wasActive && !status->available)
{ {
@@ -160,8 +178,7 @@ static void updateStatusNL(struct InputBinding * binding,
binding->ops->mousePosition && binding->ops->mousePosition &&
binding->ops->supports(binding->opaque, binding->ops->supports(binding->opaque,
LG_INPUT_SUPPORT_MOUSE_ABSOLUTE); LG_INPUT_SUPPORT_MOUSE_ABSOLUTE);
binding->generation = status->generation; updateActiveNL(false);
updateActiveNL();
} }
static void fallbackStatusChanged(void * opaque, static void fallbackStatusChanged(void * opaque,
@@ -171,7 +188,8 @@ static void fallbackStatusChanged(void * opaque,
return; return;
LG_LOCK_EXCLUSIVE(l_input.activeLock); LG_LOCK_EXCLUSIVE(l_input.activeLock);
if (l_input.fallback.ops && l_input.fallback.opaque == opaque) if (l_input.fallback.ops &&
l_input.fallback.epoch == (uint32_t)(uintptr_t)opaque)
updateStatusNL(&l_input.fallback, status); updateStatusNL(&l_input.fallback, status);
LG_UNLOCK_EXCLUSIVE(l_input.activeLock); LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
} }
@@ -183,7 +201,8 @@ static void transportStatusChanged(void * opaque,
return; return;
LG_LOCK_EXCLUSIVE(l_input.activeLock); LG_LOCK_EXCLUSIVE(l_input.activeLock);
if (l_input.transport.ops && l_input.transport.opaque == opaque) if (l_input.transport.ops &&
l_input.transport.epoch == (uint32_t)(uintptr_t)opaque)
updateStatusNL(&l_input.transport, status); updateStatusNL(&l_input.transport, status);
LG_UNLOCK_EXCLUSIVE(l_input.activeLock); LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
} }
@@ -199,6 +218,7 @@ void lgInput_init(void)
atomic_init(&l_input.keys[key], false); atomic_init(&l_input.keys[key], false);
atomic_init(&l_input.buttons, 0); atomic_init(&l_input.buttons, 0);
LG_LOCK_INIT(l_input.bindingLock);
LG_RWLOCK_INIT(l_input.activeLock); LG_RWLOCK_INIT(l_input.activeLock);
} }
@@ -207,6 +227,7 @@ void lgInput_free(void)
struct InputBinding fallback; struct InputBinding fallback;
struct InputBinding transport; struct InputBinding transport;
LG_LOCK(l_input.bindingLock);
LG_LOCK_EXCLUSIVE(l_input.activeLock); LG_LOCK_EXCLUSIVE(l_input.activeLock);
resetActiveNL(); resetActiveNL();
fallback = l_input.fallback; fallback = l_input.fallback;
@@ -221,85 +242,79 @@ void lgInput_free(void)
if (transport.ops && transport.ops->setStatusListener) if (transport.ops && transport.ops->setStatusListener)
transport.ops->setStatusListener(transport.opaque, NULL, NULL); transport.ops->setStatusListener(transport.opaque, NULL, NULL);
LG_UNLOCK(l_input.bindingLock);
LG_RWLOCK_FREE(l_input.activeLock); LG_RWLOCK_FREE(l_input.activeLock);
LG_LOCK_FREE(l_input.bindingLock);
}
static void setBinding(struct InputBinding * target,
const LG_InputOps * ops, void * opaque, LG_InputStatusFn statusFn)
{
if (ops && !validOps(ops))
{
DEBUG_ERROR("Invalid input operations");
ops = NULL;
opaque = NULL;
}
struct InputBinding old;
LG_LOCK(l_input.bindingLock);
LG_LOCK_EXCLUSIVE(l_input.activeLock);
old = *target;
LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
if (old.ops && old.ops->setStatusListener)
old.ops->setStatusListener(old.opaque, NULL, NULL);
const struct InputBinding next = makeBinding(ops, opaque);
LG_LOCK_EXCLUSIVE(l_input.activeLock);
*target = next;
updateActiveNL(false);
LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
if (next.ops && next.ops->setStatusListener)
next.ops->setStatusListener(next.opaque, statusFn,
(void *)(uintptr_t)next.epoch);
LG_UNLOCK(l_input.bindingLock);
}
static void dropBinding(struct InputBinding * target)
{
LG_LOCK(l_input.bindingLock);
LG_LOCK_EXCLUSIVE(l_input.activeLock);
const bool wasActive = bindingEqual(&l_input.active, target);
*target = (struct InputBinding) { 0 };
updateActiveNL(wasActive);
LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
LG_UNLOCK(l_input.bindingLock);
} }
void lgInput_setFallback(const LG_InputOps * ops, void * opaque) void lgInput_setFallback(const LG_InputOps * ops, void * opaque)
{ {
if (ops && !validOps(ops)) setBinding(&l_input.fallback, ops, opaque, fallbackStatusChanged);
{ }
DEBUG_ERROR("Invalid fallback input operations");
ops = NULL;
opaque = NULL;
}
const struct InputBinding next = makeBinding(ops, opaque); void lgInput_dropFallback(void)
struct InputBinding old; {
dropBinding(&l_input.fallback);
LG_LOCK_EXCLUSIVE(l_input.activeLock);
old = l_input.fallback;
l_input.fallback = next;
updateActiveNL();
LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
if (old.ops && old.ops->setStatusListener)
old.ops->setStatusListener(old.opaque, NULL, NULL);
if (next.ops && next.ops->setStatusListener)
next.ops->setStatusListener(next.opaque,
fallbackStatusChanged, next.opaque);
} }
void lgInput_setTransport(const LG_InputOps * ops, void * opaque) void lgInput_setTransport(const LG_InputOps * ops, void * opaque)
{ {
if (ops && !validOps(ops)) setBinding(&l_input.transport, ops, opaque, transportStatusChanged);
{
DEBUG_ERROR("Invalid transport input operations");
ops = NULL;
opaque = NULL;
}
const struct InputBinding next = makeBinding(ops, opaque);
struct InputBinding old;
LG_LOCK_EXCLUSIVE(l_input.activeLock);
old = l_input.transport;
l_input.transport = next;
updateActiveNL();
LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
if (old.ops && old.ops->setStatusListener)
old.ops->setStatusListener(old.opaque, NULL, NULL);
if (next.ops && next.ops->setStatusListener)
next.ops->setStatusListener(next.opaque,
transportStatusChanged, next.opaque);
} }
void lgInput_dropTransport(void) void lgInput_dropTransport(void)
{ {
struct InputBinding old; dropBinding(&l_input.transport);
LG_LOCK_EXCLUSIVE(l_input.activeLock);
old = l_input.transport;
if (l_input.useTransport && l_input.active.ops == l_input.transport.ops &&
l_input.active.opaque == l_input.transport.opaque)
{
l_input.active = (struct InputBinding) { 0 };
clearStateNL();
}
l_input.transport = (struct InputBinding) { 0 };
updateActiveNL();
LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
if (old.ops && old.ops->setStatusListener)
old.ops->setStatusListener(old.opaque, NULL, NULL);
} }
void lgInput_useTransport(bool enable) void lgInput_useTransport(bool enable)
{ {
LG_LOCK_EXCLUSIVE(l_input.activeLock); LG_LOCK_EXCLUSIVE(l_input.activeLock);
l_input.useTransport = enable; l_input.useTransport = enable;
updateActiveNL(); updateActiveNL(false);
LG_UNLOCK_EXCLUSIVE(l_input.activeLock); LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
} }