diff mbox series

[for-4.0,v9,12/16] qemu_thread: supplement error handling for iothread_complete/qemu_signalfd_compat

Message ID 20181225140449.15786-13-fli@suse.com
State New
Headers show
Series [for-4.0,v9,01/16] Fix segmentation fault when qemu_signal_init fails | expand

Commit Message

Fei Li Dec. 25, 2018, 2:04 p.m. UTC
For iothread_complete: utilize the existed errp to propagate the
error and do the corresponding cleanup to replace the temporary
&error_abort.

For qemu_signalfd_compat: add a local_err to hold the error, and
return the corresponding error code to replace the temporary
&error_abort.

Cc: Markus Armbruster <armbru@redhat.com>
Cc: Eric Blake <eblake@redhat.com>
Signed-off-by: Fei Li <fli@suse.com>
---
 iothread.c      | 17 +++++++++++------
 util/compatfd.c | 11 ++++++++---
 2 files changed, 19 insertions(+), 9 deletions(-)

Comments

Markus Armbruster Jan. 7, 2019, 5:50 p.m. UTC | #1
Fei Li <fli@suse.com> writes:

> For iothread_complete: utilize the existed errp to propagate the
> error and do the corresponding cleanup to replace the temporary
> &error_abort.
>
> For qemu_signalfd_compat: add a local_err to hold the error, and
> return the corresponding error code to replace the temporary
> &error_abort.

I'd split the patch.

>
> Cc: Markus Armbruster <armbru@redhat.com>
> Cc: Eric Blake <eblake@redhat.com>
> Signed-off-by: Fei Li <fli@suse.com>
> ---
>  iothread.c      | 17 +++++++++++------
>  util/compatfd.c | 11 ++++++++---
>  2 files changed, 19 insertions(+), 9 deletions(-)
>
> diff --git a/iothread.c b/iothread.c
> index 8e8aa01999..7335dacf0b 100644
> --- a/iothread.c
> +++ b/iothread.c
> @@ -164,9 +164,7 @@ static void iothread_complete(UserCreatable *obj, Error **errp)
>                                  &local_error);
>      if (local_error) {
>          error_propagate(errp, local_error);
> -        aio_context_unref(iothread->ctx);
> -        iothread->ctx = NULL;
> -        return;
> +        goto fail;
>      }
>  
>      qemu_mutex_init(&iothread->init_done_lock);
> @@ -178,9 +176,12 @@ static void iothread_complete(UserCreatable *obj, Error **errp)
>       */
>      name = object_get_canonical_path_component(OBJECT(obj));
>      thread_name = g_strdup_printf("IO %s", name);
> -    /* TODO: let the further caller handle the error instead of abort() here */
> -    qemu_thread_create(&iothread->thread, thread_name, iothread_run,
> -                       iothread, QEMU_THREAD_JOINABLE, &error_abort);
> +    if (!qemu_thread_create(&iothread->thread, thread_name, iothread_run,
> +                            iothread, QEMU_THREAD_JOINABLE, errp)) {
> +        g_free(thread_name);
> +        g_free(name);

I suspect you're missing cleanup here:

           qemu_cond_destroy(&iothread->init_done_cond);
           qemu_mutex_destroy(&iothread->init_done_lock);

But I'm not 100% sure, to be honest.  Stefan, can you help?


> +        goto fail;
> +    }
>      g_free(thread_name);
>      g_free(name);
>  

I'd avoid the code duplication like this:

       thread_ok = qemu_thread_create(&iothread->thread, thread_name,
                                      iothread_run, iothread,
                                      QEMU_THREAD_JOINABLE, errp);
       g_free(thread_name);
       g_free(name);
       if (!thread_ok) {
           qemu_cond_destroy(&iothread->init_done_cond);
           qemu_mutex_destroy(&iothread->init_done_lock);
           goto fail;
       }

Matter of taste.

