[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_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_dropTransport(void);
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);
}
void lgAudio_setTransport(const LG_AudioOps * ops, void * opaque)
{
setBinding(&audio.transport, ops, opaque, transportStatusChanged);
}
void lgAudio_dropTransport(void)
static void dropBinding(AudioBinding * target)
{
LG_LOCK(audio.bindingLock);
if (!atomic_load_explicit(&audio.ready, memory_order_acquire))
@@ -4351,16 +4346,27 @@ void lgAudio_dropTransport(void)
LG_LOCK(audio.providerLock);
LG_LOCK_EXCLUSIVE(audio.activeLock);
const AudioBinding old = audio.transport;
const bool wasActive = bindingActiveNL(&audio.transport);
audio.transport = (AudioBinding) { 0 };
const bool wasActive = bindingActiveNL(target);
*target = (AudioBinding) { 0 };
LG_UNLOCK_EXCLUSIVE(audio.activeLock);
updateActive(wasActive);
LG_UNLOCK(audio.providerLock);
if (old.ops && old.ops->setStatusListener)
old.ops->setStatusListener(old.opaque, NULL, NULL);
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

View File

@@ -29,6 +29,8 @@ void lgAudio_init(void);
void lgAudio_free(void);
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_dropTransport(void);
@@ -42,6 +44,7 @@ static inline void lgAudio_init(void) {}
static inline void lgAudio_free(void) {}
static inline void lgAudio_setFallback(
const LG_AudioOps * ops, void * opaque) {}
static inline void lgAudio_dropFallback(void) {}
static inline void lgAudio_setTransport(
const LG_AudioOps * ops, void * opaque) {}
static inline void lgAudio_dropTransport(void) {}

View File

@@ -482,8 +482,8 @@ static const LG_ClipboardEventOps eventOps =
.request = eventRequest,
};
/* providerLock must be held. dropActive suppresses remote release when the
* endpoint has already disappeared; detach must always quiesce local events. */
/* providerLock must be held. dropActive suppresses all calls into an endpoint
* which has already disappeared. */
static void updateActive(bool dropActive)
{
for (;;)
@@ -501,9 +501,9 @@ static void updateActive(bool dropActive)
clipboard.active = (ClipboardBinding) { 0 };
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",
old.ops->name);
old.ops->detach(old.opaque);
@@ -687,6 +687,24 @@ void lgClipboard_setFallback(const LG_ClipboardOps * ops, void * opaque)
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)
{
setBinding(&clipboard.transport,
@@ -695,22 +713,7 @@ void lgClipboard_setTransport(const LG_ClipboardOps * ops, void * opaque)
void lgClipboard_dropTransport(void)
{
LG_LOCK(clipboard.registrationLock);
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);
dropBinding(&clipboard.transport);
}
void lgClipboard_release(void)

View File

@@ -28,6 +28,8 @@ void lgClipboard_free(void);
void lgClipboard_setLocalAvailable(bool available);
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_dropTransport(void);

View File

@@ -32,12 +32,14 @@ struct InputBinding
void * opaque;
bool available;
bool mouseAbsolute;
uint32_t generation;
uint32_t epoch;
};
static struct
{
LG_Lock bindingLock;
LG_RWLock activeLock;
uint32_t nextBindingEpoch;
struct InputBinding fallback;
struct InputBinding transport;
@@ -66,6 +68,14 @@ static struct InputBinding makeBinding(const LG_InputOps * ops,
void * opaque)
{
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)
{
.ops = ops,
@@ -73,10 +83,18 @@ static struct InputBinding makeBinding(const LG_InputOps * ops,
.available = available,
.mouseAbsolute = available && ops->mousePosition &&
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)
{
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);
}
static void updateActiveNL(void)
static void updateActiveNL(bool dropActive)
{
const struct InputBinding next =
l_input.useTransport && l_input.transport.available ?
@@ -122,13 +140,15 @@ static void updateActiveNL(void)
l_input.fallback.available ? l_input.fallback :
(struct InputBinding) { 0 };
if (next.ops == l_input.active.ops &&
next.opaque == l_input.active.opaque)
if (bindingEqual(&next, &l_input.active))
{
l_input.active = next;
return;
}
if (dropActive)
clearStateNL();
else
resetActiveNL();
l_input.active = next;
@@ -145,9 +165,7 @@ static void updateActiveNL(void)
static void updateStatusNL(struct InputBinding * binding,
const LG_InputStatus * status)
{
const bool wasActive =
l_input.active.ops == binding->ops &&
l_input.active.opaque == binding->opaque;
const bool wasActive = bindingEqual(&l_input.active, binding);
if (wasActive && !status->available)
{
@@ -160,8 +178,7 @@ static void updateStatusNL(struct InputBinding * binding,
binding->ops->mousePosition &&
binding->ops->supports(binding->opaque,
LG_INPUT_SUPPORT_MOUSE_ABSOLUTE);
binding->generation = status->generation;
updateActiveNL();
updateActiveNL(false);
}
static void fallbackStatusChanged(void * opaque,
@@ -171,7 +188,8 @@ static void fallbackStatusChanged(void * opaque,
return;
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);
LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
}
@@ -183,7 +201,8 @@ static void transportStatusChanged(void * opaque,
return;
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);
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.buttons, 0);
LG_LOCK_INIT(l_input.bindingLock);
LG_RWLOCK_INIT(l_input.activeLock);
}
@@ -207,6 +227,7 @@ void lgInput_free(void)
struct InputBinding fallback;
struct InputBinding transport;
LG_LOCK(l_input.bindingLock);
LG_LOCK_EXCLUSIVE(l_input.activeLock);
resetActiveNL();
fallback = l_input.fallback;
@@ -221,85 +242,79 @@ void lgInput_free(void)
if (transport.ops && transport.ops->setStatusListener)
transport.ops->setStatusListener(transport.opaque, NULL, NULL);
LG_UNLOCK(l_input.bindingLock);
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)
{
if (ops && !validOps(ops))
{
DEBUG_ERROR("Invalid fallback input operations");
ops = NULL;
opaque = NULL;
}
setBinding(&l_input.fallback, ops, opaque, fallbackStatusChanged);
}
const struct InputBinding next = makeBinding(ops, opaque);
struct InputBinding old;
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_dropFallback(void)
{
dropBinding(&l_input.fallback);
}
void lgInput_setTransport(const LG_InputOps * ops, void * opaque)
{
if (ops && !validOps(ops))
{
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);
setBinding(&l_input.transport, ops, opaque, transportStatusChanged);
}
void lgInput_dropTransport(void)
{
struct InputBinding old;
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);
dropBinding(&l_input.transport);
}
void lgInput_useTransport(bool enable)
{
LG_LOCK_EXCLUSIVE(l_input.activeLock);
l_input.useTransport = enable;
updateActiveNL();
updateActiveNL(false);
LG_UNLOCK_EXCLUSIVE(l_input.activeLock);
}