[Bug 47843] [Rockstar Game Launcher]Unable to download a game completly

WineHQ Bugzilla wine-bugs at winehq.org
Tue Oct 22 15:13:52 CDT 2019


https://bugs.winehq.org/show_bug.cgi?id=47843

--- Comment #2 from Brendan Shanks <bshanks at codeweavers.com> ---
I spent some time looking into this issue, it's not related to bcrypt but is
actually a race condition caused by some shaky assumptions in the launcher
service and how Wine implements the NT thread pool.

There's multiple processes involved: Launcher.exe is the GUI app.
RockstarService.exe is the background service, it actually does the downloading
and file I/O. They communicate through a named pipe
("\\.\pipe\MTLService_Pipe") created by the service. They also write out
separate log files: ProgramData/Rockstar Games/Launcher/service_log.txt for the
service, and users/steamuser/My\ Documents/Rockstar\
Games/Launcher/launcher.log for the launcher. The service also logs mostly the
same output with OutputDebugString().

The problem here is with the service, and it looks like this in service_log.txt
(note that there's lots of "Last error" lines printed to this log that really
aren't errors and don't matter):

[2019-10-21 15:09:11.255] [    68] [  FD] Wrote to C:\Program Files\Rockstar
Games\Grand Theft Auto San Andreas\models\gta3.img
[2019-10-21 15:09:11.256] [    68] [  6D] Failed to write to pipe.
[2019-10-21 15:09:11.256] [    68] [  6D] Last error: 996 (0x3e4): Overlapped
I/O incomplete.

[2019-10-21 15:09:11.256] [    68] [  6D] Disconnected.
[2019-10-21 15:09:11.257] [    68] [  6D] Destroying pipe...

The pipe then gets re-created, the whole service is restarted, and this is when
the download stops. "Failed to write to pipe" is the error.

The service uses overlapped I/O to write to files, and before WriteFile() is
called it uses RegisterWaitForSingleObject() to register a callback to fire
when the overlapped I/O event is signaled/finished. The thread calls
WriteFile() for the file, it returns quickly, and then WriteFile() is used to
write a small message to the pipe (also overlapped).
The file write callback just calls GetOverlappedResult(), if it succeeds it
does some housekeeping (log messages, unregister wait, close handles) and also
writes a small message to the pipe.

The problem is, the same OVERLAPPED structure is used for all writing to the
pipe, regardless of what thread is doing the writing. There's also a callback
registered for this overlapped event, it calls GetOverlappedResult() to confirm
success.
Eventually the pipe write from the original thread and from the file write
callback happen at almost the same time. The pipe write callback calls
GetOverlappedResult() on the shared OVERLAPPED which has been set to pending by
the WriteFile() call happening simultaneously in the other thread,
GetOverlappedResult() returns FALSE, it logs the "Failed to write to pipe"
message, and starts tearing everything down.


Next question: why doesn't this break on Windows? The service calls
RegisterWaitForSingleObject() with the WT_EXECUTEINWAITTHREAD flag. An
understanding of the Windows thread pool is needed: Windows has a number of
"wait threads" which each wait on multiple objects. When an object is signaled,
normally the wait thread queues the callback to be executed by another thread,
and then goes back to waiting. But when the WT_EXECUTEINWAITTHREAD flag is
used, the wait thread itself calls the callback. This has the effect of
serializing all callbacks being waited on by that wait thread. In this case,
the launcher doesn't have many waits, so all the waits are handled by a single
wait thread, and all callbacks are serialized. This behavior is certainly not
guaranteed by Windows though, and shouldn't be depended upon.

In contrast, Wine's implementation spawns a new thread for each wait, and then
calls the callback from that thread. The callbacks aren't serialized, and end
up racing and causing this error.

Making Wine's thread pool implementation match Windows would be a big task. For
now I tried a hacky solution of having each thread enter a process-wide
critical section before calling the callback when WT_EXECUTEINWAITTHREAD is
supplied, and it seems to solve the issue (I can download GTA:SA with no
errors). I'll work on getting this upstream (at least into Proton).
Also I've attached a test app which reproduces the problem.

-- 
Do not reply to this email, post in Bugzilla using the
above URL to reply.
You are receiving this mail because:
You are watching all bug changes.



More information about the wine-bugs mailing list