Hmm, iothread.c has no maintainer.  Stefan, you created it, would you be
willing to serve as maintainer?

> @@ -191,6 +192,10 @@ static void iothread_complete(UserCreatable *obj, Error **errp)
>                         &iothread->init_done_lock);
>      }
>      qemu_mutex_unlock(&iothread->init_done_lock);
> +    return;
> +fail:
> +    aio_context_unref(iothread->ctx);
> +    iothread->ctx = NULL;
>  }
>  
>  typedef struct {
> diff --git a/util/compatfd.c b/util/compatfd.c
> index c3d8448264..9cb13381e4 100644
> --- a/util/compatfd.c
> +++ b/util/compatfd.c
> @@ -71,6 +71,7 @@ static int qemu_signalfd_compat(const sigset_t *mask)
>      struct sigfd_compat_info *info;
>      QemuThread thread;
>      int fds[2];
> +    Error *local_err = NULL;
>  
>      info = malloc(sizeof(*info));
>      if (info == NULL) {
> @@ -89,9 +90,13 @@ static int qemu_signalfd_compat(const sigset_t *mask)
>      memcpy(&info->mask, mask, sizeof(*mask));
>      info->fd = fds[1];
>  
> -    /* TODO: let the further caller handle the error instead of abort() here */
> -    qemu_thread_create(&thread, "signalfd_compat", sigwait_compat,
> -                       info, QEMU_THREAD_DETACHED, &error_abort);
> +    if (!qemu_thread_create(&thread, "signalfd_compat", sigwait_compat,
> +                            info, QEMU_THREAD_DETACHED, &local_err)) {
> +        close(fds[0]);
> +        close(fds[1]);
> +        free(info);
> +        return -1;

Leaks @local_err.  Pass NULL instead.

> +    }
>  
>      return fds[0];
>  }
fei Jan. 8, 2019, 4:18 p.m. UTC | #2
> 在 2019年1月8日,01:50,Markus Armbruster <armbru@redhat.com> 写道:
> 
> Fei Li <fli@suse.com> writes:
> 
>> For iothread_complete: utilize the existed errp to propagate the
>> error and do the corresponding cleanup to replace the temporary
>> &error_abort.
>> 
>> For qemu_signalfd_compat: add a local_err to hold the error, and
>> return the corresponding error code to replace the temporary
>> &error_abort.
> 
> I'd split the patch.
Ok.
> 
>> 
>> Cc: Markus Armbruster <armbru@redhat.com>
>> Cc: Eric Blake <eblake@redhat.com>
>> Signed-off-by: Fei Li <fli@suse.com>
>> ---
>> iothread.c      | 17 +++++++++++------
>> util/compatfd.c | 11 ++++++++---
>> 2 files changed, 19 insertions(+), 9 deletions(-)
>> 
>> diff --git a/iothread.c b/iothread.c
>> index 8e8aa01999..7335dacf0b 100644
>> --- a/iothread.c
>> +++ b/iothread.c
>> @@ -164,9 +164,7 @@ static void iothread_complete(UserCreatable *obj, Error **errp)
>>                                 &local_error);
>>     if (local_error) {
>>         error_propagate(errp, local_error);
>> -        aio_context_unref(iothread->ctx);
>> -        iothread->ctx = NULL;
>> -        return;
>> +        goto fail;
>>     }
>> 
>>     qemu_mutex_init(&iothread->init_done_lock);
>> @@ -178,9 +176,12 @@ static void iothread_complete(UserCreatable *obj, Error **errp)
>>      */
>>     name = object_get_canonical_path_component(OBJECT(obj));
>>     thread_name = g_strdup_printf("IO %s", name);
>> -    /* TODO: let the further caller handle the error instead of abort() here */
>> -    qemu_thread_create(&iothread->thread, thread_name, iothread_run,
>> -                       iothread, QEMU_THREAD_JOINABLE, &error_abort);
>> +    if (!qemu_thread_create(&iothread->thread, thread_name, iothread_run,
>> +                            iothread, QEMU_THREAD_JOINABLE, errp)) {
>> +        g_free(thread_name);
>> +        g_free(name);
> 
> I suspect you're missing cleanup here:
> 
>           qemu_cond_destroy(&iothread->init_done_cond);
>           qemu_mutex_destroy(&iothread->init_done_lock);
I remember I checked the code, when ucc->complete() fails, there’s a finalize() function to do the destroy. But did not test all the callers, so let’s wait for Stefan’s feedback. :)
> 
> But I'm not 100% sure, to be honest.  Stefan, can you help?
> 
> 
>> +        goto fail;
>> +    }
>>     g_free(thread_name);
>>     g_free(name);
>> 
> 
> I'd avoid the code duplication like this:
> 
>       thread_ok = qemu_thread_create(&iothread->thread, thread_name,
>                                      iothread_run, iothread,
>                                      QEMU_THREAD_JOINABLE, errp);
>       g_free(thread_name);
>       g_free(name);
>       if (!thread_ok) {
>           qemu_cond_destroy(&iothread->init_done_cond);
>           qemu_mutex_destroy(&iothread->init_done_lock);
>           goto fail;
>       }
> 
> Matter of taste.
> 
> Hmm, iothread.c has no maintainer.  Stefan, you created it, would you be
> willing to serve as maintainer?
> 
>> @@ -191,6 +192,10 @@ static void iothread_complete(UserCreatable *obj, Error **errp)
>>                        &iothread->init_done_lock);
>>     }
>>     qemu_mutex_unlock(&iothread->init_done_lock);
>> +    return;
>> +fail:
>> +    aio_context_unref(iothread->ctx);
>> +    iothread->ctx = NULL;
>> }
>> 
>> typedef struct {
>> diff --git a/util/compatfd.c b/util/compatfd.c
>> index c3d8448264..9cb13381e4 100644
>> --- a/util/compatfd.c
>> +++ b/util/compatfd.c
>> @@ -71,6 +71,7 @@ static int qemu_signalfd_compat(const sigset_t *mask)
>>     struct sigfd_compat_info *info;
>>     QemuThread thread;
>>     int fds[2];
>> +    Error *local_err = NULL;
>> 
>>     info = malloc(sizeof(*info));
>>     if (info == NULL) {
>> @@ -89,9 +90,13 @@ static int qemu_signalfd_compat(const sigset_t *mask)
>>     memcpy(&info->mask, mask, sizeof(*mask));
>>     info->fd = fds[1];
>> 
>> -    /* TODO: let the further caller handle the error instead of abort() here */
>> -    qemu_thread_create(&thread, "signalfd_compat", sigwait_compat,
>> -                       info, QEMU_THREAD_DETACHED, &error_abort);
>> +    if (!qemu_thread_create(&thread, "signalfd_compat", sigwait_compat,
>> +                            info, QEMU_THREAD_DETACHED, &local_err)) {
>> +        close(fds[0]);
>> +        close(fds[1]);
>> +        free(info);
>> +        return -1;
> 
> Leaks @local_err.  Pass NULL instead.
Ok, thanks!

Have a nice day 
Fei
> 
>> +    }
>> 
>>     return fds[0];
>> }
fei Jan. 13, 2019, 4:16 p.m. UTC | #3
在 2019/1/9 上午12:18, fei 写道:
>
>> 在 2019年1月8日,01:50,Markus Armbruster <armbru@redhat.com> 写道:
>>
>> Fei Li <fli@suse.com> writes:
>>
>>> For iothread_complete: utilize the existed errp to propagate the
>>> error and do the corresponding cleanup to replace the temporary
>>> &error_abort.
>>>
>>> For qemu_signalfd_compat: add a local_err to hold the error, and
>>> return the corresponding error code to replace the temporary
>>> &error_abort.
>> I'd split the patch.
> Ok.
>>> Cc: Markus Armbruster <armbru@redhat.com>
>>> Cc: Eric Blake <eblake@redhat.com>
>>> Signed-off-by: Fei Li <fli@suse.com>
>>> ---
>>> iothread.c      | 17 +++++++++++------
>>> util/compatfd.c | 11 ++++++++---
>>> 2 files changed, 19 insertions(+), 9 deletions(-)
>>>
>>> diff --git a/iothread.c b/iothread.c
>>> index 8e8aa01999..7335dacf0b 100644
>>> --- a/iothread.c
>>> +++ b/iothread.c
>>> @@ -164,9 +164,7 @@ static void iothread_complete(UserCreatable *obj, Error **errp)
>>>                                  &local_error);
>>>      if (local_error) {
>>>          error_propagate(errp, local_error);
>>> -        aio_context_unref(iothread->ctx);
>>> -        iothread->ctx = NULL;
>>> -        return;
>>> +        goto fail;
>>>      }
>>>
>>>      qemu_mutex_init(&iothread->init_done_lock);
>>> @@ -178,9 +176,12 @@ static void iothread_complete(UserCreatable *obj, Error **errp)
>>>       */
>>>      name = object_get_canonical_path_component(OBJECT(obj));
>>>      thread_name = g_strdup_printf("IO %s", name);
>>> -    /* TODO: let the further caller handle the error instead of abort() here */
>>> -    qemu_thread_create(&iothread->thread, thread_name, iothread_run,
>>> -                       iothread, QEMU_THREAD_JOINABLE, &error_abort);
>>> +    if (!qemu_thread_create(&iothread->thread, thread_name, iothread_run,
>>> +                            iothread, QEMU_THREAD_JOINABLE, errp)) {
>>> +        g_free(thread_name);
>>> +        g_free(name);
>> I suspect you're missing cleanup here:
>>
>>            qemu_cond_destroy(&iothread->init_done_cond);
>>            qemu_mutex_destroy(&iothread->init_done_lock);
> I remember I checked the code, when ucc->complete() fails, there’s a finalize() function to do the destroy.

To be specific, the qemu_xxx_destroy() is called by

object_unref() => object_finalize() => object_deinit() => 
type->instance_finalize(obj); (that is, iothread_instance_finalize).

For the iothread_complete(), it is only called in 
user_creatable_complete() as ucc->complete().
I checked the code, when callers of user_creatable_complete() fails, all 
of them will call
object_unref() to call the qemu_xxx_destroy(), except one &error_abort 
case (e.i. desugar_shm()).
> But did not test all the callers, so let’s wait for Stefan’s feedback. :)
But again, I did not do all the test. Correct me if I am wrong. :)
>> But I'm not 100% sure, to be honest.  Stefan, can you help?
>>
>>
>>> +        goto fail;
>>> +    }
>>>      g_free(thread_name);
>>>      g_free(name);
>>>
>> I'd avoid the code duplication like this:
>>
>>        thread_ok = qemu_thread_create(&iothread->thread, thread_name,
>>                                       iothread_run, iothread,
>>                                       QEMU_THREAD_JOINABLE, errp);
>>        g_free(thread_name);
>>        g_free(name);
>>        if (!thread_ok) {
>>            qemu_cond_destroy(&iothread->init_done_cond);
>>            qemu_mutex_destroy(&iothread->init_done_lock);
>>            goto fail;
>>        }

