libvirt/virEvent / GLib / RPC socket

用 GLib 处理事件,并让连接清理及时运行

libvirt 把 FD 和定时器接到 GLib 主循环,将停止监听与释放对象分开,并提高连接清理任务的优先级,避免资源用尽时清理一直排不上。

v10.10.0并发与一致性核验 2026-10-09

工程问题#

移除 socket 监听时,调用方可能还持有 socket 锁,立即执行释放回调会再次请求同一把锁。把释放延后也有陷阱:当 FD 达到上限,accept() 持续失败,监听 socket 一直可读,低优先级的清理便可能一直没有机会执行。

实际实现#

统一 virEvent API 由 GLib 的 GSource 实现。Remove 先停止事件源、标记 removed,再安排高优先级 idle 回调释放 opaque。释放回调不占着 eventlock 执行;RPC socket 最后归还事件循环持有的引用。

处理流程#

  1. 通过统一 virEvent API 注册 FD 和定时器,由 GSource 交给主循环调度
  2. 删除时先停止 GSource 并标记 removed,后续 Update 不再找到该监听
  3. 以 G_PRIORITY_HIGH 安排一次清理,在 eventlock 外执行释放回调
  4. 归还 socket 的事件引用;最后一个引用消失时释放对象及其 FD

源码片段#

固定版本 · 原文摘录
src/util/vireventglib.cv10.10.0 · L490–L502
查看原文件
490static gpointer virEventGLibRegisterOnce(gpointer data G_GNUC_UNUSED)491{492    eventlock = g_new0(GMutex, 1);493    timeouts = g_ptr_array_new_with_free_func(g_free);494    handles = g_ptr_array_new_with_free_func(g_free);495    virEventRegisterImpl(virEventGLibHandleAdd,496                         virEventGLibHandleUpdate,497                         virEventGLibHandleRemove,498                         virEventGLibTimeoutAdd,499                         virEventGLibTimeoutUpdate,500                         virEventGLibTimeoutRemove);501    return NULL;502}
virEventGLibRegisterOnce

把文件描述符和定时器的增、改、删六个操作接到统一 virEvent API。调用方仍使用 libvirt 的接口;GLib 是这个固定版本的默认实现,不是公开 API 对未来后端的承诺。

src/util/vireventglibwatch.cv10.10.0 · L236–L252
查看原文件
236GSource *237virEventGLibAddSocketWatch(int fd,238                           GIOCondition condition,239                           GMainContext *context,240                           virEventGLibSocketFunc func,241                           gpointer opaque,242                           GDestroyNotify notify)243{244    GSource *source = NULL;245246    source = virEventGLibCreateSocketWatch(fd, condition);247    g_source_set_callback(source, (GSourceFunc)func, opaque, notify);248249    g_source_attach(source, context);250251    return source;252}
virEventGLibAddSocketWatch

创建 socket 对应的 GSource,设置回调后挂到指定 GMainContext。默认 handle 实现传入 NULL,使用全局默认 context;底层仍登记 GPollFD,不能把换成 GLib 理解为不再轮询或天然更快。

src/rpc/virnetsocket.cv10.10.0 · L2194–L2209
查看原文件
2194void virNetSocketRemoveIOCallback(virNetSocket *sock)2195{2196    virObjectLock(sock);21972198    if (sock->watch < 0) {2199        VIR_DEBUG("Watch not registered on socket %p", sock);2200        virObjectUnlock(sock);2201        return;2202    }22032204    virEventRemoveHandle(sock->watch);2205    /* Don't unref @sock, it's done via eventloop callback. */2206    sock->watch = -1;22072208    virObjectUnlock(sock);2209}
virNetSocketRemoveIOCallback

移除监听时仍持有 socket 锁。如果这里同步调用释放回调,后者再次锁定同一个 socket 就会遇到重入锁问题。注释明确把事件持有的引用留给事件循环回调释放。

src/util/vireventglib.cv10.10.0 · L277–L296
查看原文件
277    if (data->source != NULL) {278        g_source_destroy(data->source);279        vir_g_source_unref(data->source, NULL);280        data->source = NULL;281        data->events = 0;282    }283284    /* since the actual watch deletion is done asynchronously, a handleUpdate call may285     * reschedule the watch before it's fully deleted, that's why we need to mark it as286     * 'removed' to prevent reuse287     */288    data->removed = TRUE;289    g_idle_add_full(G_PRIORITY_HIGH, virEventGLibHandleRemoveIdle, data, NULL);290291    ret = 0;292293 cleanup:294    g_mutex_unlock(eventlock);295    return ret;296}
virEventGLibHandleRemove

