From 54a92fb487e2b3529974513a88de821edd4970f3 Mon Sep 17 00:00:00 2001
From: Zebediah Figura <z.figura12@gmail.com>
Date: Sat, 9 Jun 2018 20:21:00 -0500
Subject: [PATCH 29/83] ntdll: Implement wait-all.

Most of the comments I would put here are covered in the code. I will just
amend:

Mutexes are extremely annoying. Microsoft should never have let you grab a
mutex more than once.
---
 dlls/ntdll/esync.c | 162 +++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 149 insertions(+), 13 deletions(-)

diff --git a/dlls/ntdll/esync.c b/dlls/ntdll/esync.c
index 79d9c2e46..1eab7367f 100644
--- a/dlls/ntdll/esync.c
+++ b/dlls/ntdll/esync.c
@@ -534,8 +534,10 @@ NTSTATUS esync_wait_objects( DWORD count, const HANDLE *handles, BOOLEAN wait_an
     LONGLONG timeleft;
     LARGE_INTEGER now;
     ULONGLONG end;
+    int64_t value;
+    ssize_t size;
+    int i, j;
     int ret;
-    int i;
 
     NtQuerySystemTime( &now );
     if (timeout)
@@ -648,9 +650,6 @@ NTSTATUS esync_wait_objects( DWORD count, const HANDLE *handles, BOOLEAN wait_an
 
                     if (obj)
                     {
-                        int64_t value;
-                        ssize_t size;
-
                         if (obj->type == ESYNC_MANUAL_EVENT || obj->type == ESYNC_MANUAL_SERVER)
                         {
                             /* Don't grab the object, just check if it's signaled. */
@@ -688,21 +687,158 @@ NTSTATUS esync_wait_objects( DWORD count, const HANDLE *handles, BOOLEAN wait_an
                  * we were waiting for. So keep waiting. */
                 NtQuerySystemTime( &now );
             }
-            else if (ret == 0)
+            else
+                goto err;
+        }
+    }
+    else
+    {
+        /* Wait-all is a little trickier to implement correctly. Fortunately,
+         * it's not as common.
+         *
+         * The idea is basically just to wait in sequence on every object in the
+         * set. Then when we're done, try to grab them all in a tight loop. If
+         * that fails, release any resources we've grabbed (and yes, we can
+         * reliably do this—it's just mutexes and semaphores that we have to
+         * put back, and in both cases we just put back 1), and if any of that
+         * fails we start over.
+         *
+         * What makes this inherently bad is that we might temporarily grab a
+         * resource incorrectly. Hopefully it'll be quick (and hey, it won't
+         * block on wineserver) so nobody will notice. Besides, consider: if
+         * object A becomes signaled but someone grabs it before we can grab it
+         * and everything else, then they could just as well have grabbed it
+         * before it became signaled. Similarly if object A was signaled and we
+         * were blocking on object B, then B becomes available and someone grabs
+         * A before we can, then they might have grabbed A before B became
+         * signaled. In either case anyone who tries to wait on A or B will be
+         * waiting for an instant while we put things back. */
+
+        while (1)
+        {
+tryagain:
+            /* First step: try to poll on each object in sequence. */
+            fds[0].events = POLLIN;
+            for (i = 0; i < count; i++)
             {
-                TRACE("Wait timed out.\n");
-                return STATUS_TIMEOUT;
+                struct esync *obj = objs[i];
+
+                fds[0].fd = obj ? obj->fd : -1;
+
+                if (obj && obj->type == ESYNC_MUTEX)
+                {
+                    /* It might be ours. */
+                    struct mutex *mutex = (struct mutex *)obj;
+
+                    if (mutex->tid == GetCurrentThreadId())
+                        continue;
+                }
+
+                ret = do_poll( fds, 1, timeout ? &end : NULL );
+                if (ret <= 0)
+                    goto err;
+
+                if (fds[0].revents & (POLLHUP | POLLERR | POLLNVAL))
+                {
+                    ERR("Polling on fd %d returned %#x.\n", fds[0].fd, fds[0].revents);
+                    return STATUS_INVALID_HANDLE;
+                }
             }
-            else
+
+            /* Don't forget to wait for driver messages. */
+            if (msgwait)
             {
-                ERR("ppoll failed: %s\n", strerror(errno));
-                return FILE_GetNtStatus();
+                fds[0].fd = ntdll_get_thread_data()->esync_queue_fd;
+                ret = do_poll( fds, 1, timeout ? &end : NULL );
+                if (ret <= 0)
+                    goto err;
             }
-        }
+
+            /* If we got here and we haven't timed out, that means all of the
+             * handles were signaled. Check to make sure they still are. */
+            for (i = 0; i < count; i++)
+            {
+                fds[i].fd = objs[i] ? objs[i]->fd : -1;
+                fds[i].events = POLLIN;
+            }
+            if (msgwait)
+            {
+                fds[count].fd = ntdll_get_thread_data()->esync_queue_fd;
+                fds[count].events = POLLIN;
+                pollcount++;
+            }
+
+            /* Poll everything to see if they're still signaled. */
+            ret = poll( fds, pollcount, 0 );
+            if (ret == pollcount)
+            {
+                /* Quick, grab everything. */
+                for (i = 0; i < pollcount; i++)
+                {
+                    struct esync *obj = objs[i];
+
+                    switch (obj->type)
+                    {
+                    case ESYNC_MUTEX:
+                    {
+                        struct mutex *mutex = (struct mutex *)obj;
+                        if (mutex->tid == GetCurrentThreadId())
+                            break;
+                        /* otherwise fall through */
+                    }
+                    case ESYNC_SEMAPHORE:
+                    case ESYNC_AUTO_EVENT:
+                        if ((size = read( fds[i].fd, &value, sizeof(value) )) != sizeof(value))
+                        {
+                            /* We were too slow. Put everything back. */
+                            value = 1;
+                            for (j = i; j >= 0; j--)
+                            {
+                                if (write( obj->fd, &value, sizeof(value) ) == -1)
+                                    return FILE_GetNtStatus();
+                            }
+
+                            goto tryagain;  /* break out of two loops and a switch */
+                        }
+                        break;
+                    default:
+                        /* If a manual-reset event changed between there and
+                         * here, it's shouldn't be a problem. */
+                        break;
+                    }
+                }
+
+                /* If we got here, we successfully waited on every object. */
+                /* Make sure to let ourselves know that we grabbed the mutexes. */
+                for (i = 0; i < count; i++)
+                {
+                    if (objs[i]->type == ESYNC_MUTEX)
+                    {
+                        struct mutex *mutex = (struct mutex *)objs[i];
+                        mutex->tid = GetCurrentThreadId();
+                        mutex->count++;
+                    }
+                }
+                TRACE("Wait successful.\n");
+                return STATUS_SUCCESS;
+            }
+
+            /* If we got here, ppoll() returned less than all of our objects.
+             * So loop back to the beginning and try again. */
+        } /* while(1) */
+    } /* else (wait-all) */
+
+err:
+    /* We should only get here if poll() failed. */
+
+    if (ret == 0)
+    {
+        TRACE("Wait timed out.\n");
+        return STATUS_TIMEOUT;
     }
     else
     {
-        FIXME("Wait-all not implemented.\n");
-        return STATUS_NOT_IMPLEMENTED;
+        ERR("ppoll failed: %s\n", strerror(errno));
+        return FILE_GetNtStatus();
     }
 }
-- 
2.20.1

