The missing cross-platform OS API for timers
gaultier.github.io
gaultier.github.io
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.
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.
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.
Unless you do it in a separate thread and then signal other threads but that's insane.
Not since 2008. POSIX has sleep(), nanosleep() and clock_nanosleep() now
Of course if you have epoll you probably have timerfd, wich does give you a unified interface, just not as portable
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?
Wait for it to time out using one of the WaitFor family of functions.
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.
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...).Meaning if a user is busy jerking off a mouse which is causing WM_MOUSEMOVE events, the WM_TIMER will never fire at the expected time.
There is a hack to peek the next event specifically looking for WM_TIMER, on every other event call, which would trigger it on the queue.
https://devblogs.microsoft.com/oldnewthing/20191108-00/?p=10...
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.
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.
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.
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.
https://www.xtof.info/Timing-on-PC-familly-under-DOS.html (wow, this is more complicated than I remember)
If you really want or need reliable timers or miss those days for any reason, check out a Real Time OS.