[HN Gopher] The missing cross-platform OS API for timers
___________________________________________________________________
The missing cross-platform OS API for timers
Author : broken_broken_
Score : 43 points
Date : 2025-02-03 06:07 UTC (3 days ago)
(HTM) web link (gaultier.github.io)
(TXT) w3m dump (gaultier.github.io)
| pjc50 wrote:
| Very minor point: Windows applications only come with a message
| queue if you explicitly start one, so if you have a headless
| application it needs to have an invisible "window". However I
| agree with the author that it _is_ a pretty nice API in general.
| There are some problems caused by the very limited parameter
| size, but it 's a standard notification API for asynchronous OS
| events. We use it headlessly for detecting USB insertions.
| 01HNNWZ0MV43FF wrote:
| And doesn't Windows have that weird distinction that every exe
| is either a console application or a GUI application but not
| both? Makes it hard to get stderr out of GUIs
| tom_ wrote:
| Every exe is sort-of both - a GUI program can allocate a
| console window using AllocConsole
| (https://learn.microsoft.com/en-
| us/windows/console/allocconso...), and a console program can
| create a GUI window and open a message loop. The main
| difference is that a console program is auto-attached to the
| console of the process that launched it, if any.
|
| I'm not certain what the rules are for what the C library
| does, but it seems to detect the no-console case on startup
| and routes stderr to NUL. So printf and friends still do
| nothing even after allocating a console. I think you can fix
| around this with something like this, having allocated a
| console:
| fclose(stdout);stdout=freopen("CON","w",stdout);
| fclose(stderr);stderr=freopen("CON","w",stderr);
|
| You can probably actually arrange for stdout and stderr to go
| to STD_OUTPUT_HANDLE and STD_ERROR_HANDLE, if the distinction
| would matter, but that'd be a bit more hassle (see
| https://learn.microsoft.com/en-us/cpp/c-runtime-
| library/refe...).
| pjc50 wrote:
| The canonical answer seems to be https://devblogs.microsoft
| .com/oldnewthing/20090101-00/?p=19... : there's a flag in
| the PE executable header. But as you say, the same APIs are
| available to both once you're running. So you can have a
| console application that decides it's going to open some
| windows.
| abnercoimbre wrote:
| This is information that I needed for my own work. Thank
| you for taking the time to share knowledge.
| o11c wrote:
| Isn't the main practical problem also that a console
| program will forcibly create a console if it doesn't
| already have one? This is what breaks the approach everyone
| else uses of "write to stderr under the assumption it will
| be visible only if the user was prepared for it, without
| causing clutter otherwise".
| DSMan195276 wrote:
| Yes, but after the program is started you can do anything.
| AFAIK the important part of the distinction is the behavior
| when starting the program - if you start a GUI program from
| the console (Ex. calc), cmd doesn't wait for it to exit. And
| if you start a console program from Explorer, a cmd window
| will pop-up (you can hide it, but it will still flicker
| briefly).
| hkwerf wrote:
| This article fails to separate the concerns one has with timers.
| There's three cases I see:
|
| The simple case is just putting a thread to sleep for a given
| time. It's somewhat odd that there's no portable API for that,
| granted. At least POSIX has sleep, nanosleep and clock_nanosleep
| (list edited, thanks oguz-ismail).
|
| The second case is that a thread wants to continue processing
| until a timer has passed. This has to happen cooperatively one
| way or another, so setting a timer is equivalent to regularly
| querying the system time and comparing to a limit. There is
| simply no need for a "timer" operating system facility. (Even if
| you were to plug POSIX timers in here, you'd in all likelihood
| still need to check some flag set in the signal handler in the
| processing thread. And then you'd still need to check the system
| time as signals can originate from anywhere, just as with
| sleep/etc. above.)
|
| In the third case, a thread waits for I/O. Then, of course, the
| call to that I/O facility defines how any timer is handled. As
| these facilities are not portable, timers aren't portable. The
| author refers to this realization as the need to implement
| "Userspace timers". These are just an artifact of the I/O
| facility and calls querying the system time, though.
|
| So, ultimately, not having portable timers is the least of our
| problems. So long as we don't unify select, poll, kqueue,
| io_uring and what not, we don't have a need for them. And, as the
| author realized, libraries like libuv, which do an acceptable job
| at unifying those facilities, tend to provide a portable timer
| API.
| Someone wrote:
| > so setting a timer is equivalent to regularly querying the
| system time and comparing to a limit
|
| Setting an OS timer would make the thread use 0% CPU.
| "Regularly querying the system time and comparing to a limit"
| would not.
|
| Also, a good OS API would allow callers to specify how
| important it is to be woken at that exact moment, and would use
| that information to coordinate wake-up times across processes,
| and thus maximize idle periods.
| hkwerf wrote:
| > Setting an OS timer would make the thread use 0% CPU.
| "Regularly querying the system time and comparing to a limit"
| would not.
|
| As mentioned, this is the first case and can be achieved by
| calls to sleep/Sleep/etc. You'd only regularly query if there
| is actual useful work to be done. That's the premise of the
| second case you quoted from.
| Joker_vD wrote:
| The article is, I reckon, about a rather common case when a
| thread that does multiplexed I/O also needs to perform some
| timer-related tasks as well. As the very first paragraph
| states, "A blocking sleep won't cut it!"
|
| Unless you do it in a separate thread and then signal other
| threads but that's insane.
| oguz-ismail wrote:
| >usleep
|
| Not since 2008. POSIX has sleep(), nanosleep() and
| clock_nanosleep() now
| hkwerf wrote:
| Thanks, I've edited the list. :)
| amelius wrote:
| Another consideration: often you want to wait for several types
| of object (timer, semaphore, file, etc.), so you need one
| interface where you can do wait([A, B, C, ...]) where A can be
| a timer and B a semaphore, C a file, etc. Basically, the OS
| should present a unified interface.
| chikere232 wrote:
| Often such apis (select/poll/epoll) have an option to wait
| with a timeout, so if you have a bunch of different timers,
| you can manage them yourself (e.g. on a heap) and set the
| timeout to the nearest timer
|
| Of course if you have epoll you probably have timerfd, wich
| does give you a unified interface, just not as portable
| robertlagrant wrote:
| Which concerns were not separated?
| russdill wrote:
| And there's the questions people usually don't think of.
|
| If you clock moves forward or backward, how would you like that
| the effect your sleep?
|
| If the system suspends during your sleep, do you want that to
| count towards the sleep? Should expiration wake the system?
| gpderetta wrote:
| I find that you already have an event loop built on top of
| poll/epoll/io_uring, maintaining a timer heap (or wheel) in
| userspace and setting the polling timeout to the next expiration
| is the easiest and more efficient solution. Use an eventfd to
| force an early wakeup if you modify timers concurrently with your
| waits.
|
| The most annoying thing is that epoll supports timeouts smaller
| than a millisecond only since 5.11.
|
| edit: that's literally what's described under "All OSes: timers
| fully implemented in userspace ", including the comment about
| epoll low resolution.
| devit wrote:
| The article's conclusion is completely wrong because it misses
| two crucial points:
|
| 1. On multicore machines you want to process timers in parallel
| on multiple cores. With userspace timers you either set the same
| timeouts on all threads and have unnecessary wakeups or
| distribute timers to cores ahead of time which leads to increased
| latency if a thread is stalled for any reason. I think this is
| unfixable without a dedicated timer API.
|
| 2. Good timer APIs let you set a time _interval_ for when the
| timer expires, which is essential so that the system can group
| timers and reduce wakeups (i.e. you process all timers where the
| lower bound has been reached before going to sleep, but don't
| wake up until the upper bound arrives). Most or all "wait with
| timeout" APIs only have a single timeout, although this could be
| fixed.
| gpderetta wrote:
| > On multicore machines you want to process timers in parallel
| on multiple cores.
|
| By experience I almost never want to run my timer callbacks on
| a random core and always want it on a specific core. With the
| typical one event loop per thread, you would register the timer
| with the thread you care about.
|
| This is going to be very application specific.
| Veliladon wrote:
| I miss having an NMI that you got at 50/60Hz come hell or high
| water.
| pjc50 wrote:
| .. or the MSDOS/PC timer interrupt at something like 15Hz?
|
| https://www.xtof.info/Timing-on-PC-familly-under-DOS.html (wow,
| this is more complicated than I remember)
| touisteur wrote:
| People sleep on /dev/rtc and PIE !
| o11c wrote:
| That's basically what `timer_settime` does if you have a
| realtime scheduler.
| mrguyorama wrote:
| This is more related to the fact that computing systems are
| usually preemptive multitasking systems that are expected to
| operate very predictably even with stuff happening in the
| "background".
|
| If you really want or need reliable timers or miss those days
| for any reason, check out a Real Time OS.
| thijsvandien wrote:
| Windows offers several kinds of timers. For example, there's also
| _CreateTimerQueueTimer_ , which (in contrast to _SetTimer_ ) does
| not require a message loop.
| tom_ wrote:
| For Windows, you can use CreateWaitableTimer to create a timer
| that doesn't need a window: https://learn.microsoft.com/en-
| us/windows/win32/api/synchapi...
|
| Wait for it to time out using one of the WaitFor family of
| functions.
| leeter wrote:
| Alternatively there is everybody's favorite
| CreateThreadpoolTimer https://learn.microsoft.com/en-
| us/windows/win32/api/threadpo... now that the threadpool is
| active on any Win32 process by default AFAIK. But for something
| you don't mind blocking the above is often easier to use.
| mrpippy wrote:
| macOS does support EVFILT_TIMER, unfortunately the man pages that
| Apple hosts are very out-of-date. A more recent page
| (https://www.manpagez.com/man/2/kqueue/osx-10.13.1.php) shows
| support, and it's been there since at least 10.9 (2013).
___________________________________________________________________
(page generated 2025-02-06 23:01 UTC)