[HN Gopher] Fixing stutters in Papers Please on Linux
       ___________________________________________________________________
        
       Fixing stutters in Papers Please on Linux
        
       Author : rdpintqogeogsaa
       Score  : 550 points
       Date   : 2022-01-02 11:20 UTC (11 hours ago)
        
 (HTM) web link (blog.jhm.dev)
 (TXT) w3m dump (blog.jhm.dev)
        
       | reallifez4 wrote:
        
       | the_af wrote:
       | This is interesting. I never noticed these pauses using the
       | native port from GOG on Ubuntu. I'm very sensitive to this kind
       | of thing (low refresh rates on CRT monitors used to drive me
       | crazy when nobody else noticed).
       | 
       | Will have to fire up my copy again. A good excuse to play this
       | marvelous game again.
        
       | reallifez4 wrote:
        
       | arghwhat wrote:
       | The issue has been identified before, but seems like it stalled:
       | https://gitlab.freedesktop.org/libinput/libinput/-/issues/50...,
       | https://patchwork.kernel.org/project/linux-input/patch/20201....
        
         | CyberRabbi wrote:
         | What the proposed patch does is delay a specific latent
         | operation to an asynchronous context so that close() doesn't
         | block on that operation (which is freeing some memory).
         | 
         | The proposed patch isn't a comprehensive fix, it admits there
         | are still other sources of relatively high close() latency.
         | 
         | So that got me thinking, there is no way to fix this "bug"
         | because there is no specification on how long close() should
         | take to complete. As far as we are promised in user-land,
         | close() is not an instantaneous operation. close() is a
         | blocking operation! Even worse, it's an IO operation.
         | 
         | So now I think the bug is in the application. If you want to
         | avoid the latency of close() you should do it asynchronously in
         | another thread. This is similar to the rule that you should not
         | do blocking IO on the main thread in an event-loop based
         | application.
        
           | londons_explore wrote:
           | close() is typically a blocking operation. But when it
           | happens in devfs, procfs, tmpfs, or some other ram only
           | filesystem I expect it to be fast unless documented
           | otherwise.
        
             | CyberRabbi wrote:
             | > I expect it to be fast unless documented otherwise.
             | 
             | Logically you should expect it to block indefinitely unless
             | documented otherwise. The exception would be completing
             | within a time bound, the rule is blocking indefinitely.
        
               | loeg wrote:
               | > Logically you should expect it to block indefinitely
               | 
               | Frankly, that's completely insane. It should block if and
               | only if there is actual io in flight which could produce
               | a failure return that an application needs. Syscalls
               | should be fast unless there is a very good reason not to
               | be.
        
               | CyberRabbi wrote:
               | > It should block if and only if there is actual io in
               | flight which could produce a failure return that an
               | application needs.
               | 
               | Blocking simply means that the specification does not
               | guarantee an upper bound on the completion time. There is
               | no other meaningful definition. POSIX is not an RTOS
               | therefore nearly all system calls block. The alternative
               | is that the specification guarantees an upper bound on
               | completion time. In that case what is an acceptable upper
               | bound for close() to complete in? 1ms? 10ms? 100ms? Any
               | answer diminishes the versatility of the POSIX VFS.
               | 
               | > Syscalls should be fast unless there is a very good
               | reason not to be.
               | 
               | I think this is an instance of confusing what should be
               | with what is. We've been through this before with
               | O_PONIES. The reality is that system calls aren't "fast"
               | and they can't portably or dynamically be guaranteed to
               | be fast. So far the only exception to this is
               | gettimeofday() and friends.
               | 
               | Robust systems aren't built on undocumented assumptions.
               | Again, POSIX is not an RTOS. Anything you build that
               | assumes a deterministic upper bound to a blocking system
               | call execution time will inevitably break, evidenced by
               | OP.
        
               | virtue3 wrote:
               | Very similar to people using node.getenv in hot sections
               | of code and the resulting not understanding what's
               | happening.
               | 
               | https://github.com/nodejs/node/issues/3104
               | 
               | When you call out to the sys or libc things are going to
               | happen and you should try and be aware of what those are.
        
               | fao_ wrote:
               | Sorry... what? Why the hell was an application using
               | env() to carry application state?!
               | 
               | The environment list is created at init, it's literally
               | placed right behind the C argument list as an array --
               | AUXV if you want to go read the ABI Specification for it.
               | 
               | Therefore, anything you grab using getenv() can be
               | considered to be static (Barring use of setenv), so the
               | proper and correct thing to do is shove the things you
               | need into a variable at init. Unless you yourself are
               | editing it, but you should still use a variable because
               | variables are typed and getenv is not (Thinking along the
               | lines of storing port information, or whatever, where you
               | need to parse it into a string to get it into the
               | environment, and then need to parse it out of a string).
               | For things like $HOME, those only ever change once, and
               | you should really have a list of those that you check,
               | because you will want to check XDG_HOME_DIR, and a few
               | other areas. So you will want those in a list anyway,
               | might as well do it at creation time when the data is
               | fresh.
               | 
               | Anything you set with setenv() only alters the your
               | environment state, and that will carry down to newly
               | created children at creation time. So the only reason I
               | can think of why anyone would do this would be to
               | communicate data to child processes. Except there are so,
               | so many better and non-stringly typed ways to do this,
               | including global variables. Child processes inherit
               | copies(?) of their parent's state, you can _just use
               | that_ , so there is literally, NO reason ever to do this.
        
               | thelopa wrote:
               | ... unless you intend to exec after forking
        
               | fao_ wrote:
               | Sure, but just use execvp and it's a damn sight safer
               | because you know exactly the state of your child's
               | environment state. You can see this in the CERT C coding
               | guidelines: https://wiki.sei.cmu.edu/confluence/display/c
               | /ENV03-C.+Sanit...
               | 
               | also ENV02-C comes into effect, as well, if your program
               | is invoked with
               | SOME_INTERNAL_VARIABLE=1 PORT=2000 ./prog
               | 
               | then you try to invoke your child with:
               | setenv("SOME_INTERNAL_VARIABLE", "2", 1);         (fork
               | blah blah)
        
               | loeg wrote:
               | > Blocking simply means that the specification does not
               | guarantee an upper bound on the completion time.
               | 
               | I don't think that's a commonly-accepted (or useful)
               | definition of "blocking." By that definition, getpid(2)
               | is blocking.
               | 
               | > I think this is an instance of confusing what should be
               | with what is.
               | 
               | Who is doing the confusing? I said "should be." Are you
               | saying they're fast now but should be slow? Why?
               | 
               | > The reality is that system calls aren't "fast" and they
               | can't portably or dynamically be guaranteed to be fast.
               | 
               | This isn't a portable program; it's a Linux program. The
               | problem isn't that close can't be portably guaranteed to
               | complete in some time bound; it's that Linux is adding
               | what is essentially an extra usleep(100000), with very
               | high probability, for the devfs synthetic filesystem in
               | Linux.
               | 
               | This is entirely an own-goal; Linux has historically
               | explicitly aimed to complete system calls quickly, when
               | that does not break other functionality. It is a bug that
               | can be fixed, e.g., with the proposed patch(es).
               | 
               | POSIX does not mandate that close blocks on anything
               | other than removing the index from the fd table -- it's
               | even allowed to leave associated IO in-flight and
               | silently ignore errors. It makes little sense for a
               | synthetic filesystem without real IO to block close so
               | grossly.
        
               | dagmx wrote:
               | CyberRabbi's definition of blocking is correct and what
               | I've always seen commonly accepted.
               | 
               | Blocking means you don't know how long it'll take, and
               | you want to wait for it to finish. The only safe
               | assumption is that you cannot guarantee how long it'll
               | take.
               | 
               | getpid is accurately therefore a blocking call. You don't
               | know how long it'll take. You can profile and make best
               | guesses, but you can never assuredly say how long it'll
               | take.
        
               | vanviegen wrote:
               | I'd say that the commonly accepted definition for a
               | blocking call is one that may depend on I/O to complete,
               | releasing control of the CPU core while waiting.
               | 
               | By that definition, getpid() is definitely nonblocking,
               | though it doesn't have an upper bound in execution time.
               | POSIX does not offer hard realtime guarantees.
               | 
               | close() in general would probably be blocking (as a
               | filesystem may need to do I/O), but I'd expect it to
               | behave nonblocking in most cases, especially when
               | operating on virtual files opened read-only.
               | Unfortunately, I don't think those kinds of behavioral
               | details are documented.
        
               | dagmx wrote:
               | A function that sleeps for 5 seconds is blocking. No IO
               | involved.
               | 
               | Blocking just means that you're blocking your current
               | code till you return out of the called function.
               | 
               | Anything else regarding a function call is an assumption
               | unless you know the exact implementation.
        
               | loeg wrote:
               | Every operation in a non-RTOS is blocking by this
               | definition, even local function calls that don't enter
               | the kernel, because the kernel may switch to another
               | thread at any time. It's utterly useless as a definition.
               | Much more common is to divide system calls into ones that
               | call depend on some external actor and those that don't.
               | Eg, recv() on a socket, blocking on a futex held by some
               | other process, or waiting on IO to some disk controller.
               | Getpid() is _synchronous_ but does not _block_.
        
               | CyberRabbi wrote:
               | Blocking in that sense is usually used in relation to
               | some event. E.g. sleep() blocks on a timer, read() blocks
               | on IO, etc.
               | 
               | In the general sense, it means that the call has an
               | indefinite run time. E.g. "this call blocks" = "this call
               | could take an arbitrarily long amount of time"
               | 
               | getpid() is blocking but it likely does not block on IO
               | (though it could as that is allowed by the spec).
        
               | dagmx wrote:
               | If you call getpid, or even local functions, can the rest
               | of your code (in a single thread) continue till getpid
               | returns?
               | 
               | E.g if you do this inside a function (useless code)
               | 
               | int pid = getpid(); std::cout << pid+2 << std::endl;
               | 
               | Will the output print even if the hypothetical call to
               | getpid takes a second?
               | 
               | If the answer is the print will wait, then it's a
               | blocking call.
               | 
               | If it was an async call, then it could happen
               | concurrently or in parallel, and unless you waited, it
               | would continue on in a non blocking fashion.
               | 
               | Waiting for a return == blocking. It may be quick but
               | unless the spec specifies that it must be
               | synchronous+non-blocking, the distinction between the two
               | is moot.
        
               | [deleted]
        
               | CyberRabbi wrote:
               | > I don't think that's a commonly-accepted (or useful)
               | definition of "blocking." By that definition, getpid(2)
               | is blocking.
               | 
               | When it comes to expecting a specific duration, getpid()
               | is blocking. If you run getpid() in a tight loop and then
               | have performance issues you can't reasonably blame the
               | system.
               | 
               | > This isn't a portable program; it's a Linux program
               | 
               | But the interface is a portable interface
               | 
               | > POSIX does not mandate that close blocks on anything
               | other than removing the index from the fd table
               | 
               | And what if the fd-table is a very large hash table with
               | high collision rate? How do you then specify how quickly
               | close() should complete? 1ms/open fd? 10ms/open fd? Etc.
               | 
               | It should be clear that the problem here is that the
               | author of the code had a faulty understanding of the
               | system in which their code runs. Today the issue was
               | close() just happened to be too "slow." If the amount of
               | input devices were higher, let's say 2x more, then the
               | same issue would have manifested even if close() were 2x
               | "faster." No matter how fast you make close() there is a
               | situation in which this issue would manifest itself. I.e.
               | the application has a design flaw.
        
               | loeg wrote:
               | > Today the issue was close() just happened to be too
               | "slow." If the amount of input devices were higher, let's
               | say 2x more, then the same issue would have manifested
               | even if close() were 2x "faster." No matter how fast you
               | make close() there is a situation in which this issue
               | would manifest itself.
               | 
               | Close, on an fd for which no asynchronous IO has
               | occurred, should be 10000x faster, or more. It's unlikely
               | a user will have even 100 real input devices. I agree the
               | algorithm leaves something to be desired, but the only
               | reason it is user-visible is the performance bug in
               | Linux.
               | 
               | I've worked on performance in both userspace and the
               | kernel and I think you're fundamentally way off-base in a
               | way we'll never reconcile.
        
               | CyberRabbi wrote:
               | > I agree the algorithm leaves something to be desired,
               | but the only reason it is user-visible is the performance
               | bug in Linux.
               | 
               | The only reason it wasn't user-visible was luck. Robust
               | applications don't depend on luck.
               | 
               | Something tells me you'll think twice before calling
               | close() in a time-sensitive context in your future
               | performance engineering endeavors. That's because both
               | you and I now know that no implementation of POSIX makes
               | any guarantee on the runtime of close() nor will likely
               | do so in the future. That's just reality kicking in.
               | Welcome to the club :)
        
               | evouga wrote:
               | > The reality is that system calls aren't "fast" and they
               | can't portably or dynamically be guaranteed to be fast.
               | 
               | Perhaps, but the reality is also that the vast majority
               | of games and other interactive applications routinely
               | make blocking system calls in a tight main loop and
               | expect these calls to take an unspecified but
               | _reasonable_ amount of time.
               | 
               | "It's a blocking syscall so if it takes 1s to close a
               | file, that's technically not a bug" is correct, but is
               | any player of "Papers, Please" going to be sympathetic to
               | that explanation? Probably not; they'll think "Linux is
               | slow," "Linux is buggy," "why can't Linux run basic
               | applications correctly that I have no problem running on
               | Windows or OS X?," etc.
               | 
               | "Syscalls should be fast unless there is a very good
               | reason not to be" strikes me as a wise operating
               | principle, which weights usability and usefulness of the
               | operating system alongside being technically correct.
        
               | CyberRabbi wrote:
               | > "It's a blocking syscall so if it takes 1s to close a
               | file, that's technically not a bug" is correct, but is
               | any player of "Papers, Please" going to be sympathetic to
               | that explanation? Probably not; they'll think "Linux is
               | slow," "Linux is buggy," "why can't Linux run basic
               | applications correctly that I have no problem running on
               | Windows or OS X?," etc.
               | 
               | I don't agree with this logic. Windows and macOS system
               | calls also block. The issue of people considering Linux
               | to be slow is not relevant to the fact that its systems
               | calls block. The poorer quality of Linux games, and
               | commercial Linux software in general, is more likely due
               | to smaller market size / profit opportunity and the
               | consequential lack of effort / investment into the Linux
               | desktop/gaming ecosystem.
               | 
               | Now if your argument is we should work around buggy
               | applications and distribute hacked patches when the
               | developers have abandoned them for the sake of improving
               | user experience. I agree with that.
               | 
               | > "Syscalls should be fast unless there is a very good
               | reason not to be" strikes me as a wise operating
               | principle, which weights usability and usefulness of the
               | operating system alongside being technically correct.
               | 
               | Linux already operates by this principle. We are
               | examining a situation where best effort was not good
               | enough to hide poor application design.
        
           | touisteur wrote:
           | Or, io_uring the thing. One could probably wrap close() with
           | LD_PRELOAD and not touch the binary...
        
             | CyberRabbi wrote:
             | While tempting, you can't generally fix this by simply
             | patching close() with some function that converts it to an
             | unchecked asynchronous operation. If that were the case,
             | you could just do that in the kernel. Close() is expected
             | to complete synchronously. This matters because posix
             | guarantees that open()/pipe() etc. will return the lowest
             | file descriptor not in use[1]. I.e. this should work:
             | close(0);         fd = open("/foo/bar", ...);         // fd
             | is guaranteed to be 0
             | 
             | If you made close() just dispatch an asynchronous operation
             | and not wait on the result, then the code above would
             | break. Any code that uses dup() likely has code that
             | expects close() to behave that way.
             | 
             | The other issue is that close() can return errors. Most
             | applications ignore close errors but to be a robust
             | solution you'd need to ensure the target application
             | ignores those errors as well.
             | 
             | [1]: https://pubs.opengroup.org/onlinepubs/9699919799/funct
             | ions/V...
        
               | [deleted]
        
               | treis wrote:
               | >This matters because posix guarantees that open()/pipe()
               | etc. will return the lowest file descriptor not in
               | use[1]. I.e. this should work: close(0); fd =
               | open("/foo/bar", ...); // fd is guaranteed to be 0
               | 
               | On a multi threaded system that isn't guaranteed is it?
               | Meaning, another thread could call open in-between your
               | close & open.
        
               | loeg wrote:
               | What you're getting at is that an individual thread
               | cannot really use this property without some form of
               | synchronization with other threads in the process. Eg, to
               | use this property, other threads either do not allocate
               | fds, or you take some central lock around all fd
               | allocations. Most well-written programs do not rely on
               | it.
        
               | CyberRabbi wrote:
               | It is guaranteed whether multi-threaded or not. It's a
               | process level guarantee. If your application is designed
               | such that you don't know what your other threads are
               | doing then POSIX cannot help you.
        
         | satnome wrote:
         | Just curious, how did you nail it down to that specific issue
         | and patch? That seems like a great skill to have.
        
         | tankenmate wrote:
         | I just did a quick check the posted fix is not in the most
         | recent -rc branch in the public git repo.
        
           | londons_explore wrote:
           | This is the issue with using mailing lists... Large numbers
           | of perfectly good fixes, embodying many hours of effort, just
           | get missed and forgotten about.
           | 
           | At least with GitHub PR's, every request either needs to be
           | merged or rejected.
        
             | Denvercoder9 wrote:
             | The modern trend is autoclosing PR's after they haven't had
             | any activity in X months, so there's not much difference
             | with mailing lists anymore...
        
             | loeg wrote:
             | I'm no fan of mailing lists, but GitHub PRs get ignored in
             | much the same way.
        
               | misnome wrote:
               | There's ignored, and there is "not aware that it's
               | unresolved". How do mailing list flows handle the "give
               | me a list of open patches"?
        
       | leonjza wrote:
       | I really enjoyed the debugging process here, and am glad to have
       | learnt about the -k flag which seems to only be available on
       | systems with strace version 5.5, at least for me.
       | 
       | As for the patch (and my love for all things Frida [1]), I think
       | a call to Intercerptor.replace() after locating the symbol with
       | Module.getExportByName() [2] would make for a simpler patch (at
       | the cost of installing Frida). For example:                 const
       | sym = Module.getExportByName("lime.ndll", "SDL_SemWait");
       | Interceptor.replace(sym, {         onEnter: function() {},
       | onLeave: function() {}       });
       | 
       | [1] https://frida.re/
       | 
       | [2] https://frida.re/docs/javascript-api/#module
        
       | Narretz wrote:
       | Why is the engine even checking input devices so often? Shouldn't
       | the input device be registered via settings and then assumed to
       | exist when the game runs? It seems wasteful to check all input
       | devices every few seconds.
        
         | pas wrote:
         | Exactly. It should enumerate them when the player opens
         | settings. Or at startup.
         | 
         | But even if it wants to do this, why is it doing it on the main
         | thread!? :(
        
           | flohofwoe wrote:
           | If I start the game without a gamepad attached to the
           | computer, and then attach the gamepad, I'd like to use the
           | gamepad without restarting the game. And one would expect
           | that polling the attached input devices should never take
           | hundreds or thousands of milliseconds, there must be
           | something seriously wrong in the Linux input device stack or
           | maybe in one of the input device drivers.
        
             | Vvector wrote:
             | Or maybe just poll for new input devices when the game is
             | paused, or before the game starts.
        
               | flohofwoe wrote:
               | Then you still have problems to handle like accidentally
               | disconnecting/reconnecting the gamepad when somebody
               | stumbles over the cable (for instance the game might want
               | to automatically pause if the gamepad suddenly
               | 'disappears' for any reason). Gamepads should be
               | automatically detected at any time in the game as they
               | are connected or disconnected. That's how it works on
               | game consoles, and PC games shouldn't behave any
               | different in that regard IMHO.
        
             | yjftsjthsd-h wrote:
             | The input stack is fine, this game disabled the things that
             | would let it work nicely (see upthread discussion of SDL
             | supporting udev and inotify)
        
         | smcameron wrote:
         | SDL should probably use inotify() on linux so the kernel can
         | let it know when /dev/input has changed rather than polling it.
        
           | nemetroid wrote:
           | SDL has three methods for detecting input devices [1]: udev,
           | inotify, and, as a fallback, enumerating /dev/input.
           | 
           | It seems like _Papers, Please_ uses a statically linked
           | version of SDL, without udev or inotify support compiled in.
           | 
           | 1: https://github.com/libsdl-
           | org/SDL/blob/d0de4c625ad26ef540166...
        
             | Sjonny wrote:
             | I've been wondering.. is it possible to write something to
             | override the statically linked functions? In this case,
             | most (if not all) functions have an SDL_ prefix. Would it
             | be possible to LD_PRELOAD a library that loads a shared
             | version of SDL and goes over all the function pointers to
             | move them point them to a new location? Is there a tool for
             | this?
        
               | ds- wrote:
               | It looks like SDL's public symbols are all global in
               | lime.ndll so LD_PRELOADing SDL should do what you want.
               | Of course it is possible that lime.ndll was built with
               | -fno-semantic-interposition or equivalent in which case
               | the functions might be called directly without going
               | through the dynamic linker or even (partially) inlined.
        
               | touisteur wrote:
               | Well if you know where to fork, you could use Intel Pin
               | and divert the CFG, favorite tool for binary 'patching'.
               | 
               | Edit: though here if it's a problem of file enumeration
               | and access, I'd probably just LD_PRELOAD something to
               | bypass libc file access functions and return the same
               | result than the first time, with no delay.
        
               | cesarb wrote:
               | > I've been wondering.. is it possible to write something
               | to override the statically linked functions?
               | 
               | SDL does have a built-in way to do that trick. A quick
               | web search tells me it's called SDL_DYNAMIC_API.
        
               | Sjonny wrote:
               | cool, I never knew! Somehow the game I thought it would
               | add a feature is still lacking it. For some reason rumble
               | on my xbox joystick with Enter the Gungeon never worked.
               | I thought it was because of an old SDL version, because
               | experimentation showed that. But by using the
               | SDL_DYNAMIC_API env and loading my system SDL the game
               | still not added rumble to my joystick. Ohwell.
        
               | seba_dos1 wrote:
               | SDL_DYNAMIC_API is a relatively recent addition (IIRC
               | 2014), so static SDL2 builds from before that won't work
               | this way.
        
               | bregma wrote:
               | Static linking means the features of the Linux dynamic
               | loader, like using the environment variable LD_PRELOAD to
               | pre-load a dynamic library, are not going to have any
               | effect.
        
           | jchw wrote:
           | Actually, I think the truly preferred path is to just monitor
           | for udev events, which SDL supports but is presumably not
           | enabled for Papers, Please for one reason or another.
        
         | yxhuvud wrote:
         | Yes, and even if it checks for new devices, it should only need
         | to check devices it hasn't already checked.
        
           | masklinn wrote:
           | Ah, but are /dev/input entries reusable?
           | 
           | Let's say you have /dev/input/event{0,10}, event5 is a USB
           | keyboard, you unplug it, I assume event5 goes away.
           | 
           | But then you plug in a controller, does this get mapped to
           | event11, or does event5 get reused? Is the behaviour reliable
           | in all versions of linux?
           | 
           | You might argue that metadata should do the trick, but in my
           | experience, on device files, anything beyond read/write is a
           | crapshoot, whether metadata makes any sense is basically a
           | roll of the dice.
           | 
           | So if you have to open device files in order to check their
           | identity, you might as well skip the identity bit and just
           | check if you're a gamepad.
           | 
           | edit: per charcircuit's comment below, it looks like the
           | metadata of /dev/input at least are considered reliable, and
           | this was used to mitigate the issue by checking the mtime of
           | /dev/input itself against a stored timestamp:
           | https://github.com/spurious/SDL-
           | mirror/commit/59728f9802c786...
        
             | 10000truths wrote:
             | Isn't this the whole point of using file descriptors? As
             | long as you have an open file descriptor, the kernel
             | resource it references should remain stable. And if the
             | resource is unexpectedly destroyed from under the process's
             | nose, the file descriptor should report an I/O error the
             | next time you try to read or write from it.
        
               | masklinn wrote:
               | > Isn't this the whole point of using file descriptors?
               | 
               | Opening the same file multiple times will yield different
               | fds, and the paths can be modified independently of the
               | fd.
               | 
               | The goal here is to find if:
               | 
               | 1. there are new input devices
               | 
               | 2. which are joysticks (a category which, for SDL,
               | includes gamepads, so basically "has the user plugged in
               | a new gamepad they might want to use for the game")
               | 
               | How would keeping a bunch of fds around help?
        
             | pdw wrote:
             | The actual SDL fix was even simpler, they now just check if
             | the mtime of the /dev/input directory changed:
             | https://github.com/spurious/SDL-
             | mirror/commit/59728f9802c786...
        
               | Sjonny wrote:
               | Upstream fixes are nice, but since the game statically
               | links SDL you can't put in a newer version of libSDL.so
               | in the game path and have it patched like that. Are there
               | other ways of patching statically linked binaries with
               | updated functions?
        
         | asdfasgasdgasdg wrote:
         | A lot of games will automatically switch between keyboard and
         | gamepad when a gamepad is connected. Perhaps this is some
         | automatic background function that SDL handles.
        
           | dkersten wrote:
           | SDL has an event for when a new gamepad is detected or
           | removed: http://wiki.libsdl.org/SDL_ControllerDeviceEvent
           | although I don't know what it does internally in order to
           | detect this (well, the article describes what it does).
           | 
           | But you can also manually enumerate devices as in the example
           | here: http://wiki.libsdl.org/SDL_GameControllerOpen
        
           | fleabitdev wrote:
           | Windows uses a similar polling-based approach, with a warning
           | that you shouldn't poll for new gamepads every frame for
           | performance reasons:
           | 
           | https://docs.microsoft.com/en-
           | us/windows/win32/xinput/gettin...
           | 
           | In general, "notifying the program when a new device has
           | become available" seems to be a surprisingly difficult
           | problem. I've encountered trouble with multiple device types
           | across multiple platforms.
        
             | Sharlin wrote:
             | There's a reason Plug'n'Play and USB were big deals back
             | then. The concept of plugging in a new peripheral and it
             | just working, without rebooting, was rather revolutionary.
             | Even though the former was more aptly called "Plug'n'Pray"
             | in the early years...
        
         | charcircuit wrote:
         | SDL wants to support hotplugging where you can plug a
         | controller in after you have already started the game.
        
           | vlovich123 wrote:
           | Operating systems let you know when a device change has
           | happened. You can even cache this where you pool the first
           | time for the initial set of data and then you just check when
           | a device is plugged in to update your knowledge of the state
           | of the world.
           | 
           | I would imagine that's what's done if you're running udev but
           | maybe SDL doesn't do that.
        
             | Sharlin wrote:
             | As has been noted in another subthread, SDL can do this but
             | apparently the particular statically linked version shipped
             | with Papers Please is compiled without udev or inotify
             | support and has to fallback to manual checking.
        
           | [deleted]
        
       | shhhum wrote:
       | Some time ago I've also stumbled upon this issue and found the
       | workaround that I've posted here:
       | https://www.gog.com/forum/papers_please/terrible_lags_on_lin...
       | 
       | Thanks for a more proper digging.
        
       | facorreia wrote:
       | This reminds me of the recent Linus Tech Tips series on gaming on
       | Linux[1]. Their conclusion is that although many games work out
       | of the box (although usually not at launch), Linux is not ready
       | for mainstream gamers. Not many people would have the expertise
       | or the interest to troubleshoot the problem as OP did.
       | 
       | [1] https://www.youtube.com/watch?v=Rlg4K16ujFw
        
         | spijdar wrote:
         | It definitely isn't. I've been a huge linux nerd since my
         | preteens in the late 2000s, I jumped on to squeeze more
         | performance out of the thoroughly mediocre hardware I had
         | access to. I wanted to program, and I found Visual Studio to be
         | incomprehensibly dense and confusing, while Linux tools were so
         | much simpler, with GCC, GEdit, makefiles and the like being
         | more to my liking. I fell deep into the rabbit hole, learned
         | emacs, then vim (it was more responsive on my intel atom-
         | powered netbook), became a "shell guru", eventually went to
         | college at 16 and started doing cybersecurity work/pentesting
         | professionally. I've even made a tiny contribution to the Linux
         | kernel, which I'm pretty proud of.
         | 
         | All this anecdata to say, I consider myself pretty okay at
         | using Linux, I "prefer" Linux, but I _don 't use Linux for
         | gaming_. Not unless it makes sense. I play Minecraft on Linux,
         | and FOSS games that were developed _on_ Linux. There 's a
         | POWER9 desktop on my desk that runs Linux, and all my
         | professional and hobby work goes there. I love it.
         | 
         | But any commercial games? They go on my old college-days Intel
         | desktop, running Win10. I can do the work to get games running
         | on Linux, but why bother? Like Linus says in that video, when I
         | have time to play video games, I really don't want to pull out
         | a debugger and strace and crap to do more $DAYJOB work.
         | 
         | Not to say I never do that for fun. I do. I've done some work
         | with https://github.com/ptitSeb/box86, and that involves a
         | similar process. But I just frankly don't find doing it to your
         | average Steam game to be very fun. Sometimes the muse strikes,
         | usually it doesn't.
         | 
         | And for your average Linux user, much less your average
         | _computer_ user overall, you can forget about it. IMO, unless
         | you have a strong ideological reason to only use FOSS OSes (and
         | all the power to you!), the reason you use Linux is because it
         | 's a vastly superior tool for certain problems.
         | 
         | Playing your average commercial game is not one of them.
        
           | CorrectHorseBat wrote:
           | It's not, but it really has come a far way and I'm extremely
           | impressed. I'm kind of the other way around, I've never been
           | more than a very casual gamer and I'm simply not interested
           | in keeping a separate Windows pc or dual boot install for
           | games. If I can't get it working on Linux I'm not bothering
           | with it. Right now I can play any game I want to play with
           | very minimal tinkering (that probably says more about me than
           | the state of Wine/Proton, but still).
        
       | papersplease wrote:
        
       | pjmlp wrote:
       | I guess that is the kind of challenge to have Windows games on
       | GNU/Linux, fix them instead of playing.
        
         | voiper1 wrote:
         | https://www.pcgamer.com/indie-dev-finds-that-linux-users-gen...
         | 
         | This dev found the linux users returned high quality bug
         | reports
         | 
         | >Only 3 of the roughly 400 bug reports submitted by Linux users
         | were platform specific, that is, would only happen on Linux.
         | 
         | While this post is a linux-specific bug, in general they can
         | end up identifying underlying bugs that affect all platforms.
        
         | the_af wrote:
         | Isn't this a native port though?
         | 
         | I remember playing Papers Please even before it had a native
         | port, and enjoying it with zero problems.
        
           | pjmlp wrote:
           | Apparently I got that wrong, point still stands though.
        
         | shmerl wrote:
         | It's not like Windows doesn't have its own issues to fix.
         | Except developers do it anyway, because of the market size.
         | Windows isn't perfect or better for gaming.
        
           | qayxc wrote:
           | > Windows isn't perfect or better for gaming.
           | 
           | This assessment depends entirely on the perspective.
           | 
           | From a developer's POV, Windows definitively is the better
           | platform, as it's very monolithic in that you can rely on the
           | presence and longevity of APIs. Depending on the dev's
           | influence on the market and the success of the game, you even
           | get free optimisation, support, and bug fixes from h/w
           | vendors in the form of game-specific driver patches.
           | 
           | From a gamer's POV, Windows has advantages as well, since
           | bugs are rarely OS-related and h/w vendors offer a lot more
           | features OOTB.
           | 
           | If you love tinkering with the OS and don't care if some
           | titles or features just won't work, Linux is a valid option
           | for gaming. Otherwise Windows _is_ objectively the better
           | option by default, since I can rely on the games working with
           | all available features (e.g. multiplayer).
        
       | papersplease wrote:
       | Gavin Newsom and London Breed are nazis.
       | 
       | The "papers please" crowd are all going to prison (hopefully much
       | much worse).
        
         | papersplease wrote:
         | Keep downvoting NAZIS!
         | 
         | You know you are EVIL. You all will pay dearly for your crimes.
        
       | Aissen wrote:
       | strace tip of the day: you don't need lsof, strace can keep track
       | of open fds quite well, just use the -y flag.
       | -y            --decode-fds            --decode-fds=path
       | Print paths associated with file descriptor arguments.
       | -yy            --decode-fds=all                   Print all
       | available information associated with file
       | descriptors: protocol-specific information associated with
       | socket file descriptors, block/character device number
       | associated with device file descriptors, and PIDs
       | associated with pidfd file descriptors.
        
       | shmerl wrote:
       | Interesting investigation. I don't remember having this problem
       | when playing the GOG version, but may be I didn't have a lot of
       | input devices?
        
       | ux wrote:
       | Great post, learned a bunch of things. Only one blog post, and no
       | RSS (yet?) unfortunately.
        
       | charcircuit wrote:
       | From the description of the problem (a freeze every 3 seconds) I
       | knew exactly what it was. You can fix it by simply upgrading SDL
       | as they fixed this bug 2 years ago.
       | 
       | https://github.com/spurious/SDL-mirror/commit/59728f9802c786...
        
         | Diggsey wrote:
         | Why is it even doing this on the main thread at all? The
         | obvious thing would be to have a background thread polling for
         | changes and then sending messages asynchronously to the main
         | thread if a change actually occurred...
        
           | ninepoints wrote:
           | Because architectural simplicity is valuable and some
           | operating systems deliver input events on a particular
           | thread.
        
           | flohofwoe wrote:
           | The problem probably only shows up on some machines and
           | hasn't been noticed during development and testing. And TBH,
           | polling what input devices are connected should never take
           | more than a few microseconds, no matter how much operating
           | system code sits between the hardware and game code.
        
           | arghwhat wrote:
           | It is appropriate to use your main thread for your OS
           | interaction - polling fds, talking to display server,
           | whatever I/O you need, etc. An open/close call should never
           | take this long, and you should never need to make a large
           | amount of them in sequence after startup.
           | 
           | What should not be on your main thread is any long blocking
           | compute, which is why rendering/game logic often goes to
           | another thread - although simple games could easily be
           | single-threaded.
        
             | dagmx wrote:
             | You shouldn't be doing IO on your game thread though. (Main
             | and game thread may differ)
        
             | lawl wrote:
             | > An open/close call should never take this long
             | 
             | > What should not be on your main thread is any long
             | blocking compute
             | 
             | Isn't that contradicting yourself? I'm pretty sure open()
             | can block.
        
           | charcircuit wrote:
           | The simple answer is that it wasn't needed for udev. It may
           | not have blown up on the dev's machine because their input
           | devices were different. It might be tested less than the udev
           | version. As the other commenter stated it's simpler to just
           | check every 3 seconds instead of adding threading.
        
         | salicideblock wrote:
         | From one of the printouts in the post, it seems that Papers
         | Please is using a bundled and statically linked SDL.
         | 
         | So it would be the game developer that would have to update the
         | version of the SDL library. The binary patching done seems like
         | a good-enough alternative in the meantime.
         | 
         | I have the feeling such bundling of dependencies is fairly
         | common when porting games for Linux.
        
           | charcircuit wrote:
           | At least for Steam you are recommended to link against
           | Valve's Steam Linux Runtime which is a set of dynamic
           | libraries including SDL.
        
           | viraptor wrote:
           | It's common in application of certain size and compatibility
           | expectations. Windows and Mac games will bundle their
           | dependencies as much as possible as well. Same for large
           | apps. Nobody wants to end to in a situation where their
           | relatively expensive purchase doesn't work because of the
           | version of local libs.
        
             | ironmagma wrote:
             | Does bundling dependencies imply static linking, though?
             | Why can't they just dynamically link to the bundled
             | dependency?
        
           | edflsafoiewq wrote:
           | SDL has its own "dynapi" layer, where you can override it
           | with your own copy of SDL even if it was statically linked:
           | https://github.com/libsdl-org/SDL/blob/main/docs/README-
           | dyna...
        
             | MayeulC wrote:
             | Just pray they didn't alter SDL like factorio does:
             | https://news.ycombinator.com/item?id=27246164
        
             | ck45 wrote:
             | Is the version statically linked recent enough to support
             | it? Also, can't decide if it's genius or insane, that extra
             | layer of dynamic linking...
        
               | charcircuit wrote:
               | According to [1] it was added (but not released) January
               | 8th, 2014. Papers Please came out on Linux February 12,
               | 2014 so I'd figure it's not in there unless the version
               | of SDL was updated in a later update.
               | 
               | [1] https://old.reddit.com/r/linux_gaming/comments/1upn39
               | /sdl2_a...
               | 
               | Edit: (what I believe to be) This freezing bug was only
               | added 3 years ago, so it might actually have it.
        
         | AshamedCaptain wrote:
         | Why is udev not used in this case?
        
           | charcircuit wrote:
           | Because this is the fallback when you compile without udev
           | support
        
             | AshamedCaptain wrote:
             | I can guess that, but I was wondering why ... Is there a
             | distro without udev? The steam/whatever sandbox does not
             | support udev?
        
               | Sjonny wrote:
               | It's not the system that is not supporting udev, it's the
               | choice of the game developers how they compiled SDL ..
               | without dependencies, and so without udev.
        
               | mschuster91 wrote:
               | That's standard for game devs in the Linux world. The
               | less dependencies you have to rely on the distribution
               | for, the better - Windows stuff is either already present
               | or shared OS-wide with binary backwards compatibility
               | (=DirectX), so you can get away with shipping stuff that
               | has a chance to run even 25 years in the future without
               | major modification.
        
         | iqanq wrote:
         | This is one of the reasons I use Gentoo in my desktops: you can
         | run a "stable" (as in old) system, but pull a more recent
         | version of a library or application if you need it. For
         | example, I remember having problems accessing files that I had
         | stored in my mobile phone. Solution: updating libmtp and libmtp
         | only. I suppose you can't do this in a distro such as Debian
         | without upgrading half the packages.
         | 
         | Are there more distros that allow you to do this?
        
           | teddyh wrote:
           | If you are advanced enough to run Gentoo, you should be able
           | to use Debian and force an install of (or re-compile
           | yourself) a new package of the newer version, working around
           | the fact that the official newer version would otherwise
           | require other new packages.
        
             | vlovich123 wrote:
             | You could. But the amount of time it takes is significantly
             | more than yay -S <package> or emerge <package> vs the hell
             | that this poses on Debian (not to mention the dependency
             | hell you can run into)
        
               | yjftsjthsd-h wrote:
               | I don't know the situation on Gentoo, but partial updates
               | are explicitly unsupported on Arch, not least because
               | they don't do stable ABIs; Debian should have a much
               | easier time upgrading just one package.
        
               | elevader wrote:
               | It's not necessarily recommended to mix stable and
               | testing but it mostly works fine in my experience. I'd
               | guess Gentoo gets around quite a few problems as
               | everything is compiled from source. So updating a single
               | libary would cause a rebuild of everything that depends
               | on it.
               | 
               | Gentoo also has the concept of "Slots", so you could have
               | multiple versions of the same libary installed and
               | packages will choose their version to build against
               | accordingly.
        
               | vlovich123 wrote:
               | Having used Arch and Debian, I've definitely had an
               | easier time installing the latest version of arbitrary
               | packages on Arch. Something like SDL is part of base and
               | thus is already running latest.
        
       | ximeng wrote:
       | Fun write up. Here's another example of a binary patch to fix a
       | Linux game issue:
       | 
       | https://steamcommunity.com/app/333300/discussions/0/26463606...
        
       | CmdrKrool wrote:
       | This looks very like a problem I encountered some time ago
       | running the closed source 3DO emulator "Phoenix Project", and
       | similarly the open source "FreeDO" project that it was forked
       | from. I narrowed it down (also using strace, IIRC) to these
       | programs repeatedly opening and closing the /dev/input/event*
       | files, and that being weirdly slow. I made a seperate little test
       | program just to open and close those files to confirm it. It was
       | only slow on my main desktop machine; while on my lesser-powered
       | laptop, running a practically identical Arch Linux setup, those
       | file operations were quick and the programs ran fine. None of
       | these programs use SDL. I couldn't/didn't progress any further
       | then, but it's good to find some pointers here for further
       | investigation (ie. the libinput issue).
       | 
       | Funnily enough I'm pretty sure I played Papers Please on this
       | machine at length without problems but I think that was probably
       | the Windows version through Wine.
        
       | salted-fry wrote:
       | Hey, I know this issue! I ran into it in CK3 when it launched.
       | You can also work around it by running chmod go-rx /dev/input/
       | while playing your game. Whether this is more or less invasive
       | than binary-patching the game is up for debate.
        
       | reallifez4 wrote:
        
         | reallifez4 wrote:
        
       | Traubenfuchs wrote:
       | Why would you not let another thread do this nasty kind of
       | polling and let the main loop check for a changed result, if at
       | all?
        
         | loeg wrote:
         | Close, on a synthetic fd with no I/O performed, should not take
         | 100ms per call. This is a Linux performance bug.
        
           | mgaunard wrote:
           | close can take arbitrarily long, it's a blocking operation.
           | 
           | Don't ever call close on the hot path.
        
       | kaszanka wrote:
       | Wait, why is `close` in libpthread.so?
        
         | cesarb wrote:
         | > why is `close` in libpthread.so?
         | 
         | That's because close() is a pthreads "cancellation point" (see
         | https://man7.org/linux/man-pages/man7/pthreads.7.html for
         | details), so it needs special handling when the process is
         | using pthreads. If the process does not link to libpthread.so,
         | the implementation in libc.so (which probably doesn't have
         | cancellation point support) will be used.
        
         | jchw wrote:
         | I believe that a few libc functions are reimplemented in
         | libpthread, the idea being that if you _don't_ link to
         | pthreads, you don't need the overhead (locking, etc.) that is
         | needed in multithreaded situations. Feels a bit antiquated
         | now...
         | 
         | As for why close specifically though, that's a good question. I
         | wonder if it has something to do with special libc treatment of
         | the standard fds or anything like that.
        
           | loeg wrote:
           | As you can see in the disassembly, it has to do with
           | implementing async cancellation. I think they wrap many
           | blocking syscalls in the same way.
           | https://man7.org/linux/man-pages/man3/pthread_cancel.3.html
        
       ___________________________________________________________________
       (page generated 2022-01-02 23:00 UTC)