mirror of
https://dev.lirent.ru/Vatrog/vm-automation-signaling.git
synced 2026-06-25 20:36:36 +03:00
fix(input): re-add the input-linux bridge object idempotently
A bare object-add fails with "duplicate property '<id>'" when a prior daemon instance left the object live: its best-effort object-del on teardown has no round-trip and can race a fast restart, so device B (the relative mouse) stays orphaned and motion is not forwarded. Fire object-del for the same id before each object-add. QMP is sequential per connection, so the del is applied before the add; on a clean first attach it no-ops (DeviceNotFound, silently dropped). Re-attach is now idempotent.
This commit is contained in:
@@ -121,6 +121,9 @@ static void bridge_id(char* out, size_t cap, uint32_t ep, char ab) {
|
|||||||
snprintf(out, cap, "vmsig-in-%c-%u", ab, ep);
|
snprintf(out, cap, "vmsig-in-%c-%u", ab, ep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Best-effort object-del of a possibly-stale object id (defined below; fwd for bridge_add). */
|
||||||
|
static void bridge_del_fire(struct vmsig_adapter* a, char ab);
|
||||||
|
|
||||||
/* Add one input-linux object forwarding an evdev node into the guest. grab_all toggles the
|
/* Add one input-linux object forwarding an evdev node into the guest. grab_all toggles the
|
||||||
* device-grab for every input-linux on this endpoint (set on A only — one is enough). The
|
* device-grab for every input-linux on this endpoint (set on A only — one is enough). The
|
||||||
* reply is correlated through the existing pend[] table under VH_OP_BRIDGE_ADD and consumed
|
* reply is correlated through the existing pend[] table under VH_OP_BRIDGE_ADD and consumed
|
||||||
@@ -130,6 +133,13 @@ static int bridge_add(struct vmsig_adapter* a, char ab, const char* evdev, int g
|
|||||||
if (!p) return -1;
|
if (!p) return -1;
|
||||||
char id[32];
|
char id[32];
|
||||||
bridge_id(id, sizeof id, a->endpoint, ab);
|
bridge_id(id, sizeof id, a->endpoint, ab);
|
||||||
|
/* Idempotent re-attach: fire object-del for this id FIRST. A prior daemon instance tears the
|
||||||
|
* bridge down best-effort WITHOUT a round-trip (bridge_del_fire in vh_close), and a fast
|
||||||
|
* restart/redeploy can reach here before QEMU processed that del — leaving the object live,
|
||||||
|
* so a bare object-add fails with "duplicate property '<id>'" (observed for device B). QMP is
|
||||||
|
* sequential per connection, so this del is applied before the add below; on a clean first
|
||||||
|
* attach it just no-ops (DeviceNotFound, silently dropped — that frame carries no QMP id). */
|
||||||
|
bridge_del_fire(a, ab);
|
||||||
uint32_t qid = ++a->next_id;
|
uint32_t qid = ++a->next_id;
|
||||||
char line[320];
|
char line[320];
|
||||||
int len = snprintf(line, sizeof line,
|
int len = snprintf(line, sizeof line,
|
||||||
@@ -144,9 +154,11 @@ static int bridge_add(struct vmsig_adapter* a, char ab, const char* evdev, int g
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Best-effort object_del fired on teardown before the fd closes (see vh_close). No reply is
|
/* Best-effort object-del, no reply awaited. Fired in TWO places: on teardown before the fd
|
||||||
* awaited; QEMU also drops these objects when the VM powers off, so del matters only on a
|
* closes (vh_close) AND before every object-add (bridge_add, idempotent re-attach). QEMU drops
|
||||||
* detach without power-off (daemon restart / endpoint move). */
|
* these objects when the VM powers off, so del matters on a detach/re-attach without power-off
|
||||||
|
* (daemon restart / endpoint move). A del of an absent id no-ops (DeviceNotFound, silently
|
||||||
|
* dropped — this frame carries no QMP id, so handle_line finds no pend). */
|
||||||
static void bridge_del_fire(struct vmsig_adapter* a, char ab) {
|
static void bridge_del_fire(struct vmsig_adapter* a, char ab) {
|
||||||
char id[32];
|
char id[32];
|
||||||
bridge_id(id, sizeof id, a->endpoint, ab);
|
bridge_id(id, sizeof id, a->endpoint, ab);
|
||||||
|
|||||||
+12
-2
@@ -5,8 +5,10 @@
|
|||||||
*
|
*
|
||||||
* It also verifies the host->guest input bridge: with bridge_evdev_a/b set in cfg, on reaching
|
* It also verifies the host->guest input bridge: with bridge_evdev_a/b set in cfg, on reaching
|
||||||
* READY the seam adds two input-linux objects (A with grab_all, B without) over its own
|
* READY the seam adds two input-linux objects (A with grab_all, B without) over its own
|
||||||
* connection, with neutral per-endpoint ids and the evdev paths from cfg; the bridge replies
|
* connection, with neutral per-endpoint ids and the evdev paths from cfg; each add is preceded
|
||||||
* never surface as ACK/VM_LIFECYCLE to control; on teardown it fires object_del for both. */
|
* by an idempotent object-del of the same id (clears a stale object from a crashed/racing prior
|
||||||
|
* daemon); the bridge replies never surface as ACK/VM_LIFECYCLE to control; on teardown it
|
||||||
|
* fires object-del for both. */
|
||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
#include "vmsig.h"
|
#include "vmsig.h"
|
||||||
#include "vmhost.h" /* private cfg (CMake provides the include path) */
|
#include "vmhost.h" /* private cfg (CMake provides the include path) */
|
||||||
@@ -148,6 +150,10 @@ int main(void) {
|
|||||||
CHECK(srv_expect(c, "\"vmsig-in-b-0\""), "bridge B has neutral per-endpoint id");
|
CHECK(srv_expect(c, "\"vmsig-in-b-0\""), "bridge B has neutral per-endpoint id");
|
||||||
CHECK(srv_expect(c, EVDEV_A), "bridge A carries the cfg evdev path for A");
|
CHECK(srv_expect(c, EVDEV_A), "bridge A carries the cfg evdev path for A");
|
||||||
CHECK(srv_expect(c, EVDEV_B), "bridge B carries the cfg evdev path for B");
|
CHECK(srv_expect(c, EVDEV_B), "bridge B carries the cfg evdev path for B");
|
||||||
|
/* Idempotent re-attach: each add is preceded by an object-del of the same id. The EOF
|
||||||
|
* teardown below skips del (seam DEAD), so this object-del can ONLY originate from the
|
||||||
|
* del-before-add path. */
|
||||||
|
CHECK(srv_expect(c, "object-del"), "bridge fires object-del before add (idempotent re-attach)");
|
||||||
srv_send(c, "{\"return\": {}, \"id\": 1}\r\n"); /* ack bridge A (consumed silently) */
|
srv_send(c, "{\"return\": {}, \"id\": 1}\r\n"); /* ack bridge A (consumed silently) */
|
||||||
srv_send(c, "{\"return\": {}, \"id\": 2}\r\n"); /* ack bridge B (consumed silently) */
|
srv_send(c, "{\"return\": {}, \"id\": 2}\r\n"); /* ack bridge B (consumed silently) */
|
||||||
|
|
||||||
@@ -219,6 +225,10 @@ int main(void) {
|
|||||||
srv_send(c2, "{\"return\": {}, \"id\": 1}\r\n");
|
srv_send(c2, "{\"return\": {}, \"id\": 1}\r\n");
|
||||||
srv_send(c2, "{\"return\": {}, \"id\": 2}\r\n");
|
srv_send(c2, "{\"return\": {}, \"id\": 2}\r\n");
|
||||||
|
|
||||||
|
/* Attach already emitted object-del (del-before-add). Reset the accumulator so the
|
||||||
|
* teardown del below is verified in ISOLATION, not satisfied by the attach del. */
|
||||||
|
rx_reset();
|
||||||
|
|
||||||
/* Clean reap WITHOUT EOF: stop the loop then free (vh_close fires del). */
|
/* Clean reap WITHOUT EOF: stop the loop then free (vh_close fires del). */
|
||||||
vmsig_core_stop(core2);
|
vmsig_core_stop(core2);
|
||||||
pthread_join(th2, NULL);
|
pthread_join(th2, NULL);
|
||||||
|
|||||||
Reference in New Issue
Block a user