Ok, thanks.

Have a nice day
Fei
>> Matter of taste.
>>
>> Hmm, iothread.c has no maintainer.  Stefan, you created it, would you be
>> willing to serve as maintainer?
>>
Markus Armbruster Jan. 14, 2019, 12:53 p.m. UTC | #4
Fei Li <lifei1214@126.com> writes:

> 在 2019/1/9 上午12:18, fei 写道:
>>
>>> 在 2019年1月8日,01:50,Markus Armbruster <armbru@redhat.com> 写道:
>>>
>>> Fei Li <fli@suse.com> writes:
>>>
>>>> For iothread_complete: utilize the existed errp to propagate the
>>>> error and do the corresponding cleanup to replace the temporary
>>>> &error_abort.
>>>>
>>>> For qemu_signalfd_compat: add a local_err to hold the error, and
>>>> return the corresponding error code to replace the temporary
>>>> &error_abort.
>>> I'd split the patch.
>> Ok.
>>>> Cc: Markus Armbruster <armbru@redhat.com>
>>>> Cc: Eric Blake <eblake@redhat.com>
>>>> Signed-off-by: Fei Li <fli@suse.com>
>>>> ---
>>>> iothread.c      | 17 +++++++++++------
>>>> util/compatfd.c | 11 ++++++++---
>>>> 2 files changed, 19 insertions(+), 9 deletions(-)
>>>>
>>>> diff --git a/iothread.c b/iothread.c
>>>> index 8e8aa01999..7335dacf0b 100644
>>>> --- a/iothread.c
>>>> +++ b/iothread.c
>>>> @@ -164,9 +164,7 @@ static void iothread_complete(UserCreatable *obj, Error **errp)
>>>>                                  &local_error);
>>>>      if (local_error) {
>>>>          error_propagate(errp, local_error);
>>>> -        aio_context_unref(iothread->ctx);
>>>> -        iothread->ctx = NULL;
>>>> -        return;
>>>> +        goto fail;
>>>>      }
>>>>
>>>>      qemu_mutex_init(&iothread->init_done_lock);
>>>> @@ -178,9 +176,12 @@ static void iothread_complete(UserCreatable *obj, Error **errp)
>>>>       */
>>>>      name = object_get_canonical_path_component(OBJECT(obj));
>>>>      thread_name = g_strdup_printf("IO %s", name);
>>>> -    /* TODO: let the further caller handle the error instead of abort() here */
>>>> -    qemu_thread_create(&iothread->thread, thread_name, iothread_run,
>>>> -                       iothread, QEMU_THREAD_JOINABLE, &error_abort);
>>>> +    if (!qemu_thread_create(&iothread->thread, thread_name, iothread_run,
>>>> +                            iothread, QEMU_THREAD_JOINABLE, errp)) {
>>>> +        g_free(thread_name);
>>>> +        g_free(name);
>>> I suspect you're missing cleanup here:
>>>
>>>            qemu_cond_destroy(&iothread->init_done_cond);
>>>            qemu_mutex_destroy(&iothread->init_done_lock);
>> I remember I checked the code, when ucc->complete() fails, there’s a finalize() function to do the destroy.
>
> To be specific, the qemu_xxx_destroy() is called by
>
> object_unref() => object_finalize() => object_deinit() =>
> type->instance_finalize(obj); (that is, iothread_instance_finalize).
>
> For the iothread_complete(), it is only called in
> user_creatable_complete() as ucc->complete().
> I checked the code, when callers of user_creatable_complete() fails,
> all of them will call
> object_unref() to call the qemu_xxx_destroy(), except one &error_abort
> case (e.i. desugar_shm()).

I'm not familiar with iothread.c.  But like anyone capable of reading C,
I can find out stuff.

iothread_instance_finalize() guards its cleanups.  In particular, it
cleans up ->init_done_cond and init_done_lock only when ->thread_id !=
-1.

iothread_instance_init() initializes ->thread_id = -1.

iothread_run() sets it to the actual thread ID.

When iothread_instance_complete() succeeds, it has waited for
->thread_id to become != -1, in the /* Wait for initialization to
complete */ loop.

When it fails, ->thread_id is still -1.

Therefore, you cannot rely on iothread_instance_finalize() for cleaning
up ->init_done_lock and ->init_done_cond on iothread_instance_complete()
failure.

I'm pretty sure you could've figured this out yourself instead of
relying on me.

[...]
fei Jan. 14, 2019, 1:52 p.m. UTC | #5
在 2019/1/14 下午8:53, Markus Armbruster 写道:
> Fei Li <lifei1214@126.com> writes:
>
>> 在 2019/1/9 上午12:18, fei 写道:
>>>> 在 2019年1月8日,01:50,Markus Armbruster <armbru@redhat.com> 写道:
>>>>
>>>> Fei Li <fli@suse.com> writes:
>>>>
>>>>> For iothread_complete: utilize the existed errp to propagate the
>>>>> error and do the corresponding cleanup to replace the temporary
>>>>> &error_abort.
>>>>>
>>>>> For qemu_signalfd_compat: add a local_err to hold the error, and
>>>>> return the corresponding error code to replace the temporary
>>>>> &error_abort.
>>>> I'd split the patch.
>>> Ok.
>>>>> Cc: Markus Armbruster <armbru@redhat.com>
>>>>> Cc: Eric Blake <eblake@redhat.com>
>>>>> Signed-off-by: Fei Li <fli@suse.com>
>>>>> ---
>>>>> iothread.c      | 17 +++++++++++------
>>>>> util/compatfd.c | 11 ++++++++---
>>>>> 2 files changed, 19 insertions(+), 9 deletions(-)
>>>>>
>>>>> diff --git a/iothread.c b/iothread.c
>>>>> index 8e8aa01999..7335dacf0b 100644
>>>>> --- a/iothread.c
>>>>> +++ b/iothread.c
>>>>> @@ -164,9 +164,7 @@ static void iothread_complete(UserCreatable *obj, Error **errp)
>>>>>                                   &local_error);
>>>>>       if (local_error) {
>>>>>           error_propagate(errp, local_error);
>>>>> -        aio_context_unref(iothread->ctx);
>>>>> -        iothread->ctx = NULL;
>>>>> -        return;
>>>>> +        goto fail;
>>>>>       }
>>>>>
>>>>>       qemu_mutex_init(&iothread->init_done_lock);
>>>>> @@ -178,9 +176,12 @@ static void iothread_complete(UserCreatable *obj, Error **errp)
>>>>>        */
>>>>>       name = object_get_canonical_path_component(OBJECT(obj));
>>>>>       thread_name = g_strdup_printf("IO %s", name);
>>>>> -    /* TODO: let the further caller handle the error instead of abort() here */
>>>>> -    qemu_thread_create(&iothread->thread, thread_name, iothread_run,
>>>>> -                       iothread, QEMU_THREAD_JOINABLE, &error_abort);
>>>>> +    if (!qemu_thread_create(&iothread->thread, thread_name, iothread_run,
>>>>> +                            iothread, QEMU_THREAD_JOINABLE, errp)) {
>>>>> +        g_free(thread_name);
>>>>> +        g_free(name);
>>>> I suspect you're missing cleanup here:
>>>>
>>>>             qemu_cond_destroy(&iothread->init_done_cond);
>>>>             qemu_mutex_destroy(&iothread->init_done_lock);
>>> I remember I checked the code, when ucc->complete() fails, there’s a finalize() function to do the destroy.
>> To be specific, the qemu_xxx_destroy() is called by
>>
>> object_unref() => object_finalize() => object_deinit() =>
>> type->instance_finalize(obj); (that is, iothread_instance_finalize).
>>
>> For the iothread_complete(), it is only called in
>> user_creatable_complete() as ucc->complete().
>> I checked the code, when callers of user_creatable_complete() fails,
>> all of them will call
>> object_unref() to call the qemu_xxx_destroy(), except one &error_abort
>> case (e.i. desugar_shm()).
> I'm not familiar with iothread.c.  But like anyone capable of reading C,
> I can find out stuff.
>
> iothread_instance_finalize() guards its cleanups.  In particular, it
> cleans up ->init_done_cond and init_done_lock only when ->thread_id !=
> -1.
Ah, sorry that I overlooked the "if (iothread->thread_id != -1)".
So embarrassed, and sorry for the trouble.. You are right, and I
will add the qemu_xxx_destroy() in the next version. ;)


