[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)