先移除 GSource 并标记 removed,避免延迟清理期间的 Update 重新启用监听。随后把真正的释放排到高优先级 idle source;高优先级针对默认 I/O 事件,不会抢占一个已经运行的回调。

src/util/vireventglib.cv10.10.0 · L239–L255
查看原文件
239static gboolean240virEventGLibHandleRemoveIdle(gpointer data)241{242    struct virEventGLibHandle *h = data;243244    PROBE(EVENT_GLIB_REMOVE_HANDLE_IDLE,245          "watch=%d ff=%p opaque=%p",246          h->watch, h->ff, h->opaque);247    if (h->ff)248        (h->ff)(h->opaque);249250    g_mutex_lock(eventlock);251    g_ptr_array_remove_fast(handles, h);252    g_mutex_unlock(eventlock);253254    return FALSE;255}
virEventGLibHandleRemoveIdle

释放 opaque 的 ff 回调在 eventlock 外运行,完成后才锁住数组移除 handle。idle 回调返回 FALSE,只执行一次。延后调用使 ff 不在 RemoveHandle 的持锁调用栈中同步重入。

src/rpc/virnetsocket.cv10.10.0 · L2126–L2141
查看原文件
2126static void virNetSocketEventFree(void *opaque)2127{2128    g_autoptr(virNetSocket) sock = opaque;2129    virFreeCallback ff;2130    void *eopaque;21312132    virObjectLock(sock);2133    ff = sock->ff;2134    eopaque = g_steal_pointer(&sock->opaque);2135    sock->func = NULL;2136    sock->ff = NULL;2137    virObjectUnlock(sock);21382139    if (ff)2140        ff(eopaque);2141}
virNetSocketEventFree

释放回调短暂锁住 socket,取出用户数据并清空回调指针,然后解锁后调用用户的 ff。g_autoptr 在函数退出时归还事件循环持有的 socket 引用;只有最后一个引用释放才会销毁对象。

六段连续源码串起后端注册、GSource 接入、持锁移除、延迟清理和 socket 引用归还。历史提交用于说明真实故障与方案变化,没有将原作者的复现步骤写成本站实测。

收益与取舍#

工程分析

带来的收益

  • 调用方通过六个事件操作使用同一套接口,默认实现可以从原有 poll 后端改为 GLib
  • 移除与释放分开,避免释放回调沿着持锁调用链立即重入
  • 高优先级清理不会被持续就绪的默认优先级监听事件一直压住,有机会归还已关闭连接的 FD

付出的代价

  • 删除监听不等于已经释放 opaque 或关闭 FD,主循环必须继续运行,其他引用也必须归还
  • G_PRIORITY_HIGH 只影响尚未调度的事件,不会中断正在执行的长回调,也不是“优先级越高越好”
  • v10.10.0 默认使用 GLib 仍是内部实现细节;应用需要保证 GLib 集成时应使用 libvirt-glib 的相应接口

可比较的选择

原有 virEventPoll 后端自己管理 poll、定时器和唤醒。2020 年改接 GLib 沿用了统一 API;这个变化本身不能证明吞吐或延迟更优。

默认低优先级 idle 清理此前的真实实现。FD 用尽且监听 socket 持续可读时,普通 I/O 事件可能一直先运行,待清理连接占用的 FD 迟迟不能释放。

版本与适用范围#

固定于 libvirt v10.10.0 的默认 GLib 事件后端及 RPC socket 路径,以非 Windows FD 为例。高优先级修复针对 handle 清理,不能推广为所有 timeout 都使用同一优先级。GSource 停止、GSource 引用释放、opaque 清理和 socket 最终关闭是不同步骤;不承诺任意嵌套主循环或应用回调都天然安全。

参考来源#

源码、官方文档与原始论文
01
virEventGLibRegisterOnce

src/util/vireventglib.c · virEventGLibRegisterOnce

源码v10.10.0
02
virEventGLibAddSocketWatch

src/util/vireventglibwatch.c · virEventGLibAddSocketWatch

源码v10.10.0
03
virNetSocketRemoveIOCallback

src/rpc/virnetsocket.c · virNetSocketRemoveIOCallback

源码v10.10.0
04
virEventGLibHandleRemove

src/util/vireventglib.c · virEventGLibHandleRemove

源码v10.10.0
05
virEventGLibHandleRemoveIdle

src/util/vireventglib.c · virEventGLibHandleRemoveIdle

源码v10.10.0
06
virNetSocketEventFree

src/rpc/virnetsocket.c · virNetSocketEventFree

源码v10.10.0
07源码5de203f8795d96229d2663e9ea1a24fba5db38fc
08源码946a25274c46ffff46323c62f567ae7e753aa921
11官方文档GLib 2.0 API

相关实现与比较#

4 个相关案例
6 个匹配案例