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.
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.
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
> 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.
> 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.
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.
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.
Whew, so we've got CreateWaitableTimer, CreateTimerQueueTimer from another comment, and now CreateThreadpoolTimer as well. But that's not all - we already had WM_TIMER from the article.
TFA says POSIX has one type of timer, and it sucks. Windows has 4 types... but how many of them suck? I've used CreateWaitableTimer, and it was alright. I do know WM_TIMER sucks, but I've never used CreateTimerQueueTimer or CreateThreadpoolTimer. So, worst case is a draw. I'd like to see Windows winning here though.
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.
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
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:
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.
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".
It's not a practical problem in practice. The approach everyone else uses is fine, but everybody else is not writing GUI programs for Windows. If you are writing GUI programs for Windows, you print debug output using OutputDebugString and/or to a probably-optional log file.
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).
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.
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).
Awesome, that’s a handy link. I made a PR to Nim’s standard libraries async to use that (or a similar flag) MacOS. It massively improved latency, great for UIs.
I created an issue for another async system showing them how to do it. They closed the issue saying “MacOS doesn’t support that”. I reckon people don’t know how to actually research these things if it’s not 1-click away.
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.
> 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.
Windows offers several kinds of timers. For example, there's also CreateTimerQueueTimer, which (in contrast to SetTimer) does not require a message loop.
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.
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.