Skip to content

server: close thread->esync_apc_fd in destroy_thread (fixes per-thr… - #33

Open
aydar-kamaltdinov wants to merge 1 commit into
GameNative:proton_10.0from
aydar-kamaltdinov:patch-1
Open

server: close thread->esync_apc_fd in destroy_thread (fixes per-thr…#33
aydar-kamaltdinov wants to merge 1 commit into
GameNative:proton_10.0from
aydar-kamaltdinov:patch-1

Conversation

@aydar-kamaltdinov

Copy link
Copy Markdown

…ead eventfd leak)

Bug

create_thread() allocates two eventfds per thread object:

thread->esync_fd     = esync_create_fd( 0, 0 );
thread->esync_apc_fd = esync_create_fd( 0, 0 );

but destroy_thread() only closes one of them. esync_apc_fd is referenced elsewhere only via esync_wake_fd() / esync_clear() (APC signaling) — never
close()d anywhere in the tree.

Consequence: every CreateThread() leaks exactly one real eventfd for the lifetime of wineserver, deterministically, on every threadeven with fully correct application code (CreateThread + WaitForSingleObject + CloseHandle). Not a race, not an edge caseit fires on every thread, unconditionally, under WINEESYNC=1.

A game/engine that spawns one short-lived worker thread per simulation tick (common patternjob systems, audio callbacks) leaks a fixed, predictable rate of real fds. Invisible on real Windows (near-unlimited handle table), fatal hereespecially on Android, where the default RLIMIT_NOFILE is far lower than desktop Linux and esync is likely to remain in use for the foreseeable future (ntsync requires Linux 6.14+, which Android vendor kernels realistically won't track for years).

Originally traced from a 100%-deterministic crash in a real commercial game (Thief, 2014) on an Android GameNative/proton-wine-derived build, crashing after a fixed ~14400 simulation ticksexactly matching a fixed-rate-per-tick fd leak hitting the fd limit.

Empirical verification

200-iteration CreateThread loop, measuring wineserver's live eventfd count via /proc/<pid>/fd:

200x CreateThread WITHOUT CloseHandle:
before fix: 852 (baseline ~452 + 200x2)
after fix:  853 (unchanged -- object never destroyed, expected)

200x CreateThread WITH CloseHandle (correct app code):
before fix: 652 (baseline ~452 + 200x1 -- leaking despite fully correct code)
after fix:  453 (= baseline, zero leak)

Related

- Filed upstream against WineHQ (original home of the esync patchset): https://bugs.winehq.org/show_bug.cgi?id=60215
- Filed against ValveSoftware/Proton, closed as out-of-scope (Proton 10 doesn't officially support ARM/Android, and esync is being phased out there in favor of ntsync) — but that doesn't apply here, since this fork is exactly the Android use case where esync will remain relevant for a long time.

---
Note: this PR description was drafted with AI assistance, but all testing, reproduction, and the empirical before/after verification were performed and manually confirmed on physical hardware.

…ead eventfd leak)

## Bug

  `create_thread()` allocates two eventfds per thread object:

  ```c
  thread->esync_fd     = esync_create_fd( 0, 0 );
  thread->esync_apc_fd = esync_create_fd( 0, 0 );

  but destroy_thread() only closes one of them. esync_apc_fd is referenced elsewhere only via esync_wake_fd() / esync_clear() (APC signaling) — never
  close()d anywhere in the tree.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant