5 ms·
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 asynch
by Diggsey 5y ago
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 5y agoBecause architectural simplicity is valuable and some operating systems deliver input events on a particular thread.
- arghwhat 5y agoIt 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.
- lawl 5y ago> 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.
- arghwhat 5y ago> Isn't that contradicting yourself? I'm pretty sure open() can block. No, blocking compute would be you doing something for a long period of time. "open/close can block" means very little. You only need a mitigation if it ends up blocking long enough to be a problem in reasonable setups. You do that have to care about what happens if someone runs the code on an intentionally terrible/horribly slow but technically in spec toy filesystem. No need to prematurely optimize for this scenario. And especially with devfs you cannot just listen to POSIX and must know what the kernel is providing you - knowing how long operations take on such fds is normal design input.
- cturner 5y ago"You only need a mitigation if it ends up blocking long enough to be a problem in reasonable setups." That criteria is established here - the OP is about an issue affecting paying end-users! Premature optimisation is not relevant - the software is failing. It is unsafe to make fair-weather assumptions about customer systems. Consider a common software failure: where the user is saving data to a SMB or NFS partition, and then there is a loss of connectivity to that file-server, and the developer has done whatever I/O they need on the main thread. This causes data loss. You /should/ be able to assume (1) reliably fast return from core async-coordination syscalls (e.g. select, poll), and (2) that you will not suffer process or thread starvation caused by someone else. Respecting those constraints, it is good practice to isolate sync calls to a non-main thread in order to catch when they are not returning. This robustly covers both common scenarios (like the missing-filesystem) and obscure scenarios like the one in blog post.
- arghwhat 5y ago> That criteria is established here - the OP is about an issue affecting paying end-users! Premature optimisation is not relevant - the software is failing. The software is not failing, it is experiencing performance degradation: a 500ms pause whenever excessive and entirely unnecessary work is done. The solution is to not do the work. Moving open/close to a different thread is premature optimization, as no necessary call has been profiled to cause issues on any known system. Performance 101, do not do things that you do not need done. Even if you want live hotplug and input reconfiguration during gameplay without touching any menus, you only open a device when it appears. > It is unsafe to make fair-weather assumptions about customer systems. It is more pointless to optimize for worst-case scenarios - experiencing performance degradation on a faulty system is fine. All applications have a minimum performance requirement to remain responsive, which is equivalent to always making a certain degree of "fair-weather assumptions". > Consider a common software failure: where the user is saving data to a SMB or NFS partition, and then there is a loss of connectivity to that file-server, and the developer has done whatever I/O they need on the main thread. This causes data loss. This is non-sequitur - doing something on the main thread does not cause data-loss. Losing connectivity causes data-loss. Heck, as main-thread I/O with an event loop implies non-blocking fds, you would not even be blocked by this unless you call fsync(2) to explicitly block until flush is complete, which a normal application does not need to do. The other (horrible) side-effects of network filesystems will cause problems for your application no matter how you interact with the fd. Furthermore, you cannot use device files without reasoning about their exact implementation. They are not basic files. > Respecting those constraints, it is good practice to isolate sync calls to a non-main thread in order to catch when they are not returning. That's a hack, and is not even a solution. What are you going to do when they don't return? Accumulate dead threads and inconsistent shared application state?
- dagmx 5y agoYou shouldn't be doing IO on your game thread though. (Main and game thread may differ)
- charcircuit 5y agoThe 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.
- flohofwoe 5y agoThe 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.