From a42e7e6311c40b9a256d6856727d9e4f0470e3c3 Mon Sep 17 00:00:00 2001 From: Joshua Tam <297250+joshuatam@users.noreply.github.com> Date: Fri, 4 Sep 2026 00:26:16 +0900 Subject: [PATCH 1/2] ntsync: add PROTON_NO_KERNEL_NTSYNC env switch to force userspace backend Runtime escape hatch for A/B testing: when set, wineserver skips the /dev/ntsync kernel driver even if present and working, and initializes the userspace shm backend instead. Distinct log line so the forced backend is identifiable in device logs. --- android/ntsync_android/README.md | 10 +++-- .../patches/common/server_inproc_sync_c.patch | 37 +++++++++++++------ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/android/ntsync_android/README.md b/android/ntsync_android/README.md index 7f982039f38..3295a69d8b4 100644 --- a/android/ntsync_android/README.md +++ b/android/ntsync_android/README.md @@ -38,15 +38,19 @@ wineserver probes for in-process sync support once at startup 1. `PROTON_NO_NTSYNC=1` disables ntsync entirely (both kernel and userspace); wineserver falls back to server-side synchronization. -2. Otherwise it opens `/dev/ntsync` and **probes** it with a real +2. `PROTON_NO_KERNEL_NTSYNC=1` skips the kernel driver even when it is + present and working, forcing the userspace backend + (`ntsync: PROTON_NO_KERNEL_NTSYNC set, using userspace ntsync.`). + Intended for A/B testing of the two backends. +3. Otherwise it opens `/dev/ntsync` and **probes** it with a real `NTSYNC_IOC_CREATE_EVENT` ioctl (the node can exist but be unusable, e.g. SELinux policy or seccomp). If the probe succeeds, the kernel driver is used, exactly like upstream Proton. -3. If the device is missing or the probe fails, it tries +4. If the device is missing or the probe fails, it tries `ntsync_init()`; on success all processes attach to the shared-memory region and use the userspace implementation (`ntsync: no usable /dev/ntsync, using userspace ntsync.`). -4. If that also fails, wineserver falls back to server-side +5. If that also fails, wineserver falls back to server-side synchronization. wineserver tells each client which backend is in use through the diff --git a/android/patches/common/server_inproc_sync_c.patch b/android/patches/common/server_inproc_sync_c.patch index 6921e1e6c9f..8b10d07e4d6 100644 --- a/android/patches/common/server_inproc_sync_c.patch +++ b/android/patches/common/server_inproc_sync_c.patch @@ -1,5 +1,5 @@ diff --git a/server/inproc_sync.c b/server/inproc_sync.c -index a7020ef8fcd..c8691d1cd49 100644 +index a7020ef8fcd..6c6b4d4cc0f 100644 --- a/server/inproc_sync.c +++ b/server/inproc_sync.c @@ -36,7 +36,14 @@ @@ -17,7 +17,7 @@ index a7020ef8fcd..c8691d1cd49 100644 #ifdef NTSYNC_IOC_EVENT_READ -@@ -45,6 +52,36 @@ +@@ -45,18 +52,89 @@ #include #include @@ -54,13 +54,24 @@ index a7020ef8fcd..c8691d1cd49 100644 int get_inproc_device_fd(void) { static int fd = -2; -@@ -53,10 +90,39 @@ int get_inproc_device_fd(void) + if (fd == -2) + { ++#ifdef __ANDROID__ ++ /* escape hatch for A/B testing: PROTON_NO_KERNEL_NTSYNC=1 ignores a ++ * working kernel driver and forces the userspace backend */ ++ int force_userspace = getenv( "PROTON_NO_KERNEL_NTSYNC" ) && atoi(getenv( "PROTON_NO_KERNEL_NTSYNC" )); ++#endif if (getenv( "PROTON_NO_NTSYNC" ) && atoi(getenv( "PROTON_NO_NTSYNC" ))) fd = -1; else + { fd = open( "/dev/ntsync", O_CLOEXEC | O_RDONLY ); +#ifdef __ANDROID__ ++ if (force_userspace && fd >= 0) ++ { ++ close( fd ); ++ fd = -1; ++ } + if (fd >= 0) + { + /* the device node can exist but be unusable (SELinux policy, @@ -88,13 +99,15 @@ index a7020ef8fcd..c8691d1cd49 100644 do_fsync_cached = 0; +#ifdef __ANDROID__ + if (ntsync_userspace) -+ fprintf( stderr, "ntsync: no usable /dev/ntsync, using userspace ntsync.\n" ); ++ fprintf( stderr, force_userspace ++ ? "ntsync: PROTON_NO_KERNEL_NTSYNC set, using userspace ntsync.\n" ++ : "ntsync: no usable /dev/ntsync, using userspace ntsync.\n" ); + else +#endif fprintf( stderr, "ntsync: up and running.\n" ); } else if (do_fsync()) fd = FSYNC_USED_BY_SERVER; -@@ -124,6 +190,10 @@ struct inproc_sync *create_inproc_internal_sync( int manual, int signaled ) +@@ -124,6 +202,10 @@ struct inproc_sync *create_inproc_internal_sync( int manual, int signaled ) else { event->type = INPROC_SYNC_INTERNAL; @@ -105,7 +118,7 @@ index a7020ef8fcd..c8691d1cd49 100644 event->fd = ioctl( get_inproc_device_fd(), NTSYNC_IOC_CREATE_EVENT, &args ); } list_init( &event->entry ); -@@ -151,6 +221,10 @@ struct inproc_sync *create_inproc_event_sync( int manual, int signaled ) +@@ -151,6 +233,10 @@ struct inproc_sync *create_inproc_event_sync( int manual, int signaled ) else { event->type = INPROC_SYNC_EVENT; @@ -116,7 +129,7 @@ index a7020ef8fcd..c8691d1cd49 100644 event->fd = ioctl( get_inproc_device_fd(), NTSYNC_IOC_CREATE_EVENT, &args ); } list_init( &event->entry ); -@@ -178,6 +252,10 @@ struct inproc_sync *create_inproc_mutex_sync( thread_id_t owner, unsigned int co +@@ -178,6 +264,10 @@ struct inproc_sync *create_inproc_mutex_sync( thread_id_t owner, unsigned int co else { mutex->type = INPROC_SYNC_MUTEX; @@ -127,7 +140,7 @@ index a7020ef8fcd..c8691d1cd49 100644 mutex->fd = ioctl( get_inproc_device_fd(), NTSYNC_IOC_CREATE_MUTEX, &args ); } list_add_tail( &inproc_mutexes, &mutex->entry ); -@@ -206,6 +284,10 @@ struct inproc_sync *create_inproc_semaphore_sync( unsigned int initial, unsigned +@@ -206,6 +296,10 @@ struct inproc_sync *create_inproc_semaphore_sync( unsigned int initial, unsigned else { sem->type = INPROC_SYNC_SEMAPHORE; @@ -138,7 +151,7 @@ index a7020ef8fcd..c8691d1cd49 100644 sem->fd = ioctl( get_inproc_device_fd(), NTSYNC_IOC_CREATE_SEM, &args ); } list_init( &sem->entry ); -@@ -228,17 +310,23 @@ static void inproc_sync_dump( struct object *obj, int verbose ) +@@ -228,17 +322,23 @@ static void inproc_sync_dump( struct object *obj, int verbose ) void signal_inproc_sync( struct inproc_sync *sync ) { @@ -164,7 +177,7 @@ index a7020ef8fcd..c8691d1cd49 100644 else ioctl( sync->fd, NTSYNC_IOC_EVENT_RESET, &count ); } -@@ -262,6 +350,9 @@ static void inproc_sync_destroy( struct object *obj ) +@@ -262,6 +362,9 @@ static void inproc_sync_destroy( struct object *obj ) assert( obj->ops == &inproc_sync_ops ); list_remove( &sync->entry ); if (do_fsync()) fsync_free_shm_idx( sync->fd ); @@ -174,7 +187,7 @@ index a7020ef8fcd..c8691d1cd49 100644 else close( sync->fd ); } -@@ -277,7 +368,13 @@ void abandon_inproc_mutexes( thread_id_t tid ) +@@ -277,7 +380,13 @@ void abandon_inproc_mutexes( thread_id_t tid ) } LIST_FOR_EACH_ENTRY( mutex, &inproc_mutexes, struct inproc_sync, entry ) @@ -188,7 +201,7 @@ index a7020ef8fcd..c8691d1cd49 100644 } static int get_obj_inproc_sync( struct object *obj, int *type ) -@@ -359,6 +456,10 @@ DECL_HANDLER(get_inproc_sync_fd) +@@ -359,6 +468,10 @@ DECL_HANDLER(get_inproc_sync_fd) if ((fd = get_obj_inproc_sync( obj, &reply->type )) < 0) set_error( STATUS_NOT_IMPLEMENTED ); else if (do_fsync()) reply->fsync_shm_idx = fsync_grab_shm_idx( fd ); From 83b7534f9b11664d68525ffa98dd113c3c0cface Mon Sep 17 00:00:00 2001 From: Joshua Tam <297250+joshuatam@users.noreply.github.com> Date: Fri, 4 Sep 2026 00:42:43 +0900 Subject: [PATCH 2/2] ci: release notes for PROTON_NO_KERNEL_NTSYNC userspace-force switch --- .github/workflows/build-proton.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-proton.yml b/.github/workflows/build-proton.yml index bdcda26732c..208110719aa 100644 --- a/.github/workflows/build-proton.yml +++ b/.github/workflows/build-proton.yml @@ -331,7 +331,7 @@ jobs: Single SDK 28 build with 16KB page size support (works on Android 9+ and Android 15+). ### Features - - Runtime ntsync backend selection: kernel `/dev/ntsync` when present and usable (probed by wineserver at startup), otherwise experimental userspace ntsync via [ntsync-android](https://github.com/GameNative/ntsync-android) (commit [`7ce6435`](https://github.com/GameNative/ntsync-android/commit/7ce6435e5979b1cb5341aa4b299f31e8937fe121)) — plus fsync and wineserver-side `ntsync_event_set` support; `PROTON_NO_NTSYNC=1` disables ntsync entirely + - Runtime ntsync backend selection: kernel `/dev/ntsync` when present and usable (probed by wineserver at startup), otherwise experimental userspace ntsync via [ntsync-android](https://github.com/GameNative/ntsync-android) (commit [`7ce6435`](https://github.com/GameNative/ntsync-android/commit/7ce6435e5979b1cb5341aa4b299f31e8937fe121)) — plus fsync and wineserver-side `ntsync_event_set` support; `PROTON_NO_NTSYNC=1` disables ntsync entirely, `PROTON_NO_KERNEL_NTSYNC=1` forces the userspace backend - `WINE_FAST_YIELD` fast-yield hook in `NtYieldExecution` - FEX integration (stats shm, `SkipThreadAttach` detach guard) via bylaws' arm64ec patches - FEX unixlib loader: `MemoryWineLoadUnixLibByName` / wow64 / unload support with `$PREFIX/lib/wine` fallback @@ -340,8 +340,8 @@ jobs: - Stripped, `-g0 -O2` build: smaller tree, faster install (zstd-packed `.wcp`) ### ntsync changes in this build - - **Runtime kernel/userspace backend detection (proton-wine side)** — ntsync is no longer hard-selected at compile time. wineserver probes `/dev/ntsync` at startup with a real `NTSYNC_IOC_CREATE_EVENT` ioctl (catches SELinux/seccomp-blocked nodes): if the kernel driver is present and usable it is used directly; otherwise the build falls back to the userspace ntsync-android shm backend. Backend is chosen once per session and applies to all processes. - - **No shm region on kernel-ntsync devices** — the dead-process sweep is now gated on the userspace backend being active, so the ~960 KB shm file is never initialized when the kernel driver handles ntsync. + - **`PROTON_NO_KERNEL_NTSYNC=1` forces the userspace backend** — new runtime escape hatch for A/B testing: wineserver skips `/dev/ntsync` even when the kernel driver is present and working, and initializes the userspace ntsync-android shm backend instead. The forced backend is identifiable in logs: `ntsync: PROTON_NO_KERNEL_NTSYNC set, using userspace ntsync.` + - Runtime kernel/userspace backend detection (probed `NTSYNC_IOC_CREATE_EVENT`, userspace fallback, no shm region on kernel-ntsync devices) shipped in the previous build and is unchanged. - ntsync-android library is unchanged since the last build ([`7ce6435`](https://github.com/GameNative/ntsync-android/commit/7ce6435e5979b1cb5341aa4b299f31e8937fe121)); `PROTON_NO_NTSYNC=1` still disables both backends. ### Android / bionic fixes