cqueues icon indicating copy to clipboard operation
cqueues copied to clipboard

Windows support

Open daurnimator opened this issue 10 years ago • 15 comments

I've been mulling over how hard adding windows support to cqueues would be. Lower level windows programming is not something I'm familar with, so please correct any incorrect assumptions I've made.

I'd love to have cqueues be a library I can depend on everywhere: if I write a network client in cqueues, even windows developers should still be able to use it (though maybe not in a performant way, which is the approach many applications take, e.g. nginx).

Comments @wahern has made before:

The concept is not portable to Windows because Windows lacks an analog to the pollable event queues of modern Unix systems. Libraries which try to abstract the two approaches—event readiness signaling versus event completion—invariably produce a pallid least common denominator library suffering all the weaknesses and enjoying none of the strengths of each approach.

Microsoft Windows support is basically out of the question1, for far too many reasons to put here. Aside from the more technical reasons, Windows I/O and networking programming interfaces have a fundamentally different character than on Unix. Unix historically relies on readiness polling, while Windows uses event completion callbacks. There are strengths and weaknesses to each approach. Trying to paper over the chasm between the two approaches invariably results in a framework with the strengths of neither and the weaknesses of both. The purpose of cqueues is to leverage the strengths of polling as well as address the weaknesses.

1 I have been toying with the idea of using an fd set in-place of a pollable descriptor on Windows, and taking the union of all fd sets when polling.


Solution 1. Add :pollset

One possible route is catering to the lowest common denominator (which helps with other platforms, such as AIX) and use select(). This can be accomplished by iterating over the fileno LLRB and building a pollset; this could be done on demand in a :pollset() function, or inside of fileno_ctl.

Issues

select does not expose a file handle we can wait on itself, and makes things much harder to nest/stack, :pollsets will need to be propogated up to the parent controller.

On windows, select boils down to a WaitForMultipleObjectsEx call, this has a maximum of 64 handles; which could easily be exceeded by a regular application. To get around this, you can spawn extra threads that each wait for events, and wait for them, and now we've recreated stackable event waiting.


Solution 2. Use an IOCP and RegisterWaitForSingleObject

Issues

Windows potentially has more than just readable and writable events; you need to use WSACreateEvent + WSAEventSelect to manage a HANDLE that encapsulates the events being waited for.

It is not possible to specify different event objects for different network events. The following code will not work; the second call will cancel the effects of the first, and only the FD_WRITE network event will be associated with hEventObject2:

rc = WSAEventSelect(s, hEventObject1, FD_READ);
rc = WSAEventSelect(s, hEventObject2, FD_WRITE); //bad

daurnimator avatar Jan 03 '15 08:01 daurnimator

Solution 2.5:

@piscisaureus alerted me to the undocumented IOCTL_AFD_POLL fast polling interface. This is what select() on windows uses internally, it's not limited to 64 descriptors. libuv uses it. This is apparently incompatible with some LSPs, but they're deprecated, so we can probably just not support that case.

daurnimator avatar Mar 27 '15 00:03 daurnimator

On Thu, Mar 26, 2015 at 05:34:46PM -0700, daurnimator wrote:

Solution 2.5:

@piscisaureus alerted me to the undocumented IOCTL_AFD_POLL fast polling interface. This is what select() on windows uses internally, it's not limited to 64 descriptors. libuv uses it. This is apparently incompatible with some LSPs, but they're deprecated, so we can probably just not support that case.

Very cool. Looks like the work originated here

https://github.com/piscisaureus/epoll_windows

according to this thread

http://comments.gmane.org/gmane.comp.lang.javascript.nodejs.libuv/1049

wahern avatar Mar 27 '15 22:03 wahern

More information about what AFD is: http://mista.nu/blog/2012/02/17/cve-2012-0148-a-deep-dive-into-afd/

wahern avatar Mar 27 '15 23:03 wahern

I had a play around with AFD today, check out this example: https://gist.github.com/daurnimator/63d2970aedc952f0beb3

