chardev: socket chardev aborts on async connect failure with `reconnect-ms`
**Note:** This issue report was drafted with assistance from Cline (an AI coding assistant) using the Qwen3.6-27B model. The bug report, root cause analysis, and potential fix below should be reviewed and verified independently.
[api_conversation_history.json](/uploads/dd1aa52d84ef1ef183116780f2c6fa4c/api_conversation_history.json)
---
## Bug Description
When using a socket chardev in client mode with the `reconnect-ms` option, QEMU aborts (SIGABRT, exit code 134) if the initial connection attempt fails. This makes it impossible to start QEMU when the remote socket is not yet available — which was the primary use case for the `reconnect-ms` option.
## Reproduction
```bash
qemu-system-x86_64 \
-nodefaults -nographic \
-chardev socket,id=test,host=127.0.0.1,port=19999,reconnect-ms=10000 \
-serial chardev:test \
-monitor stdio
```
**Result:** QEMU prints the monitor prompt, then immediately aborts with exit code 134 (SIGABRT).
**Expected:** QEMU should start successfully, log the connection error, and periodically retry the connection every `reconnect-ms` milliseconds.
## Affected Versions
v11.0.0 and current master (commit 405c32d2b1). The regression was introduced in commit 5c102ac9f1 (2026-03-06).
## Potential Fix
Removing the `yank_unregister_function()` call from the error path in `qemu_chr_socket_connected()` (chardev/char-socket.c) resolves the crash. After this change, QEMU starts successfully, logs the error message, and continues running with the reconnect timer active.
```diff
--- a/chardev/char-socket.c
+++ b/chardev/char-socket.c
@@ -1128,11 +1128,7 @@ static void qemu_chr_socket_connected(QIOTask *task, void *opaque)
if (qio_task_propagate_error(task, &err)) {
tcp_chr_change_state(s, TCP_CHARDEV_STATE_DISCONNECTED);
- if (s->registered_yank) {
- yank_unregister_function(CHARDEV_YANK_INSTANCE(chr->label),
- char_socket_yank_iochannel,
- QIO_CHANNEL(sioc));
- }
check_report_connect_error(chr, err);
goto cleanup;
}
```
**Note:** This fix has been locally tested and verified, but a full code review is recommended before merging.
## Root Cause Analysis
The crash occurs in `qemu_chr_socket_connected()` in `chardev/char-socket.c`. When the async connect task fails, the error handler calls `yank_unregister_function()` for the `QIOChannelSocket`:
```c
if (qio_task_propagate_error(task, &err)) {
tcp_chr_change_state(s, TCP_CHARDEV_STATE_DISCONNECTED);
if (s->registered_yank) {
yank_unregister_function(CHARDEV_YANK_INSTANCE(chr->label),
char_socket_yank_iochannel,
QIO_CHANNEL(sioc));
}
check_report_connect_error(chr, err);
goto cleanup;
}
```
However, `yank_register_function()` for this `sioc` is only called inside `tcp_chr_new_client()`, which is invoked only on a **successful** connection. On the failure path, `tcp_chr_new_client()` is never reached, so the yank function was never registered for this `sioc`.
The `yank_unregister_function()` implementation in `util/yank.c` calls `abort()` when the function+opaque pair is not found in the registered list, which triggers the crash.
### Regression History
- **2021-01-13** (commit `8ee4480692`): yank feature added to socket chardev. The yank function was registered in `tcp_chr_connect_client_async()` and unregistered in `qemu_chr_socket_connected()`'s error path — both were balanced.
- **2026-03-06** (commit `5c102ac9f1`, "chardev: Consolidate yank registration"): Moved `yank_register_function()` from `tcp_chr_connect_client_async()` into `tcp_chr_new_client()` (only called on success). The corresponding `yank_unregister_function()` call in the error path was not removed, breaking the balance.
The `if (s->registered_yank)` guard is insufficient: it checks whether the yank _instance_ was registered (set during chardev open), but not whether the yank _function_ was registered for this specific `sioc` (which only happens in `tcp_chr_new_client()` on success).
For comparison, `tcp_chr_free_connection()` (the normal disconnect cleanup path) correctly guards the unregister call with an additional `s->sioc` check, which is NULL on the error path.
## GDB Backtrace (debug build)
```
#3 yank_unregister_function (util/yank.c:150)
#4 qemu_chr_socket_connected (chardev/char-socket.c:1132)
#5 qio_task_complete (io/task.c:200)
#6 qio_task_thread_result (io/task.c:114)
```
issue