dockerfile/examples/openssl/openssl-3.2.1-src/doc/designs/ddd/WINDOWS.md

81 lines
3.9 KiB
Markdown

Windows-related issues
======================
Supporting Windows introduces some complications due to some "fun" peculiarities
of Windows's socket API.
In general, Windows does not provide a poll(2) call. WSAPoll(2) was introduced
in Vista and supposed to bring this functionality, but it had a bug in it which
Microsoft refused to fix, making it rather pointless. However Microsoft has now
finally fixed this bug in a build of Windows 10. So WSAPoll(2) is a viable
method, but only on fairly new versions of Windows.
Traditionally, polling has been done on windows using select(). However, this
call works a little differently than on POSIX platforms. Whereas on POSIX
platforms select() accepts a bitmask of FDs, on Windows select() accepts a
structure which embeds a fixed-length array of socket handles. This is necessary
because sockets are NT kernel handles on Windows and thus are not allocated
contiguously like FDs. As such, Windows select() is actually very similar to
POSIX poll(), making select() a viable option for polling on Windows.
Neither select() nor poll() are, of course, high performance polling options.
Windows does not provide anything like epoll or kqueue. For high performance
network I/O, you are expected to use a Windows API called I/O Completion Ports
(IOCP).
Supporting these is a pain for applications designed around polling. The reason
is that IOCPs are a higher-level interface; it is easy to build an IOCP-like
interface on top of polling, but it is not really possible to build a
polling-like interface on top of IOCPs.
For this reason it's actually common for asynchronous I/O libraries to basically
contain two separate implementations of their APIs internally, or at least a
substantial chunk of their code (e.g. libuv, nanomsg). It turns out to be easier
just to write a poll-based implementation of an I/O reactor and an IOCP-based
implementation than try to overcome the impedance discontinuities.
The difference between polling and IOCPs is that polling reports *readiness*
whereas IOCPs report *completion of an operation*. For example, in the IOCP
model, you make a read or write on a socket and an event is posted to the IOCP
when the read or write is complete. This is a fundamentally different model and
actually more similar to a high-level asynchronous I/O library such as libuv or
so on.
Evaluation of the existing demos and their applicability to Windows IOCP:
- ddd-01-conn-blocking: Blocking example, use of IOCP is not applicable.
- ddd-02-conn-nonblocking: Socket is managed by OpenSSL, and IOCP is not
supported.
- ddd-03-fd-blocking: Blocking example, use of IOCP is not applicable.
- ddd-04-fd-nonblocking: libssl is passed an FD with BIO_set_fd.
BIO_s_sock doesn't appear to support overlapped (that is, IOCP-based) I/O
as this requires use of special WSASend() and WSARecv() functions, rather
than standard send()/recv().
Since libssl already doesn't support IOCP for use of BIO_s_sock,
we might say here that any existing application using BIO_s_sock
obviously isn't trying to use IOCP, and therefore we don't need to
worry about the adapability of this example to IOCP.
- ddd-05-mem-nonblocking: Since the application is in full control of passing
data from the memory BIO to the network, or vice versa, the application
can use IOCP if it wishes.
This is demonstrated in the following demo:
- ddd-06-mem-uv: This demo uses a memory BIO and libuv. Since libuv supports
IOCP, it proves that a memory BIO can be used to support IOCP-based usage.
Further, a cursory examination of code on GitHub seems to suggest that when
people do use IOCP with libssl, they do it using memory BIOs passed to libssl.
So ddd-05 and ddd-06 essentially demonstrate this use case, especially ddd-06 as
it uses IOCP internally on Windows.
My conclusion here is that since libssl does not support IOCP in the first
place, we don't need to be particularly worried about this. But in the worst
case there are always workable solutions, as in demos 5 and 6.