1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-10 04:06:47 +00:00

fix(computes): a VM moved between computes keeps its owners on PostgreSQL

Two computes decide the owners of the same UUID: the one the VM left
buries them in a tombstone (or a compute delete does), the one it went
to restores them. On PostgreSQL the two ran at once: the restore still
saw the old row of the uncommitted bury, skipped, and the VM ended up
without owners while the tombstone stayed. SQLite was never affected:
its IMMEDIATE transactions take the write lock at the start.

The reconcile and the compute delete now take one transaction-level
advisory lock (pg_advisory_xact_lock) when their database step starts,
so ownership decisions run one after another on PostgreSQL as they do
on SQLite. Reading libvirt stays outside the lock.
This commit is contained in:
catborise 2026-10-23 15:07:45 +03:00
parent 291690bb20
commit addc7ccfcb
3 changed files with 99 additions and 2 deletions

View file

@ -5,7 +5,7 @@ from datetime import timedelta
from accounts.models import UserInstance
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import transaction
from django.db import connection, transaction
from django.utils import timezone
from computes.models import Compute
from instances.models import Instance, InstanceTombstone
@ -46,6 +46,19 @@ def libvirt_instance_lock(instance):
yield
# pg_advisory_xact_lock key of the ownership step ("wvc_own")
OWNERSHIP_LOCK = 0x7776635F6F776E
def lock_ownership():
"""Hold the ownership step of every compute until this transaction ends:
a bury and a restore of the same UUID on two computes must not see each
other's work half done. SQLite's IMMEDIATE transactions already do this."""
if connection.vendor == "postgresql":
with connection.cursor() as cursor:
cursor.execute("SELECT pg_advisory_xact_lock(%s)", [OWNERSHIP_LOCK])
def _log(inst, what):
write("-", inst.compute.name, "", f"VM {inst.name} ({inst.uuid}): {what}", True)
@ -170,6 +183,7 @@ def refresh_instance_database(compute):
return
with transaction.atomic():
lock_ownership()
# 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: