我运行 2 个 goroutines 来修改 VM 并在同一实例上使用 libvirt-go 包删除相同的 VM,会发生什么情况?

What happens i run 2 goroutines to modify a VM and destory the same VM using libvirt-go package on the same instance?

提问人:swastik sarkar 提问时间:10/22/2023 更新时间:10/23/2023 访问量:35

问:

众所周知,libvirt 是线程安全的。 但是,同时运行两个 goroutine,例如修改和删除 VM,会使其处于不明确的状态。libvirt 如何决定 goroutines 的执行顺序?

这是我尝试过的代码:

package main

import (
    "fmt"

    "github.com/libvirt/libvirt-go"
)

func main() {
    conn, err := libvirt.NewConnect("qemu:///system")
    if err != nil {
        fmt.Printf("Failed to connect to libvirt: %v\n", err)
        return
    }
    defer conn.Close()

    // Create a new VM
    domainXML := `
        <domain type='kvm'>
            <name>myvm</name>
            <memory unit='KiB'>1048576</memory>
            <vcpu placement='static'>1</vcpu>
            <os>
                <type arch='x86_64' machine='pc-i440fx-2.9'>hvm</type>
                <boot dev='hd'/>
            </os>
            <devices>
                <disk type='file' device='disk'>
                    <driver name='qemu' type='qcow2'/>
                    <source file='path/to/disk'/>
                    <target dev='vda' bus='virtio'/>
                    <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
                </disk>
            </devices>
        </domain>`

    dom, err := createVM(conn, domainXML)
    if err != nil {
        fmt.Printf("Failed to create VM: %v\n", err)
        return
    }

    go modifyVMMemory(dom, 2*1024*1024) // 2 GiB

    go deleteVM(dom)

}

func createVM(conn *libvirt.Connect, domainXML string) (*libvirt.Domain, error) {
    dom, err := conn.DomainCreateXML(domainXML, 0)
    if err != nil {
        return nil, err
    }
    return dom, nil
}

func modifyVMMemory(dom *libvirt.Domain, newMemory uint64) error {
    err := dom.SetMaxMemory(newMemory)
    if err != nil {
        return err
    }
    fmt.Print("Modified VM")
    return nil
}

func deleteVM(dom *libvirt.Domain) error {
    err := dom.Destroy()
    if err != nil {
        return err
    }

    err = dom.Undefine()
    if err != nil {
        return err
    }

    fmt.Print("Deleted VM")

    return nil
}

程序成功完成,因此域被销毁并可以重新创建 但再次运行此操作会导致以下错误:

virError(Code=9, Domain=20, Message='operation failed: domain 'myvm' already exists with uuid 32c25acb-a4c5-4bfd-b2f5-f07b3d9b8eea')
kvm libvirt

评论


答:

1赞 Hamed N. 10/23/2023 #1

您正在运行并在 goroutine 中运行,并在不检查错误的情况下返回错误。modifyVMMemorydeleteVM

errCh := make (chan error, 1)
go func () {
    err := modifyVMMemory(dom, 2*1024*1024) // 2 GiB
    errCh <- err
}()

err := <- errCh
if err != nil {
    // handle the error
}

go func() {
    err := deleteVM(dom)
    errCh <- err
}()

err = <-errCh
if err != nil {
    // handle the error
}

此外,胎面安全库并不一定意味着底层软件(如 qemu)是胎面安全的。

评论

0赞 swastik sarkar 10/26/2023
感谢您的回答,您是对的,我没有正确处理错误。
1赞 DanielB 10/23/2023 #2

众所周知,libvirt 是线程安全的。但是,同时运行两个 goroutine,例如修改和删除 VM,会使其处于不明确的状态。libvirt 如何决定 goroutines 的执行顺序?

线程安全只是意味着当多个线程同时使用同一连接时,代码不会出现内存损坏问题。

你将得到的语义行为仍然是不确定的。

libvirt QEMU/KVM 驱动程序在客户端应用程序和(或)守护程序之间使用 RPC 层。因此,首先你有非确定性,其中 Goroutine 首先运行。当 API 通过 CGo 调用 C 库时,它们将被锁定到本机操作系统线程,然后这些线程将在内部同步,以决定哪个 API 首先将其 RPC 消息放在网络上。在守护进程中,还有许多线程,RPC 消息是名义上处理的 FIFO,但是,内部的 API 逻辑仍然会争相获取锁,因此在与 QEMU 通信/交互时增加了更多的非确定性。基本上,您的和 API 调用可以按任一顺序运行。如果您需要有保证的订购,则需要在您的应用程序中序列化它们,以便您只在完成后才调用它们libvirtdvirtqemudlibvirt-go-moduleibvirt.solibvirt.solibvirtdlibvirtdSetMaxMemoryDestroyDestroySetMaxMemory

最后,IIUC 你的 Go 代码并不健壮,因为该方法会生成两个 goroutine,但不会等待它们中的任何一个完成。IOW,Go 进程很可能在任何一个 goroutine 完全运行之前就已经退出了。这可能就是你收到有关 VM 已存在的错误消息的原因 - 在进程退出之前,goroutine 从未运行过。main()deleteVM

评论

0赞 swastik sarkar 10/26/2023
感谢您的回答,您是对的,我没有使 go 代码足够健壮。我现在了解了工作原理,所以我不需要信号量来获取连接,而是使用信号量进行销毁和修改等操作。右?仅供参考,我打算使用信号量来使这些操作可序列化。libvirt