Have a nice day, thanks so much!
Fei
>
> iothread_instance_init() initializes ->thread_id = -1.
>
> iothread_run() sets it to the actual thread ID.
>
> When iothread_instance_complete() succeeds, it has waited for
> ->thread_id to become != -1, in the /* Wait for initialization to
> complete */ loop.
>
> When it fails, ->thread_id is still -1.
>
> Therefore, you cannot rely on iothread_instance_finalize() for cleaning
> up ->init_done_lock and ->init_done_cond on iothread_instance_complete()
> failure.
>
> I'm pretty sure you could've figured this out yourself instead of
> relying on me.
>
> [...]
diff mbox series

Patch

diff --git a/iothread.c b/iothread.c
index 8e8aa01999..7335dacf0b 100644
--- a/iothread.c
+++ b/iothread.c
@@ -164,9 +164,7 @@  static void iothread_complete(UserCreatable *obj, Error **errp)
                                 &local_error);
     if (local_error) {
         error_propagate(errp, local_error);
-        aio_context_unref(iothread->ctx);
-        iothread->ctx = NULL;
-        return;
+        goto fail;
     }
 
     qemu_mutex_init(&iothread->init_done_lock);
@@ -178,9 +176,12 @@  static void iothread_complete(UserCreatable *obj, Error **errp)
      */
     name = object_get_canonical_path_component(OBJECT(obj));
     thread_name = g_strdup_printf("IO %s", name);
