File fsync_futex_waitv.patch of Package wine

From 04904bbab87d5662e40743f26534a74ee7d2c943 Mon Sep 17 00:00:00 2001
From: Tk-Glitch <ti3nou@gmail.com>
Date: Wed, 16 Mar 2022 11:50:28 +0100
Subject: Import futex_waitv support from Proton Experimental
 https://github.com/ValveSoftware/wine/tree/205874085341305cac84ae61a0141d2fa5b892dd
 From ntdll/fsync: Support futex_waitv() API to ntdll: Include linux/futex.h in fsync.c


diff --git a/configure b/configure
index 7ea8b1c0ceb..03533857085 100755
--- a/configure
+++ b/configure
@@ -7982,6 +7982,12 @@ if test "x$ac_cv_header_linux_filter_h" = xyes
 then :
   printf "%s\n" "#define HAVE_LINUX_FILTER_H 1" >>confdefs.h
 
+fi
+ac_fn_c_check_header_compile "$LINENO" "linux/futex.h" "ac_cv_header_linux_futex_h" "$ac_includes_default"
+if test "x$ac_cv_header_linux_futex_h" = xyes
+then :
+  printf "%s\n" "#define HAVE_LINUX_FUTEX_H 1" >>confdefs.h
+
 fi
 ac_fn_c_check_header_compile "$LINENO" "linux/hdreg.h" "ac_cv_header_linux_hdreg_h" "$ac_includes_default"
 if test "x$ac_cv_header_linux_hdreg_h" = xyes
