[libvirt] [PATCH 1/2] tools: console: cleanup console on errors in main thread

John Ferlan jferlan at redhat.com
Fri Feb 22 14:55:53 UTC 2019



On 2/15/19 9:39 AM, Nikolay Shirokovskiy wrote:
> We only check now for virObjectWait failures in virshRunConsole but
> we'd better check and for other failures too. Anyway if failure
> happened we need to shutdown console to stop delivering events
> from event loop thread or we are in trouble as console is freed
> on virshRunConsole exit.
> 
> And we need to add refcounter to console object because event
> can be delivered after we remove fd handle/stream callback.
> 
> Signed-off-by: Nikolay Shirokovskiy <nshirokovskiy at virtuozzo.com>
> ---
>  tools/virsh-console.c | 161 +++++++++++++++++++++++++++++++++++++-------------
>  1 file changed, 119 insertions(+), 42 deletions(-)
> 

There's three things going on in this patch, let's split into convert
virConsole to virObjectLockable, the usage of locks in the event
callback methods, and finally handling the late event.

FWIW: I could see the order being

1. Add the virMutex{Lock|Unlock} into the functions and use cleanup path
logic since right now @con is being altered without the lock.

2. Convert to using virObjectLockable

3. Add logic to handle late event


> diff --git a/tools/virsh-console.c b/tools/virsh-console.c
> index 045a636..c0c3f90 100644
> --- a/tools/virsh-console.c
> +++ b/tools/virsh-console.c
> @@ -60,9 +60,10 @@ struct virConsoleBuffer {
>  typedef struct virConsole virConsole;
>  typedef virConsole *virConsolePtr;
>  struct virConsole {
> +    virObjectLockable parent;
> +
>      virStreamPtr st;
>      bool quit;
> -    virMutex lock;
>      virCond cond;
>  
>      int stdinWatch;
> @@ -74,6 +75,19 @@ struct virConsole {
>      char escapeChar;
>  };
>  
> +static virClassPtr virConsoleClass;
> +static void virConsoleDispose(void *obj);
> +
> +static int
> +virConsoleOnceInit(void)
> +{
> +    if (!VIR_CLASS_NEW(virConsole, virClassForObjectLockable()))
> +        return -1;
> +
> +    return 0;
> +}
> +
> +VIR_ONCE_GLOBAL_INIT(virConsole);
>  
>  static void
>  virConsoleHandleSignal(int sig ATTRIBUTE_UNUSED)
> @@ -104,16 +118,14 @@ virConsoleShutdown(virConsolePtr con)
>  
>  
>  static void
> -virConsoleFree(virConsolePtr con)
> +virConsoleDispose(void *obj)
>  {
> -    if (!con)
> -        return;
> +    virConsolePtr con = obj;
>  
>      if (con->st)
>          virStreamFree(con->st);
> -    virMutexDestroy(&con->lock);
> +
>      virCondDestroy(&con->cond);
> -    VIR_FREE(con);
>  }
>  
>  
> @@ -123,6 +135,12 @@ virConsoleEventOnStream(virStreamPtr st,
>  {
>      virConsolePtr con = opaque;
>  
> +    virObjectLock(con);
> +
> +    /* we got late event after console shutdowned */

s/shutdowned/was shutdown/

(repeats)

> +    if (!con->st)
> +        goto cleanup;
> +
>      if (events & VIR_STREAM_EVENT_READABLE) {
>          size_t avail = con->streamToTerminal.length -
>              con->streamToTerminal.offset;
> @@ -132,7 +150,7 @@ virConsoleEventOnStream(virStreamPtr st,
>              if (VIR_REALLOC_N(con->streamToTerminal.data,
>                                con->streamToTerminal.length + 1024) < 0) {
>                  virConsoleShutdown(con);
> -                return;
> +                goto cleanup;
>              }
>              con->streamToTerminal.length += 1024;
>              avail += 1024;
> @@ -143,10 +161,10 @@ virConsoleEventOnStream(virStreamPtr st,
>                              con->streamToTerminal.offset,
>                              avail);
>          if (got == -2)
> -            return; /* blocking */
> +            goto cleanup; /* blocking */
>          if (got <= 0) {
>              virConsoleShutdown(con);
> -            return;
> +            goto cleanup;
>          }
>          con->streamToTerminal.offset += got;
>          if (con->streamToTerminal.offset)
> @@ -162,10 +180,10 @@ virConsoleEventOnStream(virStreamPtr st,
>                               con->terminalToStream.data,
>                               con->terminalToStream.offset);
>          if (done == -2)
> -            return; /* blocking */
> +            goto cleanup; /* blocking */
>          if (done < 0) {
>              virConsoleShutdown(con);
> -            return;
> +            goto cleanup;
>          }
>          memmove(con->terminalToStream.data,
>                  con->terminalToStream.data + done,
> @@ -187,6 +205,9 @@ virConsoleEventOnStream(virStreamPtr st,
>          events & VIR_STREAM_EVENT_HANGUP) {
>          virConsoleShutdown(con);
>      }
> +
> + cleanup:
> +    virObjectUnlock(con);
>  }
>  
>  
> @@ -198,6 +219,12 @@ virConsoleEventOnStdin(int watch ATTRIBUTE_UNUSED,
>  {
>      virConsolePtr con = opaque;
>  
> +    virObjectLock(con);
> +
> +    /* we got late event after console shutdowned */
> +    if (!con->st)
> +        goto cleanup;
> +
>      if (events & VIR_EVENT_HANDLE_READABLE) {
>          size_t avail = con->terminalToStream.length -
>              con->terminalToStream.offset;
> @@ -207,7 +234,7 @@ virConsoleEventOnStdin(int watch ATTRIBUTE_UNUSED,
>              if (VIR_REALLOC_N(con->terminalToStream.data,
>                                con->terminalToStream.length + 1024) < 0) {
>                  virConsoleShutdown(con);
> -                return;
> +                goto cleanup;
>              }
>              con->terminalToStream.length += 1024;
>              avail += 1024;
> @@ -220,15 +247,15 @@ virConsoleEventOnStdin(int watch ATTRIBUTE_UNUSED,
>          if (got < 0) {
>              if (errno != EAGAIN)
>                  virConsoleShutdown(con);
> -            return;
> +            goto cleanup;
>          }
>          if (got == 0) {
>              virConsoleShutdown(con);
> -            return;
> +            goto cleanup;
>          }
>          if (con->terminalToStream.data[con->terminalToStream.offset] == con->escapeChar) {
>              virConsoleShutdown(con);
> -            return;
> +            goto cleanup;
>          }
>  
>          con->terminalToStream.offset += got;
> @@ -242,6 +269,9 @@ virConsoleEventOnStdin(int watch ATTRIBUTE_UNUSED,
>          events & VIR_EVENT_HANDLE_HANGUP) {
>          virConsoleShutdown(con);
>      }
> +
> + cleanup:
> +    virObjectUnlock(con);
>  }
>  
>  
> @@ -253,6 +283,12 @@ virConsoleEventOnStdout(int watch ATTRIBUTE_UNUSED,
>  {
>      virConsolePtr con = opaque;
>  
> +    virObjectLock(con);
> +
> +    /* we got late event after console shutdowned */
> +    if (!con->st)
> +        goto cleanup;
> +
>      if (events & VIR_EVENT_HANDLE_WRITABLE &&
>          con->streamToTerminal.offset) {
>          ssize_t done;
> @@ -263,7 +299,7 @@ virConsoleEventOnStdout(int watch ATTRIBUTE_UNUSED,
>          if (done < 0) {
>              if (errno != EAGAIN)
>                  virConsoleShutdown(con);
> -            return;
> +            goto cleanup;
>          }
>          memmove(con->streamToTerminal.data,
>                  con->streamToTerminal.data + done,
> @@ -285,6 +321,38 @@ virConsoleEventOnStdout(int watch ATTRIBUTE_UNUSED,
>          events & VIR_EVENT_HANDLE_HANGUP) {
>          virConsoleShutdown(con);
>      }
> +
> + cleanup:
> +    virObjectUnlock(con);
> +}
> +
> +
> +static virConsolePtr
> +virConsoleNew(void)
> +{
> +    virConsolePtr con;
> +
> +    if (virConsoleInitialize() < 0)
> +        return NULL;
> +
> +    if (!(con = virObjectNew(virConsoleClass)))
> +        return NULL;
> +
> +    if (virCondInit(&con->cond) < 0) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> +                       _("cannot initialize console condition"));
> +
> +        goto error;
> +    }
> +
> +    con->stdinWatch = -1;
> +    con->stdoutWatch = -1;
> +
> +    return con;
> +
> + error:
> +    virObjectUnref(con);
> +    return NULL;
>  }
>  
>  
> @@ -324,6 +392,11 @@ virshRunConsole(vshControl *ctl,
>      if (vshTTYMakeRaw(ctl, true) < 0)
>          goto resettty;
>  
> +    if (!(con = virConsoleNew()))
> +        goto resettty;
> +
> +    virObjectLock(con);
> +
>      /* Trap all common signals so that we can safely restore the original
>       * terminal settings on STDIN before the process exits - people don't like
>       * being left with a messed up terminal ! */
> @@ -333,9 +406,6 @@ virshRunConsole(vshControl *ctl,
>      sigaction(SIGHUP,  &sighandler, &old_sighup);
>      sigaction(SIGPIPE, &sighandler, &old_sigpipe);
>  
> -    if (VIR_ALLOC(con) < 0)
> -        goto cleanup;
> -
>      con->escapeChar = virshGetEscapeChar(priv->escapeChar);
>      con->st = virStreamNew(virDomainGetConnect(dom),
>                             VIR_STREAM_NONBLOCK);
> @@ -345,42 +415,49 @@ virshRunConsole(vshControl *ctl,
>      if (virDomainOpenConsole(dom, dev_name, con->st, flags) < 0)
>          goto cleanup;
>  
> -    if (virCondInit(&con->cond) < 0 || virMutexInit(&con->lock) < 0)
> +    virObjectRef(con);
> +    if ((con->stdinWatch = virEventAddHandle(STDIN_FILENO,
> +                                             VIR_EVENT_HANDLE_READABLE,
> +                                             virConsoleEventOnStdin,
> +                                             con,
> +                                             virObjectFreeCallback)) < 0) {
> +        virObjectUnref(con);
>          goto cleanup;
> +    }

Is there a specific reason to go with:

   ObjectRef
   if condition
      error path
      ObjectUnref

instead of

   if condition
   ObjectRef

I guess it doesn't matter too much, but looks odd to me... If the
virEventAddHandle fails, the virObjectFreeCallback isn't called so @con
wouldn't be Unref'd until at least you Unlock'd @con.

>  
> -    virMutexLock(&con->lock);
> -
> -    con->stdinWatch = virEventAddHandle(STDIN_FILENO,
> -                                        VIR_EVENT_HANDLE_READABLE,
> -                                        virConsoleEventOnStdin,
> -                                        con,
> -                                        NULL);
> -    con->stdoutWatch = virEventAddHandle(STDOUT_FILENO,
> -                                         0,
> -                                         virConsoleEventOnStdout,
> -                                         con,
> -                                         NULL);
> +    virObjectRef(con);
> +    if ((con->stdoutWatch = virEventAddHandle(STDOUT_FILENO,
> +                                              0,
> +                                              virConsoleEventOnStdout,
> +                                              con,
> +                                              virObjectFreeCallback)) < 0) {
> +        virObjectUnref(con);
> +        goto cleanup;
> +    }
>  
> -    virStreamEventAddCallback(con->st,
> -                              VIR_STREAM_EVENT_READABLE,
> -                              virConsoleEventOnStream,
> -                              con,
> -                              NULL);
> +    virObjectRef(con);
> +    if (virStreamEventAddCallback(con->st,
> +                                  VIR_STREAM_EVENT_READABLE,
> +                                  virConsoleEventOnStream,
> +                                  con,
> +                                  virObjectFreeCallback) < 0) {
> +        virObjectUnref(con);
> +        goto cleanup;
> +    }
>  
>      while (!con->quit) {
> -        if (virCondWait(&con->cond, &con->lock) < 0) {
> -            virMutexUnlock(&con->lock);
> +        if (virCondWait(&con->cond, &con->parent.lock) < 0) {
>              VIR_ERROR(_("unable to wait on console condition"));
>              goto cleanup;
>          }
>      }
>  
> -    virMutexUnlock(&con->lock);
> -
>      ret = 0;
>  
>   cleanup:
> -    virConsoleFree(con);
> +    virConsoleShutdown(con);

Should virConsoleShutdown be adjusted to avoid virCondSignal if
con->quit is tree or this call be adjusted to not call if con->quit is
true... IOW: Don't call it twice...

John

> +    virObjectUnlock(con);
> +    virObjectUnref(con);
>  
>      /* Restore original signal handlers */
>      sigaction(SIGQUIT, &old_sigquit, NULL);
> 




More information about the libvir-list mailing list