daurnimator avatar Mar 29 '15 13:03 daurnimator

One of the roadblocks I learnt about was that you can't have an IOCP listen for events on an IOCP. Or at least, I haven't figured it out yet.

  • You can't add an IOCP handle to another IOCP
  • You can't register waits on IOCP handles

Maybe we can have some arrangement where a worker thread polls GetQueuedCompletionStatus and posts across? I'm not sure...

daurnimator avatar Mar 29 '15 23:03 daurnimator

For the pollfd code style to work, it needs to be able to return any type of HANDLE, I imagine we have users yield HANDLEs as lightuserdata? Once you have a handle you may need to know what to do with it. To find out the type of a HANDLE, you need to use NtQueryObject() with PUBLIC_OBJECT_TYPE_INFORMATION, and dispatch based on TypeName.

daurnimator avatar May 26 '15 02:05 daurnimator

My current opinion has tended back to Solution 1. It seems that an array of HANDLEs will be needed no matter what: the windows primitive of WaitForMultipleObjectsEx and the AFD stuff I found both need it. If we implement :pollset now, we can then special case socket descriptors later to go via AFD.

daurnimator avatar Jun 16 '15 07:06 daurnimator

Solution 1. Add :pollset

I played around with adding a :pollset function; + a pair'd function :pollresult over on a branch: https://github.com/daurnimator/cqueues/tree/pollset

daurnimator avatar Jun 26 '15 06:06 daurnimator

@katlogic suggested using https://github.com/richardhundt/upoll But it looks like upoll doesn't implement "stacking"; however it might not be hard to add.

daurnimator avatar Mar 08 '16 02:03 daurnimator

On Mon, Mar 07, 2016 at 06:31:51PM -0800, daurnimator wrote:

@katlogic suggested using https://github.com/richardhundt/upoll But it looks like upoll doesn't implement "stacking"; however it might not be hard to add.

It's not doing anything that the existing code isn't already doing except for a dozen or so lines for select. It'd be easier to add select to the existing code than to add kqueue, ports, etc support to upoll.c. Likewise for adding select stacking.

I had an idea for supporting Windows overlapped I/O in a performant manner, but it first involves properly breaking out the buffering code.

But simply supporting select on Windows is probably the best interim measure. Slightly easier (stacking notwithstanding) would be just supporting WSAPoll, available since Windows 2008.

wahern avatar Mar 08 '16 02:03 wahern

In midipix there's an interesting approach: for sockets, use AFD; but for other handle types, do a 0 byte read/write using the NT API in a worker thread. These block until there is data available for read/write. At which point you can signal an event. This works out at scale because on NT, canceling io operations is cheap.

daurnimator avatar May 20 '16 04:05 daurnimator

On Thu, May 19, 2016 at 09:46:19PM -0700, daurnimator wrote:

In midipix there's an interesting approach: for sockets, use AFD; but for other handle types, do a 0 byte read/write using the NT API in a worker thread. These block until there is data available for read/write. At which point you can signal an event. This works out at scale because on NT, canceling io operations is cheap.

Can you post a link?

wahern avatar May 20 '16 18:05 wahern

I suggest use select on windows. Generally in windows are use for client. like https://github.com/daurnimator/lua-http , I want use http client in windows, But now can't.

Here has a epoll in windows by select.

https://github.com/dpull/skynet-mingw/blob/master/platform/epoll.c

hanxi avatar Aug 13 '19 22:08 hanxi

Somewhat tangential, but I was reading up on Linux's io_uring API and had the thought that adding support for it in cqueues might make it simpler to also support IOCP, given that there seem to be similarities.

I'm oversimplifying and understand that this would likely be a lot of work. I suggest this because cqueues and Lua-HTTP are my favorite libraries for their purpose and a lack of Windows is a frequent issue.

andrewstarks avatar Feb 18 '21 15:02 andrewstarks

Looking in the comments, I see that @wahern isn't huge fan of this, so maybe it's not a great fit. :)

andrewstarks avatar Feb 18 '21 16:02 andrewstarks