diff --git a/configure.ac b/configure.ac
index 815e40fa08d..0082bd7e4a7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -441,6 +441,7 @@ AC_CHECK_HEADERS(\
 	link.h \
 	linux/cdrom.h \
 	linux/filter.h \
+	linux/futex.h \
 	linux/hdreg.h \
 	linux/hidraw.h \
 	linux/input.h \
diff --git a/dlls/ntdll/unix/esync.c b/dlls/ntdll/unix/esync.c
index 3b97bfd62ad..0c6c5b8553b 100644
--- a/dlls/ntdll/unix/esync.c
+++ b/dlls/ntdll/unix/esync.c
@@ -953,6 +953,8 @@ static NTSTATUS __esync_wait_objects( DWORD count, const HANDLE *handles, BOOLEA
 
                     if (event->signaled)
                     {
+                        if (ac_odyssey && alertable)
+                            usleep( 0 );
                         if ((size = read( obj->fd, &value, sizeof(value) )) == sizeof(value))
                         {
                             TRACE("Woken up by handle %p [%d].\n", handles[i], i);
@@ -968,6 +970,12 @@ static NTSTATUS __esync_wait_objects( DWORD count, const HANDLE *handles, BOOLEA
 
                     if (event->signaled)
                     {
+                        if (ac_odyssey && alertable)
+                        {
+                            usleep( 0 );
+                            if (!event->signaled)
+                                break;
+                        }
                         TRACE("Woken up by handle %p [%d].\n", handles[i], i);
                         return i;
                     }
@@ -996,6 +1004,9 @@ static NTSTATUS __esync_wait_objects( DWORD count, const HANDLE *handles, BOOLEA
 
         while (1)
         {
+            if (ac_odyssey && alertable)
+                usleep( 0 );
+
             ret = do_poll( fds, pollcount, timeout ? &end : NULL );
             if (ret > 0)
             {
diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c
index d0f8be6c309..f9f6e252b6c 100644
--- a/dlls/ntdll/unix/file.c
+++ b/dlls/ntdll/unix/file.c
@@ -5363,6 +5363,230 @@ static NTSTATUS set_pending_write( HANDLE device )
     return status;
 }
 
+static pthread_mutex_t async_file_read_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t async_file_read_cond = PTHREAD_COND_INITIALIZER;
+
+struct async_file_read_job
+{
+    HANDLE handle;
+    int unix_handle;
+    int needs_close;
+    HANDLE event;
+    IO_STATUS_BLOCK *io;
+    void *buffer;
+    ULONG length;
+    LARGE_INTEGER offset;
+    DWORD thread_id;
+    LONG  cancelled;
+    struct list queue_entry;
+    struct async_file_read_job *next;
+};
+
+
+static struct list async_file_read_queue = LIST_INIT( async_file_read_queue );
+static struct async_file_read_job *async_file_read_running, *async_file_read_free;
+
+static void async_file_complete_io( struct async_file_read_job *job, NTSTATUS status, ULONG total )
+{
+    job->io->Status = status;
+    job->io->Information = total;
+
+    if (job->event) NtSetEvent( job->event, NULL );
+}
+
+static void *async_file_read_thread(void *dummy)
+{
+    struct async_file_read_job *job, *ptr;
+    ULONG buffer_length = 0;
+    void *buffer = NULL;
+    struct list *entry;
+    NTSTATUS status;
+    ULONG total;
+    int result;
+
+    pthread_mutex_lock( &async_file_read_mutex );
+    while (1)
+    {
+        while (!(entry = list_head( &async_file_read_queue )))
+        {
+            pthread_cond_wait( &async_file_read_cond, &async_file_read_mutex );
+            continue;
+        }
+
+        job = LIST_ENTRY( entry, struct async_file_read_job, queue_entry );
+        list_remove( entry );
+
+        total = 0;
+
+        if ( job->cancelled )
+        {
+            pthread_mutex_unlock( &async_file_read_mutex );
+            status = STATUS_CANCELLED;
+            goto done;
+        }
+
+        job->next = async_file_read_running;
+        async_file_read_running = job;
+        pthread_mutex_unlock( &async_file_read_mutex );
+
+        if (!buffer_length)
+        {
+            buffer = malloc(job->length);
+            buffer_length = job->length;
+        }
+        else if (buffer_length < job->length)
+        {
+            buffer = realloc(buffer, job->length);
+            buffer_length = job->length;
+        }
+
+        while ((result = pread( job->unix_handle, buffer, job->length, job->offset.QuadPart )) == -1)
+        {
+            if (errno != EINTR)
+            {
+                status = errno_to_status( errno );
+                goto done;
+            }
+            if (job->cancelled)
+                break;
+        }
+
+        total = result;
+        status = (total || !job->length) ? STATUS_SUCCESS : STATUS_END_OF_FILE;
+done:
+        if (job->needs_close) close( job->unix_handle );
+
+        if (!InterlockedCompareExchange(&job->cancelled, 1, 0))
+        {
+            if (status == STATUS_SUCCESS)
+                memcpy( job->buffer, buffer, total );
+
+            async_file_complete_io( job, status, total );
+        }
+
+        pthread_mutex_lock( &async_file_read_mutex );
+
+        if (status != STATUS_CANCELLED)
+        {
+            ptr = async_file_read_running;
+            if (job == ptr)
+            {
+                async_file_read_running = job->next;
+            }
+            else
+            {
+                while (ptr && ptr->next != job)
+                    ptr = ptr->next;
+
+                assert( ptr );
+                ptr->next = job->next;
+            }
+        }
+
+        job->next = async_file_read_free;
+        async_file_read_free = job;
+    }
+
+    return NULL;
+}
+
+static pthread_once_t async_file_read_once = PTHREAD_ONCE_INIT;
+
+static void async_file_read_init(void)
+{
+    pthread_t async_file_read_thread_id;
+    pthread_attr_t pthread_attr;
+
+    ERR("HACK: AC Odyssey async read workaround.\n");
+
+    pthread_attr_init( &pthread_attr );
+    pthread_attr_setscope( &pthread_attr, PTHREAD_SCOPE_SYSTEM );
+    pthread_attr_setdetachstate( &pthread_attr, PTHREAD_CREATE_DETACHED );
+
+    pthread_create( &async_file_read_thread_id, &pthread_attr, (void * (*)(void *))async_file_read_thread, NULL);
+    pthread_attr_destroy( &pthread_attr );
+}
+
+static NTSTATUS queue_async_file_read( HANDLE handle, int unix_handle, int needs_close, HANDLE event,
+                            IO_STATUS_BLOCK *io, void *buffer, ULONG length, LARGE_INTEGER *offset )
+{
+    struct async_file_read_job *job;
+
+    pthread_once( &async_file_read_once, async_file_read_init );
+
+    NtResetEvent( event, NULL );
+
+    pthread_mutex_lock( &async_file_read_mutex );
+
+    if (async_file_read_free)
+    {
+        job = async_file_read_free;
+        async_file_read_free = async_file_read_free->next;
+    }
+    else
+    {
+        if (!(job = malloc( sizeof(*job) )))
+        {
+            pthread_mutex_unlock( &async_file_read_mutex );
+            return STATUS_NO_MEMORY;
+        }
+    }
+
+    job->handle = handle;
+    job->unix_handle = unix_handle;
+    job->needs_close = needs_close;
+    job->event = event;
+    job->io = io;
+    job->buffer = buffer;
+    job->length = length;
+    job->offset = *offset;
+    job->thread_id = GetCurrentThreadId();
+    job->cancelled = 0;
+
+    list_add_tail( &async_file_read_queue, &job->queue_entry );
+
+    pthread_cond_signal( &async_file_read_cond );
+    pthread_mutex_unlock( &async_file_read_mutex );
+
+    return STATUS_PENDING;
+}
+
+static NTSTATUS cancel_async_file_read( HANDLE handle, IO_STATUS_BLOCK *io )
+{
+    DWORD thread_id = GetCurrentThreadId();
+    struct async_file_read_job *job;
+    unsigned int count = 0;
+
+    TRACE( "handle %p, io %p.\n", handle, io );
+
+    pthread_mutex_lock( &async_file_read_mutex );
+    job = async_file_read_running;
+    while (job)
+    {
+        if (((io && job->io == io)
+                || (!io && job->handle == handle && job->thread_id == thread_id))
+                && !InterlockedCompareExchange(&job->cancelled, 1, 0))
+        {
+            async_file_complete_io( job, STATUS_CANCELLED, 0 );
+            ++count;
+        }
+        job = job->next;
+    }
+
+    LIST_FOR_EACH_ENTRY( job, &async_file_read_queue, struct async_file_read_job, queue_entry )
+    {
+        if (((io && job->io == io)
+                || (!io && job->handle == handle && job->thread_id == thread_id))
+                && !InterlockedCompareExchange(&job->cancelled, 1, 0))
+        {
+            async_file_complete_io( job, STATUS_CANCELLED, 0 );
+            ++count;
+        }
+    }
+
+    pthread_mutex_unlock( &async_file_read_mutex );
+    return count ? STATUS_SUCCESS : STATUS_NOT_FOUND;
+}
 
 /******************************************************************************
  *              NtReadFile   (NTDLL.@)
@@ -5404,6 +5628,13 @@ NTSTATUS WINAPI NtReadFile( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, vo
             goto done;
         }
 
+        if (ac_odyssey && async_read && length && event && !apc)
+        {
+            status = queue_async_file_read( handle, unix_handle, needs_close, event, io, buffer, length, offset );
+            needs_close = 0;
+            goto err;
+        }
+
         if (offset && offset->QuadPart != FILE_USE_FILE_POINTER_POSITION)
         {
             /* async I/O doesn't make sense on regular files */
@@ -6823,6 +7054,9 @@ NTSTATUS WINAPI NtCancelIoFile( HANDLE handle, IO_STATUS_BLOCK *io_status )
 
     TRACE( "%p %p\n", handle, io_status );
 
+    if (ac_odyssey && !cancel_async_file_read( handle, NULL ))
+        return (io_status->Status = STATUS_SUCCESS);
+
     SERVER_START_REQ( cancel_async )
     {
         req->handle      = wine_server_obj_handle( handle );
@@ -6848,6 +7082,9 @@ NTSTATUS WINAPI NtCancelIoFileEx( HANDLE handle, IO_STATUS_BLOCK *io, IO_STATUS_
 
     TRACE( "%p %p %p\n", handle, io, io_status );
 
+    if (ac_odyssey && !cancel_async_file_read( handle, io ))
+        return (io_status->Status = STATUS_SUCCESS);
+
     SERVER_START_REQ( cancel_async )
     {
         req->handle = wine_server_obj_handle( handle );
diff --git a/dlls/ntdll/unix/fsync.c b/dlls/ntdll/unix/fsync.c
index f5ae0a821b0..284e2fcce01 100644
--- a/dlls/ntdll/unix/fsync.c
+++ b/dlls/ntdll/unix/fsync.c
@@ -27,6 +27,9 @@
 #include <assert.h>
 #include <errno.h>
 #include <fcntl.h>
+#ifdef HAVE_LINUX_FUTEX_H
+# include <linux/futex.h>
+#endif
 #include <limits.h>
 #include <stdarg.h>
 #include <stdio.h>
@@ -39,6 +42,7 @@
 # include <sys/syscall.h>
 #endif
 #include <unistd.h>
+#include <stdint.h>
 
 #include "ntstatus.h"
 #define WIN32_NO_STATUS
@@ -54,43 +58,82 @@
 WINE_DEFAULT_DEBUG_CHANNEL(fsync);
 
 #include "pshpack4.h"
-struct futex_wait_block
-{
-    int *addr;
-#if __SIZEOF_POINTER__ == 4
-    int pad;
+#include "poppack.h"
+
+/* futex_waitv interface */
+
+#ifndef __NR_futex_waitv
+# define __NR_futex_waitv 449
 #endif
-    int val;
-    int bitset;
+
+#ifndef FUTEX_32
+# define FUTEX_32 2
+struct futex_waitv {
+    uint64_t   val;
+    uint64_t   uaddr;
+    uint32_t   flags;
+    uint32_t __reserved;
 };
-#include "poppack.h"
+#endif
+
+#define u64_to_ptr(x) (void *)(uintptr_t)(x)
 
-static inline void small_pause(void)
+struct timespec64
 {
-#if defined(__i386__) || defined(__x86_64__)
-    __asm__ __volatile__( "rep;nop" : : : "memory" );
-#else
-    __asm__ __volatile__( "" : : : "memory" );
-#endif
+    long long tv_sec;
+    long long tv_nsec;
+};
+
+static LONGLONG update_timeout( ULONGLONG end )
+{
+    LARGE_INTEGER now;
+    LONGLONG timeleft;
+
+    NtQuerySystemTime( &now );
+    timeleft = end - now.QuadPart;
+    if (timeleft < 0) timeleft = 0;
+    return timeleft;
 }
 
-static inline int futex_wait_multiple( const struct futex_wait_block *futexes,
-        int count, const struct timespec *timeout )
+static inline void futex_vector_set( struct futex_waitv *waitv, int *addr, int val )
 {
-    return syscall( __NR_futex, futexes, 31, count, timeout, 0, 0 );
+    waitv->uaddr = (uintptr_t) addr;
+    waitv->val = val;
+    waitv->flags = FUTEX_32;
+    waitv->__reserved = 0;
 }
 
-static inline int futex_wake( int *addr, int val )
+static void simulate_sched_quantum(void)
 {
-    return syscall( __NR_futex, addr, 1, val, NULL, 0, 0 );
+    if (!fsync_simulate_sched_quantum) return;
+    /* futex wait is often very quick to resume a waiting thread when woken.
+     * That reveals synchonization bugs in some games which happen to work on
+     * Windows due to the waiting threads having some minimal delay to wake up. */
+    usleep(0);
 }
 
-static inline int futex_wait( int *addr, int val, struct timespec *timeout )
+static inline int futex_wait_multiple( const struct futex_waitv *futexes,
+        int count, const ULONGLONG *end )
 {
-    return syscall( __NR_futex, addr, 0, val, timeout, 0, 0 );
+   if (end)
+   {
+        struct timespec64 timeout;
+        ULONGLONG tmp = *end - SECS_1601_TO_1970 * TICKSPERSEC;
+        timeout.tv_sec = tmp / (ULONGLONG)TICKSPERSEC;
+        timeout.tv_nsec = (tmp % TICKSPERSEC) * 100;
+
+        return syscall( __NR_futex_waitv, futexes, count, 0, &timeout, CLOCK_REALTIME );
+   }
+   else
+   {
+        return syscall( __NR_futex_waitv, futexes, count, 0, NULL, 0 );
+   }
 }
 
-static unsigned int spincount = 100;
+static inline int futex_wake( int *addr, int val )
+{
+    return syscall( __NR_futex, addr, 1, val, NULL, 0, 0 );
+}
 
 int do_fsync(void)
 {
@@ -99,11 +142,16 @@ int do_fsync(void)
 
     if (do_fsync_cached == -1)
     {
-        static const struct timespec zero;
-        futex_wait_multiple( NULL, 0, &zero );
+        FILE *f;
+        if ((f = fopen( "/sys/kernel/futex2/wait", "r" )))
+        {
+            fclose(f);
+            do_fsync_cached = 0;
+            return do_fsync_cached;
+        }
+
+        syscall( __NR_futex_waitv, NULL, 0, 0, NULL, 0 );
         do_fsync_cached = getenv("WINEFSYNC") && atoi(getenv("WINEFSYNC")) && errno != ENOSYS;
-        if (getenv("WINEFSYNC_SPINCOUNT"))
-            spincount = atoi(getenv("WINEFSYNC_SPINCOUNT"));
     }
 
     return do_fsync_cached;
@@ -646,64 +694,30 @@ NTSTATUS fsync_query_mutex( HANDLE handle, void *info, ULONG *ret_len )
     return STATUS_SUCCESS;
 }
 
-static LONGLONG update_timeout( ULONGLONG end )
-{
-    LARGE_INTEGER now;
-    LONGLONG timeleft;
-
-    NtQuerySystemTime( &now );
-    timeleft = end - now.QuadPart;
-    if (timeleft < 0) timeleft = 0;
-    return timeleft;
-}
-
 static NTSTATUS do_single_wait( int *addr, int val, ULONGLONG *end, BOOLEAN alertable )
 {
+    struct futex_waitv futexes[2];
     int ret;
 
+    futex_vector_set( &futexes[0], addr, val );
+
     if (alertable)
     {
         int *apc_futex = ntdll_get_thread_data()->fsync_apc_futex;
-        struct futex_wait_block futexes[2];
 
         if (__atomic_load_n( apc_futex, __ATOMIC_SEQ_CST ))
             return STATUS_USER_APC;
 
-        futexes[0].addr = addr;
-        futexes[0].val = val;
-        futexes[1].addr = apc_futex;
-        futexes[1].val = 0;
-#if __SIZEOF_POINTER__ == 4
-        futexes[0].pad = futexes[1].pad = 0;
-#endif
-        futexes[0].bitset = futexes[1].bitset = ~0;
+        futex_vector_set( &futexes[1], apc_futex, 0 );
 
-        if (end)
-        {
-            LONGLONG timeleft = update_timeout( *end );
-            struct timespec tmo_p;
-            tmo_p.tv_sec = timeleft / (ULONGLONG)TICKSPERSEC;
-            tmo_p.tv_nsec = (timeleft % TICKSPERSEC) * 100;
-            ret = futex_wait_multiple( futexes, 2, &tmo_p );
-        }
-        else
-            ret = futex_wait_multiple( futexes, 2, NULL );
+        ret = futex_wait_multiple( futexes, 2, end );
 
         if (__atomic_load_n( apc_futex, __ATOMIC_SEQ_CST ))
             return STATUS_USER_APC;
     }
     else
     {
-        if (end)
-        {
-            LONGLONG timeleft = update_timeout( *end );
-            struct timespec tmo_p;
-            tmo_p.tv_sec = timeleft / (ULONGLONG)TICKSPERSEC;
-            tmo_p.tv_nsec = (timeleft % TICKSPERSEC) * 100;
-            ret = futex_wait( addr, val, &tmo_p );
-        }
-        else
-            ret = futex_wait( addr, val, NULL );
+        ret = futex_wait_multiple( futexes, 1, end );
     }
 
     if (!ret)
@@ -719,12 +733,11 @@ static NTSTATUS __fsync_wait_objects( DWORD count, const HANDLE *handles,
 {
     static const LARGE_INTEGER zero = {0};
 
-    struct futex_wait_block futexes[MAXIMUM_WAIT_OBJECTS + 1];
+    struct futex_waitv futexes[MAXIMUM_WAIT_OBJECTS + 1];
     struct fsync *objs[MAXIMUM_WAIT_OBJECTS];
+    BOOL msgwait = FALSE, waited = FALSE;
     int has_fsync = 0, has_server = 0;
-    BOOL msgwait = FALSE;
     int dummy_futex = 0;
-    unsigned int spin;
     LONGLONG timeleft;
     LARGE_INTEGER now;
     DWORD waitcount;
@@ -834,23 +847,15 @@ static NTSTATUS __fsync_wait_objects( DWORD count, const HANDLE *handles,
                         struct semaphore *semaphore = obj->shm;
                         int current;
 
-                        /* It would be a little clearer (and less error-prone)
-                         * to use a dedicated interlocked_dec_if_nonzero()
-                         * helper, but nesting loops like that is probably not
-                         * great for performance... */
-                        for (spin = 0; spin <= spincount || current; ++spin)
+                        if ((current = __atomic_load_n( &semaphore->count, __ATOMIC_SEQ_CST ))
+                                && __sync_val_compare_and_swap( &semaphore->count, current, current - 1 ) == current)
                         {
-                            if ((current = __atomic_load_n( &semaphore->count, __ATOMIC_SEQ_CST ))
-                                    && __sync_val_compare_and_swap( &semaphore->count, current, current - 1 ) == current)
-                            {
-                                TRACE("Woken up by handle %p [%d].\n", handles[i], i);
-                                return i;
-                            }
-                            small_pause();
+                            TRACE("Woken up by handle %p [%d].\n", handles[i], i);
+                            if (waited) simulate_sched_quantum();
+                            return i;
                         }
 
-                        futexes[i].addr = &semaphore->count;
-                        futexes[i].val = 0;
+                        futex_vector_set( &futexes[i], &semaphore->count, 0 );
                         break;
                     }
                     case FSYNC_MUTEX:
@@ -862,28 +867,25 @@ static NTSTATUS __fsync_wait_objects( DWORD count, const HANDLE *handles,
                         {
                             TRACE("Woken up by handle %p [%d].\n", handles[i], i);
                             mutex->count++;
+                            if (waited) simulate_sched_quantum();
                             return i;
                         }
 
-                        for (spin = 0; spin <= spincount; ++spin)
+                        if (!(tid = __sync_val_compare_and_swap( &mutex->tid, 0, GetCurrentThreadId() )))
                         {
-                            if (!(tid = __sync_val_compare_and_swap( &mutex->tid, 0, GetCurrentThreadId() )))
-                            {
-                                TRACE("Woken up by handle %p [%d].\n", handles[i], i);
-                                mutex->count = 1;
-                                return i;
-                            }
-                            else if (tid == ~0 && (tid = __sync_val_compare_and_swap( &mutex->tid, ~0, GetCurrentThreadId() )) == ~0)
-                            {
-                                TRACE("Woken up by abandoned mutex %p [%d].\n", handles[i], i);
-                                mutex->count = 1;
-                                return STATUS_ABANDONED_WAIT_0 + i;
-                            }
-                            small_pause();
+                            TRACE("Woken up by handle %p [%d].\n", handles[i], i);
+                            mutex->count = 1;
+                            if (waited) simulate_sched_quantum();
+                            return i;
+                        }
+                        else if (tid == ~0 && (tid = __sync_val_compare_and_swap( &mutex->tid, ~0, GetCurrentThreadId() )) == ~0)
+                        {
+                            TRACE("Woken up by abandoned mutex %p [%d].\n", handles[i], i);
+                            mutex->count = 1;
+                            return STATUS_ABANDONED_WAIT_0 + i;
                         }
 
-                        futexes[i].addr = &mutex->tid;
-                        futexes[i].val  = tid;
+                        futex_vector_set( &futexes[i], &mutex->tid, tid );
                         break;
                     }
                     case FSYNC_AUTO_EVENT:
@@ -891,18 +893,17 @@ static NTSTATUS __fsync_wait_objects( DWORD count, const HANDLE *handles,
                     {
                         struct event *event = obj->shm;
 
-                        for (spin = 0; spin <= spincount; ++spin)
+                        if (__sync_val_compare_and_swap( &event->signaled, 1, 0 ))
                         {
-                            if (__sync_val_compare_and_swap( &event->signaled, 1, 0 ))
-                            {
-                                TRACE("Woken up by handle %p [%d].\n", handles[i], i);
-                                return i;
-                            }
-                            small_pause();
+                            if (ac_odyssey && alertable)
+                                usleep( 0 );
+
+                            TRACE("Woken up by handle %p [%d].\n", handles[i], i);
+                            if (waited) simulate_sched_quantum();
+                            return i;
                         }
 
-                        futexes[i].addr = &event->signaled;
-                        futexes[i].val = 0;
+                        futex_vector_set( &futexes[i], &event->signaled, 0 );
                         break;
                     }
                     case FSYNC_MANUAL_EVENT:
@@ -911,18 +912,17 @@ static NTSTATUS __fsync_wait_objects( DWORD count, const HANDLE *handles,
                     {
                         struct event *event = obj->shm;
 
-                        for (spin = 0; spin <= spincount; ++spin)
+                        if (__atomic_load_n( &event->signaled, __ATOMIC_SEQ_CST ))
                         {
-                            if (__atomic_load_n( &event->signaled, __ATOMIC_SEQ_CST ))
-                            {
-                                TRACE("Woken up by handle %p [%d].\n", handles[i], i);
-                                return i;
-                            }
-                            small_pause();
+                            if (ac_odyssey && alertable)
+                                usleep( 0 );
+
+                            TRACE("Woken up by handle %p [%d].\n", handles[i], i);
+                            if (waited) simulate_sched_quantum();
+                            return i;
                         }
 
-                        futexes[i].addr = &event->signaled;
-                        futexes[i].val = 0;
+                        futex_vector_set( &futexes[i], &event->signaled, 0 );
                         break;
                     }
                     default:
@@ -933,31 +933,22 @@ static NTSTATUS __fsync_wait_objects( DWORD count, const HANDLE *handles,
                 else
                 {
                     /* Avoid breaking things entirely. */
-                    futexes[i].addr = &dummy_futex;
-                    futexes[i].val = dummy_futex;
+                    futex_vector_set( &futexes[i], &dummy_futex, dummy_futex );
                 }
-
-#if __SIZEOF_POINTER__ == 4
-                futexes[i].pad = 0;
-#endif
-                futexes[i].bitset = ~0;
             }
 
             if (alertable)
             {
                 /* We already checked if it was signaled; don't bother doing it again. */
-                futexes[i].addr = ntdll_get_thread_data()->fsync_apc_futex;
-                futexes[i].val = 0;
-#if __SIZEOF_POINTER__ == 4
-                futexes[i].pad = 0;
-#endif
-                futexes[i].bitset = ~0;
-                i++;
+                futex_vector_set( &futexes[i++], ntdll_get_thread_data()->fsync_apc_futex, 0 );
             }
             waitcount = i;
 
             /* Looks like everything is contended, so wait. */
 
+            if (ac_odyssey && alertable)
+                usleep( 0 );
+
             if (timeout && !timeout->QuadPart)
             {
                 /* Unlike esync, we already know that we've timed out, so we
@@ -965,17 +956,8 @@ static NTSTATUS __fsync_wait_objects( DWORD count, const HANDLE *handles,
                 TRACE("Wait timed out.\n");
                 return STATUS_TIMEOUT;
             }
-            else if (timeout)
-            {
-                LONGLONG timeleft = update_timeout( end );
-                struct timespec tmo_p;
-                tmo_p.tv_sec = timeleft / (ULONGLONG)TICKSPERSEC;
-                tmo_p.tv_nsec = (timeleft % TICKSPERSEC) * 100;
 
-                ret = futex_wait_multiple( futexes, waitcount, &tmo_p );
-            }
-            else
-                ret = futex_wait_multiple( futexes, waitcount, NULL );
+            ret = futex_wait_multiple( futexes, waitcount, timeout ? &end : NULL );
 
             /* FUTEX_WAIT_MULTIPLE can succeed or return -EINTR, -EAGAIN,
              * -EFAULT/-EACCES, -ETIMEDOUT. In the first three cases we need to
@@ -986,6 +968,7 @@ static NTSTATUS __fsync_wait_objects( DWORD count, const HANDLE *handles,
                 TRACE("Wait timed out.\n");
                 return STATUS_TIMEOUT;
             }
+            else waited = TRUE;
         } /* while (1) */
     }
     else
@@ -1090,6 +1073,7 @@ tryagain:
             for (i = 0; i < count; i++)
             {
                 struct fsync *obj = objs[i];
+                if (!obj) continue;
                 switch (obj->type)
                 {
                 case FSYNC_MUTEX:
@@ -1109,7 +1093,10 @@ tryagain:
                 case FSYNC_SEMAPHORE:
                 {
                     struct semaphore *semaphore = obj->shm;
-                    if (__sync_fetch_and_sub( &semaphore->count, 1 ) <= 0)
+                    int current;
+
+                    if (!(current = __atomic_load_n( &semaphore->count, __ATOMIC_SEQ_CST ))
+                            || __sync_val_compare_and_swap( &semaphore->count, current, current - 1 ) != current)
                         goto tooslow;
                     break;
                 }
@@ -1151,6 +1138,7 @@ tooslow:
             for (--i; i >= 0; i--)
             {
                 struct fsync *obj = objs[i];
+                if (!obj) continue;
                 switch (obj->type)
                 {
                 case FSYNC_MUTEX:
diff --git a/dlls/ntdll/unix/loader.c b/dlls/ntdll/unix/loader.c
index 0360bc64b00..f30d900a07b 100644
--- a/dlls/ntdll/unix/loader.c
+++ b/dlls/ntdll/unix/loader.c
@@ -2028,6 +2028,34 @@ const unixlib_entry_t __wine_unix_call_wow64_funcs[] =
 };

 #endif  /* _WIN64 */
+
+BOOL ac_odyssey;
+BOOL fsync_simulate_sched_quantum;
+
+static void hacks_init(void)
+{
+    static const char upc_exe[] = "Ubisoft Game Launcher\\upc.exe";
+    static const char ac_odyssey_exe[] = "ACOdyssey.exe";
+    const char *env_str;
+
+    if (main_argc > 1 && strstr(main_argv[1], ac_odyssey_exe))
+    {
+        ERR("HACK: AC Odyssey sync tweak on.\n");
+        ac_odyssey = TRUE;
+        return;
+    }
+    env_str = getenv("WINE_FSYNC_SIMULATE_SCHED_QUANTUM");
+    if (env_str)
+        fsync_simulate_sched_quantum = !!atoi(env_str);
+    else if (main_argc > 1)
+        fsync_simulate_sched_quantum = !!strstr(main_argv[1], upc_exe);
+    if (fsync_simulate_sched_quantum)
+        ERR("HACK: Simulating sched quantum in fsync.\n");
+
+    env_str = getenv("SteamGameId");
+    if (env_str && !strcmp(env_str, "50130"))
+        setenv("WINESTEAMNOEXEC", "1", 0);
+}
 
 /***********************************************************************
  *           start_main_thread
@@ -2041,6 +2041,7 @@ static void start_main_thread(void)
     signal_alloc_thread( teb );
     dbg_init();
     startup_info_size = server_init_process();
+    hacks_init();
     fsync_init();
     esync_init();
     virtual_map_user_shared_data();
diff --git a/dlls/ntdll/unix/unix_private.h b/dlls/ntdll/unix/unix_private.h
index dbd3e645976..683c2a4f1c2 100644
--- a/dlls/ntdll/unix/unix_private.h
+++ b/dlls/ntdll/unix/unix_private.h
@@ -146,6 +146,9 @@ #ifdef __i386__
 extern struct ldt_copy __wine_ldt_copy;
 #endif
 
+extern BOOL ac_odyssey;
+extern BOOL fsync_simulate_sched_quantum;
+
 extern void init_environment(void);
 extern void init_startup_info(void);
 extern void *create_startup_info( const UNICODE_STRING *nt_image, const RTL_USER_PROCESS_PARAMETERS *params,
diff --git a/include/config.h.in b/include/config.h.in
index 8f9b9737b24..4670e4bc881 100644
--- a/include/config.h.in
+++ b/include/config.h.in
@@ -188,6 +188,9 @@
 /* Define to 1 if you have the <linux/filter.h> header file. */
 #undef HAVE_LINUX_FILTER_H
 
+/* Define to 1 if you have the <linux/futex.h> header file. */
+#undef HAVE_LINUX_FUTEX_H
+
 /* Define if Linux-style gethostbyname_r and gethostbyaddr_r are available */
 #undef HAVE_LINUX_GETHOSTBYNAME_R_6
 
diff --git a/server/fsync.c b/server/fsync.c
index af1c68f2a90..2b8c5e4bc15 100644
--- a/server/fsync.c
+++ b/server/fsync.c
@@ -44,21 +44,11 @@
 #include "fsync.h"
 
 #include "pshpack4.h"
-struct futex_wait_block
-{
-    int *addr;
-#if __SIZEOF_POINTER__ == 4
-    int pad;
-#endif
-    int val;
-};
 #include "poppack.h"
 
-static inline int futex_wait_multiple( const struct futex_wait_block *futexes,
-        int count, const struct timespec *timeout )
-{
-    return syscall( __NR_futex, futexes, 31, count, timeout, 0, 0 );
-}
+#ifndef __NR_futex_waitv
+#define __NR_futex_waitv 449
+#endif
 
 int do_fsync(void)
 {
@@ -67,8 +57,16 @@ int do_fsync(void)
 
     if (do_fsync_cached == -1)
     {
-        static const struct timespec zero;
-        futex_wait_multiple( NULL, 0, &zero );
+        FILE *f;
+        if ((f = fopen( "/sys/kernel/futex2/wait", "r" )))
+        {
+            fclose(f);
+            do_fsync_cached = 0;
+            fprintf( stderr, "fsync: old futex2 patches detected, disabling.\n" );
+            return do_fsync_cached;
+        }
+
+        syscall( __NR_futex_waitv, 0, 0, 0, 0, 0);
         do_fsync_cached = getenv("WINEFSYNC") && atoi(getenv("WINEFSYNC")) && errno != ENOSYS;
     }
 
openSUSE Build Service is sponsored by