I miss so many things besides the UI. seamless integration of Cortana with in-car bluetooth to read incoming SMS, live tiles, fantastic cameras in Nokia devices.
In this specific type of Win32 API case, I can think of a way to make this safe.
It would involve looking at the function pointer in QueueUserAPC and making sure the function being called doesn't mess with the stack frame being executed on.
This function will run in the context of the called thread, in that thread's stack. NOT in the calling thread.
It's a weird execution mode where you're allowed to hijack a blocked thread and run some code in its context.
Don't know enough about Rust or the like to say if that's something that could be done in the language with attributes/annotations for a function, but it seems plausible.
Perhaps simpler would be to just not unwind C++ exceptions through non-C++ stack frames and abort instead. (You'd run into these crashes at development time, debugging them would be pretty obvious, and it'd never release like this.) This might not be viable on Windows, though, where there is a lot of both C++ and legacy C code.
Another possibility is to avoid it in the first place by not allowing C++ function pointers that are not marked noexcept to be passed to C functions. I filed bugs against both GCC and LLVM requesting warnings:
Doesn't seem all that useful unless C++ compilers will start warning about noexcept functions calling exception-throwing functions -- they don't today: https://godbolt.org/z/4qbcbxaET .
> Whenever an exception is thrown and the search for a handler ([except.handle]) encounters the outermost block of a function with a non-throwing exception specification, the function std :: terminate is invoked ([except.terminate])
Catching it at runtime somewhat defeats the benefit of your approach upthread:
> Another possibility is to avoid it in the first place by not allowing C++ function pointers that are not marked noexcept to be passed to C functions.
Nothing in C can prevent your function from being abnormally unwound through (whether it's via C++ exceptions or via C longjmp()). The only real fix is "don't use C++ exceptions unless you're 100% sure that the code in between is exception-safe (and don't use C longjmp() at all outside of controlled scenarios)".
A better fix is to avoid passing pointers to C++ functions that can throw exceptions to C functions. This theoretically can be enforced by the compiler by requiring the C++ function pointers be marked noexcept.
I filed bugs against both GCC and LLVM requesting warnings:
It's not an async completion. The call is synchronous.
Windows allows some synchronous calls to be interrupted by another thread to run an APC if the called thread is in an "alertable wait" state. The interrupted thread then returns to the blocking call, so the pointers in the call are expected to be valid.
Edit 2: I should clarify that the thread returns to the blocking call, which then exits with WAIT_IO_COMPLETION status. So you have to retry it again. but the stack context is expected to be safe.
APC is an "Asynchronous procedure call", which is asynchronous to the calling thread in that it may or may not get run.
Edit: May or may not run a future time.
There are very limited things you are supposed to do in an APC, but these are poorly documented and need one to think carefully about what is happening when a thread is executing in a stack frame and you interrupt it with this horrorshow.
Win32 API is a plethora of footguns. For the uninitiated it can be like playing Minesweeper with code. Or like that scene in Galaxy Quest where the hammers are coming at you at random times as you try to cross a hallway.
A lot of it was designed by people who, I think, would call one stupid for holding it wrong.
I suppose it's a relic of the late 80s and 90s when you crawled on broken glass because there was no other way to get to the other side.
You learn a lot of the underlying systems this way, but these days people need to get shit done and move on with their lives.
Us olds are left behind staring at nostalgically at our mangled feet while we yell at people to get off our lawns.
> There are very limited things you are supposed to do in an APC, but these are poorly documented and need one to think carefully about what is happening when a thread is executing in a stack frame and you interrupt it with this horrorshow.
One must not throw a C++ exception across stack frames that don't participate in C++ stack unwinding, whether it's a Win32 APC, another Win32 callback, a POSIX signal or `qsort` (for the people that believe qsort still has a place in this decade). How the Win32 API is designed is absolutely irrelevant for the bug in this code.
I haven't looked into io_uring except superficially, but Windows implemented Registered I/O in Windows 8 circa 2011. this is the basically the same programming paradigm used in io_uring, except it is sockets-only.
Talk here [1] speaks to the modern reality of 14 years ago :-).
Since kqueue seems very similar to IOCP in paradigm, I guess some of the overheads are similar and hence a ring-buffer-based I/O system would be more performant.
It's worth noting that NVME storage also seems to use a similar I/O pattern as RIO, so I assume we're "closer to the hardware" in this way.
Microsoft implemented I/O rings in an update to Windows 10 with some differences and it is largely a copy-and-paste of io_uring.
It's important to note that the NT kernel was built to leverage async I/O throughout. It was part of the original design documents and not an after-thought.
Winsock Registered I/O or RIO predates io_uring and you could make the argument that io_uring "is largely a copy-paste of RIO" if you wanted to be childish.
The truth is that they both use a obvious pattern of high-performance data transfer that's been around a long time. As I said, NVME devices have been doing that for a while and it's a common paradigm in any DMA-based transfer.
io_uring seems more expansive and hence useful compared to the limited scope of RIO or even the NtIoRing stuff.
> io_uring "is largely a copy-paste of RIO" if you wanted to be childish.
This was unnecessary. I'm not denigrating I/O Rings or io_uring. The NT kernel is more advanced than the Linux/BSD/macOS kernels in certain ways. There should be a back-and-forth copying of the good ideas/implementations.
best I've used since the OG Microsoft Natural kyboards
The Microsoft Natural keyboards were an ergonomic nightmare. The cheap, sliding post keys would have varying resistance depending on how close to the center your finger landed, and how vertically you applied pressure.
Also, the keys were arranged over a hump. The dished design of a Kinesis has much better ergonomics.
Microsoft had a poorly documented tool called tracewpp that did this. Blindingly fast logging with very little runtime overhead.
It was hard to figure out how to use it without documentation so it wasn’t very popular. No idea if they still ship it in the DDK.
It was a preprocessor that converted logging macros into string table references so there was no runtime formatting. You decoded the binary logs with another tool after the fact.
Vaguely remember some open source cross platform tool that did something similar but the name escapes me now.
I think the Windows event log works like this. Sadly it's very opaque and difficult to use for non admin apps (you need admin rights to install your logs for the first time. Afterwards you can run with less privileges.)
If you're thinking of ETW (event tracing for Windows) and not the actual Windows EventLog itself, then you're right. traceWPP used ETW under the hood to record logging as ETW events in a file.
The Windows Event Log also used (uses?) this idea of pre-defined messages and you just supplied an event ID and data to fill in the blanks in the message.
Originally there was only one system-wide application event log and you needed to be admin to install your message definitions but it all changed in Vista (IIRC). I'd lost interest by then so I don't know how it works now. I do know that the event log viewer is orders of magnitude slower than it was before the refit.
It’s not the same system. The message catalog based method requires entering your message strings in .mc files during compilation. It is an aid for localization and display is up to the application as to time and method of output.
ETW is for high speed general purpose logging of low-level events with multiple collection mechanisms including realtime capture.
> It was a preprocessor that converted logging macros into string table references so there was no runtime formatting. You decoded the binary logs with another tool after the fact.
Is be curious how that stacks up against something like zstd with a predefined dictionary.
reply