1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-09 11:46:49 +00:00

fix(computes): instance sync asks libvirt outside the DB transaction

refresh_instance_database opened the transaction first and then asked
libvirt for the domain list. A host that goes silent on an established
connection keeps that call waiting until keepalive gives up (30 s). On
SQLite, the default database, the open transaction holds a read lock,
so every other write of the app failed meanwhile with "database is
locked", logins (session writes) included. Reproduced in the test
setup: with a relay to a TCP compute frozen after connecting, three
session writes during the 30 s refresh each failed after 5 s.

- The connection check and listAllDomains run before the transaction,
  still under the compute lock, so create, delete and migration of the
  compute stay excluded as before.
- The transaction only applies the changes. It checks the compute row
  first: a compute deleted while libvirt was asked gets no instances
  (this ended in a foreign key error before).

A failed listing still leaves the database untouched. With the change,
the same reproduction writes the sessions at once and keeps the
instances. Both new tests fail on the previous code.
This commit is contained in:
catborise 2026-10-05 10:47:35 +03:00
parent 7e08224330
commit fbd780af02
2 changed files with 71 additions and 23 deletions

View file

@ -220,37 +220,42 @@ def refresh_instance_database(compute):
try:
with libvirt_compute_lock(compute, timeout=15.0):
with transaction.atomic():
comp_locked = Compute.objects.select_for_update().filter(pk=compute_pk).first()
if not comp_locked:
return
# libvirt is asked before the transaction: a silent host can keep a
# call waiting for 30 s, and on SQLite an open transaction makes every
# other write of the app fail meanwhile. The compute lock still keeps
# create/delete/migration of this compute out until the end.
if hasattr(compute, "status") and compute.status is False:
return
if hasattr(compute, "status") and compute.status is False:
return
try:
domains = compute.proxy.wvm.listAllDomains()
except Exception as e:
logger.warning(
"Failed to retrieve domains from compute %s (%s): %s",
getattr(compute, "name", compute),
getattr(compute, "hostname", ""),
e,
)
return
# Map libvirt UUIDs to current domain names
host_domains = {}
for d in domains:
try:
domains = compute.proxy.wvm.listAllDomains()
host_domains[d.UUIDString()] = d.name()
except Exception as e:
logger.warning(
"Failed to retrieve domains from compute %s (%s): %s",
"Error reading domain metadata from compute %s: %s",
getattr(compute, "name", compute),
getattr(compute, "hostname", ""),
e,
)
return
# Map libvirt UUIDs to current domain names
host_domains = {}
for d in domains:
try:
host_domains[d.UUIDString()] = d.name()
except Exception as e:
logger.warning(
"Error reading domain metadata from compute %s: %s",
getattr(compute, "name", compute),
e,
)
return
with transaction.atomic():
# the compute may have been deleted while libvirt was asked
comp_locked = Compute.objects.select_for_update().filter(pk=compute_pk).first()
if not comp_locked:
return
db_instances = list(Instance.objects.filter(compute=compute).select_for_update())
db_uuids = set(inst.uuid for inst in db_instances)