-    /* TODO: let the further caller handle the error instead of abort() here */
-    qemu_thread_create(&iothread->thread, thread_name, iothread_run,
-                       iothread, QEMU_THREAD_JOINABLE, &error_abort);
+    if (!qemu_thread_create(&iothread->thread, thread_name, iothread_run,
+                            iothread, QEMU_THREAD_JOINABLE, errp)) {
+        g_free(thread_name);
+        g_free(name);
+        goto fail;
+    }
     g_free(thread_name);
     g_free(name);
 
@@ -191,6 +192,10 @@  static void iothread_complete(UserCreatable *obj, Error **errp)
                        &iothread->init_done_lock);
     }
     qemu_mutex_unlock(&iothread->init_done_lock);
+    return;
+fail:
+    aio_context_unref(iothread->ctx);
+    iothread->ctx = NULL;
 }
 
 typedef struct {
diff --git a/util/compatfd.c b/util/compatfd.c
index c3d8448264..9cb13381e4 100644
--- a/util/compatfd.c
+++ b/util/compatfd.c
@@ -71,6 +71,7 @@  static int qemu_signalfd_compat(const sigset_t *mask)
     struct sigfd_compat_info *info;
     QemuThread thread;
     int fds[2];
+    Error *local_err = NULL;
 
     info = malloc(sizeof(*info));
     if (info == NULL) {
@@ -89,9 +90,13 @@  static int qemu_signalfd_compat(const sigset_t *mask)
     memcpy(&info->mask, mask, sizeof(*mask));
     info->fd = fds[1];
 
-    /* TODO: let the further caller handle the error instead of abort() here */
-    qemu_thread_create(&thread, "signalfd_compat", sigwait_compat,
-                       info, QEMU_THREAD_DETACHED, &error_abort);
+    if (!qemu_thread_create(&thread, "signalfd_compat", sigwait_compat,
+                            info, QEMU_THREAD_DETACHED, &local_err)) {
+        close(fds[0]);
+        close(fds[1]);
+        free(info);
+        return -1;
+    }
 
     return fds[0];
 }