From c321b2a7d4d40f2b9591f990f0e02b1e4533d7fa Mon Sep 17 00:00:00 2001
From: Constantin Papizh <p.const@bk.ru>
Date: Thu, 28 Nov 2019 22:21:58 +0300
Subject: [PATCH] wepoll issues

---
 3rdparty/libmemcached/libmemcached/common.h   |    2 -
 CMakeLists.txt                                |   22 +-
 lib/[x86_64CLANG]/wepoll[x86_64CLANG].a       |  Bin 35328 -> 0 bytes
 libdap                                        |    2 +-
 libdap-chain                                  |    2 +-
 libdap-chain-cs-dag                           |    2 +-
 libdap-chain-cs-dag-poa                       |    2 +-
 libdap-chain-cs-dag-pos                       |    2 +-
 libdap-chain-gdb                              |    2 +-
 libdap-chain-global-db                        |    2 +-
 libdap-chain-mempool                          |    2 +-
 libdap-chain-net                              |    2 +-
 libdap-chain-net-srv                          |    2 +-
 libdap-chain-net-srv-app                      |    2 +-
 libdap-chain-net-srv-app-db                   |    2 +-
 libdap-chain-net-srv-datum                    |    2 +-
 libdap-chain-net-srv-datum-pool               |    2 +-
 libdap-chain-wallet                           |    2 +-
 libdap-client                                 |    2 +-
 libdap-server                                 |    2 +-
 libdap-server-core                            |    2 +-
 libdap-server-udp                             |    2 +-
 libdap-stream                                 |    2 +-
 libdap-stream-ch                              |    2 +-
 libdap-stream-ch-chain                        |    2 +-
 libdap-stream-ch-chain-net                    |    2 +-
 libdap-stream-ch-chain-net-srv                |    2 +-
 libdap-stream-ch-vpn                          |    2 +-
 sources/main.c                                |    3 -
 sources/main_node_cli_net.c                   |    3 -
 sources/main_node_cli_shell.c                 |    3 -
 sources/main_node_tool.c                      |    3 -
 sources/wepoll/LICENSE                        |   28 +
 sources/wepoll/README.md                      |  202 ++
 sources/wepoll/wepoll.c                       | 2189 +++++++++++++++++
 .../include => sources/wepoll}/wepoll.h       |   43 +-
 36 files changed, 2488 insertions(+), 60 deletions(-)
 delete mode 100644 lib/[x86_64CLANG]/wepoll[x86_64CLANG].a
 create mode 100644 sources/wepoll/LICENSE
 create mode 100644 sources/wepoll/README.md
 create mode 100644 sources/wepoll/wepoll.c
 rename {3rdparty/wepoll/include => sources/wepoll}/wepoll.h (52%)

diff --git a/3rdparty/libmemcached/libmemcached/common.h b/3rdparty/libmemcached/libmemcached/common.h
index 5761657c2..da828a6d0 100644
--- a/3rdparty/libmemcached/libmemcached/common.h
+++ b/3rdparty/libmemcached/libmemcached/common.h
@@ -43,8 +43,6 @@
 
 
 #ifdef WIN32
-#undef _WIN32_WINNT
-#define _WIN32_WINNT 0x0600
 
 #include <winsock2.h>
 #include <windows.h>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a182c8410..4e94d63c0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -80,6 +80,7 @@ if(WIN32)
   add_definitions("-DHAVE_PREAD")
   add_definitions("-DHAVE_MMAP")
   add_definitions("-DHAVE_STRNDUP")
+  add_definitions("-DWINVER=0x0600 -D_WIN32_WINNT=0x0600")
 
   if(DAP_RELEASE)
     set(_CCOPT "-static -std=gnu11 -Wall -O3 -fno-ident -ffast-math -ftree-vectorize -mfpmath=sse -mmmx -msse2 -fno-asynchronous-unwind-tables -ffunction-sections -Wl,--gc-sections -Wl,--strip-all")
@@ -94,12 +95,11 @@ if(WIN32)
   include_directories(libdap/src/win32/)
   include_directories(3rdparty/libmemcached/)
   include_directories(3rdparty/libmemcached/win32/)
-  include_directories(3rdparty/wepoll/include/)
   include_directories(3rdparty/uthash/src/)
   include_directories(3rdparty/libjson-c/)
   include_directories(3rdparty/curl/include/)
   include_directories(3rdparty/libsqlite3/)
-
+  include_directories(sources/wepoll/)
   include_directories(libdap-server-http-db-auth/)
   include_directories(libdap-chain-net-srv-vpn/)
 endif()
@@ -177,11 +177,9 @@ if(WIN32)
   add_executable(${PROJECT_NAME} "sources/main.c" "sources/exh_win32.c" "sources/sig_win32_handler.c")
   add_executable(${PROJECT_NAME}-cli "sources/main_node_cli.c" "sources/main_node_cli_shell.c" "sources/main_node_cli_net.c" )
   add_executable(${PROJECT_NAME}-tool "sources/main_node_tool.c" )
-
   target_link_libraries(${PROJECT_NAME}-cli dap_chain_net
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libjson-c[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libmemcached[x86_64CLANG].a
-    ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/wepoll[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libsqlite3[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libmongoc[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libbson[x86_64CLANG].a
@@ -210,9 +208,9 @@ if(WIN32)
     Bcrypt
     Crypt32
     Secur32
-	userenv
+        userenv
   )
-	set_property(TARGET ${PROJECT_NAME}-cli APPEND_STRING PROPERTY LINK_FLAGS "-mconsole")
+        set_property(TARGET ${PROJECT_NAME}-cli APPEND_STRING PROPERTY LINK_FLAGS "-mconsole")
   target_link_libraries(${PROJECT_NAME}-tool dap_core dap_crypto dap_server_core dap_enc_server dap_udp_server dap_session
     dap_enc_server dap_stream dap_stream_ch_vpn dap_stream_ch_chain dap_stream_ch_chain_net
     dap_stream_ch_chain_net_srv dap_chain dap_chain_crypto dap_client
@@ -223,7 +221,6 @@ if(WIN32)
 
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libjson-c[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libmemcached[x86_64CLANG].a
-    ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/wepoll[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libsqlite3[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libmongoc[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libbson[x86_64CLANG].a
@@ -252,9 +249,9 @@ if(WIN32)
     Bcrypt
     Crypt32
     Secur32
-	userenv
+        userenv
 )
-	set_property(TARGET ${PROJECT_NAME}-tool APPEND_STRING PROPERTY LINK_FLAGS "-mconsole")
+        set_property(TARGET ${PROJECT_NAME}-tool APPEND_STRING PROPERTY LINK_FLAGS "-mconsole")
 
   target_link_libraries(${PROJECT_NAME} dap_core dap_crypto dap_server_core dap_enc_server dap_udp_server dap_session
     dap_enc_server dap_stream dap_stream_ch_vpn dap_stream_ch_chain dap_stream_ch_chain_net
@@ -265,7 +262,6 @@ if(WIN32)
     dap_chain_wallet dap_chain_global_db dap_chain_mempool dap_chain_gdb
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libjson-c[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libmemcached[x86_64CLANG].a
-    ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/wepoll[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libsqlite3[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libmongoc[x86_64CLANG].a
     ${CMAKE_CURRENT_SOURCE_DIR}/lib/[x86_64CLANG]/libbson[x86_64CLANG].a
@@ -294,9 +290,9 @@ if(WIN32)
     Bcrypt
     Crypt32
     Secur32
-	userenv
+        userenv
 )
-	set_property(TARGET ${PROJECT_NAME} APPEND_STRING PROPERTY LINK_FLAGS "-mwindows")
+        set_property(TARGET ${PROJECT_NAME} APPEND_STRING PROPERTY LINK_FLAGS "-mwindows")
 
 #dap_chain_net_srv_vpn dap_server_http_db_auth
 
@@ -315,7 +311,7 @@ target_link_libraries(${PROJECT_NAME} dap_core dap_crypto dap_crypto dap_server_
     dap_chain_net_srv_datum dap_chain_net_srv_datum_pool dap_chain_net_srv_vpn
     dap_chain_wallet dap_chain_global_db dap_chain_mempool dap_chain_gdb m pthread magic dap_server_http_db dap_server_http_db_auth)
 
-target_link_libraries(${PROJECT_NAME}-cli m dap_chain_net curl) 
+target_link_libraries(${PROJECT_NAME}-cli m dap_chain_net curl)
 
 target_link_libraries(${PROJECT_NAME}-tool dap_core dap_crypto dap_server_core dap_enc_server  dap_udp_server dap_session
     dap_enc_server dap_stream dap_stream_ch_vpn dap_stream_ch_chain dap_stream_ch_chain_net
diff --git a/lib/[x86_64CLANG]/wepoll[x86_64CLANG].a b/lib/[x86_64CLANG]/wepoll[x86_64CLANG].a
deleted file mode 100644
index ccfe34c9e9e4664ffabcbd9832ff991afcf5e22f..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 35328
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
DZ)z8d

diff --git a/libdap b/libdap
index 1c0614797..9053a0597 160000
--- a/libdap
+++ b/libdap
@@ -1 +1 @@
-Subproject commit 1c0614797a3a2a2f4c179630025600e177637a65
+Subproject commit 9053a0597613f8dc685bfd756b058494b8aaef14
diff --git a/libdap-chain b/libdap-chain
index 04be73e68..65f715269 160000
--- a/libdap-chain
+++ b/libdap-chain
@@ -1 +1 @@
-Subproject commit 04be73e68e1994e86a71154370616a3a3b2b85ae
+Subproject commit 65f715269361e3d9ba317537e655961f31bf2247
diff --git a/libdap-chain-cs-dag b/libdap-chain-cs-dag
index 447d6db4e..4d83c5afc 160000
--- a/libdap-chain-cs-dag
+++ b/libdap-chain-cs-dag
@@ -1 +1 @@
-Subproject commit 447d6db4e8587d6118e968eb1109fbf2a3356247
+Subproject commit 4d83c5afc1e0f71c24b48603557372f8bb20f021
diff --git a/libdap-chain-cs-dag-poa b/libdap-chain-cs-dag-poa
index db06bbd6a..757f58797 160000
--- a/libdap-chain-cs-dag-poa
+++ b/libdap-chain-cs-dag-poa
@@ -1 +1 @@
-Subproject commit db06bbd6a784ff2496f2d1bb9cbb54924efe83b6
+Subproject commit 757f58797844421ce4b3b369e11dff9065acbfb6
diff --git a/libdap-chain-cs-dag-pos b/libdap-chain-cs-dag-pos
index b1a0d674d..52bf11864 160000
--- a/libdap-chain-cs-dag-pos
+++ b/libdap-chain-cs-dag-pos
@@ -1 +1 @@
-Subproject commit b1a0d674dbb56938e0ba58862ad1d638420e554f
+Subproject commit 52bf11864520d7090362713cadf87fe0d63bd8a3
diff --git a/libdap-chain-gdb b/libdap-chain-gdb
index 761812252..86d32bd6d 160000
--- a/libdap-chain-gdb
+++ b/libdap-chain-gdb
@@ -1 +1 @@
-Subproject commit 76181225215f91f3a2a42671f735f70ade46d045
+Subproject commit 86d32bd6dc2159e7fe3eb50996dd6b8c8f51be5d
diff --git a/libdap-chain-global-db b/libdap-chain-global-db
index 8c6bbf445..b7d2fb60f 160000
--- a/libdap-chain-global-db
+++ b/libdap-chain-global-db
@@ -1 +1 @@
-Subproject commit 8c6bbf4458bd10614bf48bb2da7e84b4f80f0d33
+Subproject commit b7d2fb60fe97c6f8e2a08cb41429d1f33a3bc353
diff --git a/libdap-chain-mempool b/libdap-chain-mempool
index 4bbdcf60e..74421dd77 160000
--- a/libdap-chain-mempool
+++ b/libdap-chain-mempool
@@ -1 +1 @@
-Subproject commit 4bbdcf60e10de8462820e45d7cc3715dca4c2139
+Subproject commit 74421dd778babd15c0e8e2c28f9b4e4c1d93d41e
diff --git a/libdap-chain-net b/libdap-chain-net
index 94ff5a32e..8de304672 160000
--- a/libdap-chain-net
+++ b/libdap-chain-net
@@ -1 +1 @@
-Subproject commit 94ff5a32e30465d3e5986a11753caf57f67e5096
+Subproject commit 8de30467285db82cdeef34e95eae60efecec95d3
diff --git a/libdap-chain-net-srv b/libdap-chain-net-srv
index 5edea70af..cafb5af49 160000
--- a/libdap-chain-net-srv
+++ b/libdap-chain-net-srv
@@ -1 +1 @@
-Subproject commit 5edea70af6a5ede0c759e9d83a5741376f4da9fb
+Subproject commit cafb5af4984e8ee347518c0718f5c68733b51f3a
diff --git a/libdap-chain-net-srv-app b/libdap-chain-net-srv-app
index e5f23784d..18e4d357d 160000
--- a/libdap-chain-net-srv-app
+++ b/libdap-chain-net-srv-app
@@ -1 +1 @@
-Subproject commit e5f23784de6b8cc16b80b4bc755c67df5fc7565f
+Subproject commit 18e4d357d6d984b96ea8c95582d206217ab3f032
diff --git a/libdap-chain-net-srv-app-db b/libdap-chain-net-srv-app-db
index ce7f0333b..9e115e17a 160000
--- a/libdap-chain-net-srv-app-db
+++ b/libdap-chain-net-srv-app-db
@@ -1 +1 @@
-Subproject commit ce7f0333b77f8290428949b593e0ca2bd3df01b3
+Subproject commit 9e115e17ab30a8b6df0507471e2c08a4dad8c184
diff --git a/libdap-chain-net-srv-datum b/libdap-chain-net-srv-datum
index 5186f865f..602391159 160000
--- a/libdap-chain-net-srv-datum
+++ b/libdap-chain-net-srv-datum
@@ -1 +1 @@
-Subproject commit 5186f865f3a1bd10ef61f3cb039d85c9b32c7ad7
+Subproject commit 602391159d47da34b08d8da743812a26a2efdd97
diff --git a/libdap-chain-net-srv-datum-pool b/libdap-chain-net-srv-datum-pool
index 0eb7c0802..883e227be 160000
--- a/libdap-chain-net-srv-datum-pool
+++ b/libdap-chain-net-srv-datum-pool
@@ -1 +1 @@
-Subproject commit 0eb7c08027ff1a643c90b5ca59a8e4267e71ca6f
+Subproject commit 883e227be78ca7f2aa33a9283b5122198875dad3
diff --git a/libdap-chain-wallet b/libdap-chain-wallet
index 5273ba5ba..3fcaedb1f 160000
--- a/libdap-chain-wallet
+++ b/libdap-chain-wallet
@@ -1 +1 @@
-Subproject commit 5273ba5ba0a15dd94e680f9d961ab150f3764848
+Subproject commit 3fcaedb1f33a3db1bde6050f824fdabf07e9ad34
diff --git a/libdap-client b/libdap-client
index d3f5458f3..b583f811f 160000
--- a/libdap-client
+++ b/libdap-client
@@ -1 +1 @@
-Subproject commit d3f5458f352294547b1ac68404d5ccdc8dea28ba
+Subproject commit b583f811fe3221c51f491809095340e0a6ff47af
diff --git a/libdap-server b/libdap-server
index e3f07ae78..d7401c149 160000
--- a/libdap-server
+++ b/libdap-server
@@ -1 +1 @@
-Subproject commit e3f07ae78aaecfada9f44abdd48a4ebbce9e2184
+Subproject commit d7401c1493dbbd86e843c639182469c09331d077
diff --git a/libdap-server-core b/libdap-server-core
index 73a86ef82..a50355cba 160000
--- a/libdap-server-core
+++ b/libdap-server-core
@@ -1 +1 @@
-Subproject commit 73a86ef8202ee67a05098d186aac2deacb207f63
+Subproject commit a50355cbaa236b4e02ebc64e8db5d53125c8527d
diff --git a/libdap-server-udp b/libdap-server-udp
index a14daa6c3..8e0641bce 160000
--- a/libdap-server-udp
+++ b/libdap-server-udp
@@ -1 +1 @@
-Subproject commit a14daa6c39aed9bba32684872d03afc7778db61e
+Subproject commit 8e0641bceb42e785dae16040ef3458a178e1b9f5
diff --git a/libdap-stream b/libdap-stream
index eb3f6b659..af3ea5274 160000
--- a/libdap-stream
+++ b/libdap-stream
@@ -1 +1 @@
-Subproject commit eb3f6b659c2ad25e1b9906eb9820e46de7443103
+Subproject commit af3ea52745feb78f9ca9f23125dd42dbbd144731
diff --git a/libdap-stream-ch b/libdap-stream-ch
index daeb35e46..c90d1ec2c 160000
--- a/libdap-stream-ch
+++ b/libdap-stream-ch
@@ -1 +1 @@
-Subproject commit daeb35e4680f659d41ade4da7514138e56278685
+Subproject commit c90d1ec2c98a7062d5a3afdf71df873e829f72c4
diff --git a/libdap-stream-ch-chain b/libdap-stream-ch-chain
index 7dcfe38b6..f5fe9e183 160000
--- a/libdap-stream-ch-chain
+++ b/libdap-stream-ch-chain
@@ -1 +1 @@
-Subproject commit 7dcfe38b65abc0b04d4b2fc2292e22153322c7d6
+Subproject commit f5fe9e183e788af329d863645986d1c1cf17a65a
diff --git a/libdap-stream-ch-chain-net b/libdap-stream-ch-chain-net
index 6875d05d0..cd646f000 160000
--- a/libdap-stream-ch-chain-net
+++ b/libdap-stream-ch-chain-net
@@ -1 +1 @@
-Subproject commit 6875d05d0d3c3da2c9562b4840f48a5267e865ca
+Subproject commit cd646f0008051a5bc9a9c32f4ba6641f9ed4c444
diff --git a/libdap-stream-ch-chain-net-srv b/libdap-stream-ch-chain-net-srv
index e136afe77..9f7ed19b5 160000
--- a/libdap-stream-ch-chain-net-srv
+++ b/libdap-stream-ch-chain-net-srv
@@ -1 +1 @@
-Subproject commit e136afe77716b6e327310ccaa7f58ad75c63b2c5
+Subproject commit 9f7ed19b5956822f2587dba7f09886c48e139a8e
diff --git a/libdap-stream-ch-vpn b/libdap-stream-ch-vpn
index 29383e0b4..02652f21a 160000
--- a/libdap-stream-ch-vpn
+++ b/libdap-stream-ch-vpn
@@ -1 +1 @@
-Subproject commit 29383e0b47628acf0dc085f69a87a449dd03acb5
+Subproject commit 02652f21a7d7b741193610487d0de05dd2d51f57
diff --git a/sources/main.c b/sources/main.c
index 026fe91f7..f42202f88 100644
--- a/sources/main.c
+++ b/sources/main.c
@@ -34,15 +34,12 @@
 #include <signal.h>
 
 #ifdef _WIN32
-#undef _WIN32_WINNT
-#define _WIN32_WINNT 0x0600
 #include <winsock2.h>
 #include <windows.h>
 #include <mswsock.h>
 #include <ws2tcpip.h>
 #include <io.h>
 //#include "wrappers.h"
-#include <wepoll.h>
 #include <pthread.h>
 #include "userenv.h"
 
diff --git a/sources/main_node_cli_net.c b/sources/main_node_cli_net.c
index 022d5baa6..35c81bbab 100644
--- a/sources/main_node_cli_net.c
+++ b/sources/main_node_cli_net.c
@@ -39,15 +39,12 @@
 #include <assert.h>
 
 #ifdef _WIN32
-#undef _WIN32_WINNT
-#define _WIN32_WINNT 0x0600
 #include <winsock2.h>
 #include <windows.h>
 #include <mswsock.h>
 #include <ws2tcpip.h>
 #include <io.h>
 //#include "wrappers.h"
-#include <wepoll.h>
 #include <pthread.h>
 #else
 #include <sys/socket.h>
diff --git a/sources/main_node_cli_shell.c b/sources/main_node_cli_shell.c
index 9f8397c70..90e45d13a 100644
--- a/sources/main_node_cli_shell.c
+++ b/sources/main_node_cli_shell.c
@@ -15,15 +15,12 @@
 #include <locale.h>
 
 #ifdef _WIN32
-#undef _WIN32_WINNT
-#define _WIN32_WINNT 0x0600
 #include <winsock2.h>
 #include <windows.h>
 #include <mswsock.h>
 #include <ws2tcpip.h>
 #include <io.h>
 //#include "wrappers.h"
-#include <wepoll.h>
 #include <pthread.h>
 #else
 #include <sys/ttydefaults.h>
diff --git a/sources/main_node_tool.c b/sources/main_node_tool.c
index 9fcab399d..53a7e1662 100644
--- a/sources/main_node_tool.c
+++ b/sources/main_node_tool.c
@@ -29,15 +29,12 @@
 #include <string.h>
 
 #ifdef _WIN32
-#undef _WIN32_WINNT
-#define _WIN32_WINNT 0x0600
 #include <winsock2.h>
 #include <windows.h>
 #include <mswsock.h>
 #include <ws2tcpip.h>
 #include <io.h>
 //#include "wrappers.h"
-#include <wepoll.h>
 #endif
 
 #include <pthread.h>
diff --git a/sources/wepoll/LICENSE b/sources/wepoll/LICENSE
new file mode 100644
index 000000000..6c8b1c842
--- /dev/null
+++ b/sources/wepoll/LICENSE
@@ -0,0 +1,28 @@
+wepoll - epoll for Windows
+https://github.com/piscisaureus/wepoll
+
+Copyright 2012-2019, Bert Belder <bertbelder@gmail.com>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+  * Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+
+  * Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the
+    documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/sources/wepoll/README.md b/sources/wepoll/README.md
new file mode 100644
index 000000000..d334d0833
--- /dev/null
+++ b/sources/wepoll/README.md
@@ -0,0 +1,202 @@
+# wepoll - epoll for windows
+
+[![][ci status badge]][ci status link]
+
+This library implements the [epoll][man epoll] API for Windows
+applications. It is fast and scalable, and it closely resembles the API
+and behavior of Linux' epoll.
+
+## Rationale
+
+Unlike Linux, OS X, and many other operating systems, Windows doesn't
+have a good API for receiving socket state notifications. It only
+supports the `select` and `WSAPoll` APIs, but they
+[don't scale][select scale] and suffer from
+[other issues][wsapoll broken].
+
+Using I/O completion ports isn't always practical when software is
+designed to be cross-platform. Wepoll offers an alternative that is
+much closer to a drop-in replacement for software that was designed
+to run on Linux.
+
+## Features
+
+* Can poll 100000s of sockets efficiently.
+* Fully thread-safe.
+* Multiple threads can poll the same epoll port.
+* Sockets can be added to multiple epoll sets.
+* All epoll events (`EPOLLIN`, `EPOLLOUT`, `EPOLLPRI`, `EPOLLRDHUP`)
+  are supported.
+* Level-triggered and one-shot (`EPOLLONESTHOT`) modes are supported
+* Trivial to embed: you need [only two files][dist].
+
+## Limitations
+
+* Only works with sockets.
+* Edge-triggered (`EPOLLET`) mode isn't supported.
+
+## How to use
+
+The library is [distributed][dist] as a single source file
+([wepoll.c][wepoll.c]) and a single header file ([wepoll.h][wepoll.h]).<br>
+Compile the .c file as part of your project, and include the header wherever
+needed.
+
+## Compatibility
+
+* Requires Windows Vista or higher.
+* Can be compiled with recent versions of MSVC, Clang, and GCC.
+
+## API
+
+### General remarks
+
+* The epoll port is a `HANDLE`, not a file descriptor.
+* All functions set both `errno` and `GetLastError()` on failure.
+* For more extensive documentation, see the [epoll(7) man page][man epoll],
+  and the per-function man pages that are linked below.
+
+### epoll_create/epoll_create1
+
+```c
+HANDLE epoll_create(int size);
+HANDLE epoll_create1(int flags);
+```
+
+* Create a new epoll instance (port).
+* `size` is ignored but most be greater than zero.
+* `flags` must be zero as there are no supported flags.
+* Returns `NULL` on failure.
+* [Linux man page][man epoll_create]
+
+### epoll_close
+
+```c
+int epoll_close(HANDLE ephnd);
+```
+
+* Close an epoll port.
+* Do not attempt to close the epoll port with `close()`,
+  `CloseHandle()` or `closesocket()`.
+
+### epoll_ctl
+
+```c
+int epoll_ctl(HANDLE ephnd,
+              int op,
+              SOCKET sock,
+              struct epoll_event* event);
+```
+
+* Control which socket events are monitored by an epoll port.
+* `ephnd` must be a HANDLE created by
+  [`epoll_create()`](#epoll_createepoll_create1) or
+  [`epoll_create1()`](#epoll_createepoll_create1).
+* `op` must be one of `EPOLL_CTL_ADD`, `EPOLL_CTL_MOD`, `EPOLL_CTL_DEL`.
+* `sock` must be a valid socket created by [`socket()`][msdn socket],
+  [`WSASocket()`][msdn wsasocket], or [`accept()`][msdn accept].
+* `event` should be a pointer to a [`struct epoll_event`](#struct-epoll_event).<br>
+  If `op` is `EPOLL_CTL_DEL` then the `event` parameter is ignored, and it
+  may be `NULL`.
+* Returns 0 on success, -1 on failure.
+* It is recommended to always explicitly remove a socket from its epoll
+  set using `EPOLL_CTL_DEL` *before* closing it.<br>
+  As on Linux, closed sockets are automatically removed from the epoll set, but
+  wepoll may not be able to detect that a socket was closed until the next call
+  to [`epoll_wait()`](#epoll_wait).
+* [Linux man page][man epoll_ctl]
+
+### epoll_wait
+
+```c
+int epoll_wait(HANDLE ephnd,
+               struct epoll_event* events,
+               int maxevents,
+               int timeout);
+```
+
+* Receive socket events from an epoll port.
+* `events` should point to a caller-allocated array of
+  [`epoll_event`](#struct-epoll_event) structs, which will receive the
+  reported events.
+* `maxevents` is the maximum number of events that will be written to the
+  `events` array, and must be greater than zero.
+* `timeout` specifies whether to block when no events are immediately available.
+  - `<0` block indefinitely
+  - `0`  report any events that are already waiting, but don't block
+  - `≥1` block for at most N milliseconds
+* Return value:
+  - `-1` an error occurred
+  - `0`  timed out without any events to report
+  - `≥1` the number of events stored in the `events` buffer
+* [Linux man page][man epoll_wait]
+
+### struct epoll_event
+
+```c
+typedef union epoll_data {
+  void* ptr;
+  int fd;
+  uint32_t u32;
+  uint64_t u64;
+  SOCKET sock;        /* Windows specific */
+  HANDLE hnd;         /* Windows specific */
+} epoll_data_t;
+```
+
+```c
+struct epoll_event {
+  uint32_t events;    /* Epoll events and flags */
+  epoll_data_t data;  /* User data variable */
+};
+```
+
+* The `events` field is a bit mask containing the events being
+  monitored/reported, and optional flags.<br>
+  Flags are accepted by [`epoll_ctl()`](#epoll_ctl), but they are not reported
+  back by [`epoll_wait()`](#epoll_wait).
+* The `data` field can be used to associate application-specific information
+  with a socket; its value will be returned unmodified by
+  [`epoll_wait()`](#epoll_wait).
+* [Linux man page][man epoll_ctl]
+
+| Event         | Description                                                          |
+|---------------|----------------------------------------------------------------------|
+| `EPOLLIN`     | incoming data available, or incoming connection ready to be accepted |
+| `EPOLLOUT`    | ready to send data, or outgoing connection successfully established  |
+| `EPOLLRDHUP`  | remote peer initiated graceful socket shutdown                       |
+| `EPOLLPRI`    | out-of-band data available for reading                               |
+| `EPOLLERR`    | socket error<sup>1</sup>                                             |
+| `EPOLLHUP`    | socket hang-up<sup>1</sup>                                           |
+| `EPOLLRDNORM` | same as `EPOLLIN`                                                    |
+| `EPOLLRDBAND` | same as `EPOLLPRI`                                                   |
+| `EPOLLWRNORM` | same as `EPOLLOUT`                                                   |
+| `EPOLLWRBAND` | same as `EPOLLOUT`                                                   |
+| `EPOLLMSG`    | never reported                                                       |
+
+| Flag             | Description               |
+|------------------|---------------------------|
+| `EPOLLONESHOT`   | report event(s) only once |
+| `EPOLLET`        | not supported by wepoll   |
+| `EPOLLEXCLUSIVE` | not supported by wepoll   |
+| `EPOLLWAKEUP`    | not supported by wepoll   |
+
+<sup>1</sup>: the `EPOLLERR` and `EPOLLHUP` events may always be reported by
+[`epoll_wait()`](#epoll_wait), regardless of the event mask that was passed to
+[`epoll_ctl()`](#epoll_ctl).
+
+
+[ci status badge]:  https://ci.appveyor.com/api/projects/status/github/piscisaureus/wepoll?branch=master&svg=true
+[ci status link]:   https://ci.appveyor.com/project/piscisaureus/wepoll/branch/master
+[dist]:             https://github.com/piscisaureus/wepoll/tree/dist
+[man epoll]:        http://man7.org/linux/man-pages/man7/epoll.7.html
+[man epoll_create]: http://man7.org/linux/man-pages/man2/epoll_create.2.html
+[man epoll_ctl]:    http://man7.org/linux/man-pages/man2/epoll_ctl.2.html
+[man epoll_wait]:   http://man7.org/linux/man-pages/man2/epoll_wait.2.html
+[msdn accept]:      https://msdn.microsoft.com/en-us/library/windows/desktop/ms737526(v=vs.85).aspx
+[msdn socket]:      https://msdn.microsoft.com/en-us/library/windows/desktop/ms740506(v=vs.85).aspx
+[msdn wsasocket]:   https://msdn.microsoft.com/en-us/library/windows/desktop/ms742212(v=vs.85).aspx
+[select scale]:     https://daniel.haxx.se/docs/poll-vs-select.html
+[wsapoll broken]:   https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/
+[wepoll.c]:         https://github.com/piscisaureus/wepoll/blob/dist/wepoll.c
+[wepoll.h]:         https://github.com/piscisaureus/wepoll/blob/dist/wepoll.h
diff --git a/sources/wepoll/wepoll.c b/sources/wepoll/wepoll.c
new file mode 100644
index 000000000..651673aad
--- /dev/null
+++ b/sources/wepoll/wepoll.c
@@ -0,0 +1,2189 @@
+/*
+ * wepoll - epoll for Windows
+ * https://github.com/piscisaureus/wepoll
+ *
+ * Copyright 2012-2019, Bert Belder <bertbelder@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef WEPOLL_EXPORT
+#define WEPOLL_EXPORT
+#endif
+
+#include <stdint.h>
+
+enum EPOLL_EVENTS {
+  EPOLLIN      = (int) (1U <<  0),
+  EPOLLPRI     = (int) (1U <<  1),
+  EPOLLOUT     = (int) (1U <<  2),
+  EPOLLERR     = (int) (1U <<  3),
+  EPOLLHUP     = (int) (1U <<  4),
+  EPOLLRDNORM  = (int) (1U <<  6),
+  EPOLLRDBAND  = (int) (1U <<  7),
+  EPOLLWRNORM  = (int) (1U <<  8),
+  EPOLLWRBAND  = (int) (1U <<  9),
+  EPOLLMSG     = (int) (1U << 10), /* Never reported. */
+  EPOLLRDHUP   = (int) (1U << 13),
+  EPOLLONESHOT = (int) (1U << 31)
+};
+
+#define EPOLLIN      (1U <<  0)
+#define EPOLLPRI     (1U <<  1)
+#define EPOLLOUT     (1U <<  2)
+#define EPOLLERR     (1U <<  3)
+#define EPOLLHUP     (1U <<  4)
+#define EPOLLRDNORM  (1U <<  6)
+#define EPOLLRDBAND  (1U <<  7)
+#define EPOLLWRNORM  (1U <<  8)
+#define EPOLLWRBAND  (1U <<  9)
+#define EPOLLMSG     (1U << 10)
+#define EPOLLRDHUP   (1U << 13)
+#define EPOLLONESHOT (1U << 31)
+
+#define EPOLL_CTL_ADD 1
+#define EPOLL_CTL_MOD 2
+#define EPOLL_CTL_DEL 3
+
+typedef void* HANDLE;
+typedef uintptr_t SOCKET;
+
+typedef union epoll_data {
+  void* ptr;
+  int fd;
+  uint32_t u32;
+  uint64_t u64;
+  SOCKET sock; /* Windows specific */
+  HANDLE hnd;  /* Windows specific */
+} epoll_data_t;
+
+struct epoll_event {
+  uint32_t events;   /* Epoll events and flags */
+  epoll_data_t data; /* User data variable */
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+WEPOLL_EXPORT HANDLE epoll_create(int size);
+WEPOLL_EXPORT HANDLE epoll_create1(int flags);
+
+WEPOLL_EXPORT int epoll_close(HANDLE ephnd);
+
+WEPOLL_EXPORT int epoll_ctl(HANDLE ephnd,
+                            int op,
+                            SOCKET sock,
+                            struct epoll_event* event);
+
+WEPOLL_EXPORT int epoll_wait(HANDLE ephnd,
+                             struct epoll_event* events,
+                             int maxevents,
+                             int timeout);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#include <malloc.h>
+#include <stdlib.h>
+
+#define WEPOLL_INTERNAL static
+#define WEPOLL_INTERNAL_VAR static
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wreserved-id-macro"
+#endif
+
+#ifdef _WIN32_WINNT
+#undef _WIN32_WINNT
+#endif
+
+#define _WIN32_WINNT 0x0600
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#ifndef __GNUC__
+#pragma warning(push, 1)
+#endif
+
+#include <WS2tcpip.h>
+#include <WinSock2.h>
+#include <Windows.h>
+
+#ifndef __GNUC__
+#pragma warning(pop)
+#endif
+
+WEPOLL_INTERNAL int nt_global_init(void);
+
+typedef LONG NTSTATUS;
+typedef NTSTATUS* PNTSTATUS;
+
+#ifndef NT_SUCCESS
+#define NT_SUCCESS(status) (((NTSTATUS)(status)) >= 0)
+#endif
+
+#ifndef STATUS_SUCCESS
+#define STATUS_SUCCESS ((NTSTATUS) 0x00000000L)
+#endif
+
+#ifndef STATUS_PENDING
+#define STATUS_PENDING ((NTSTATUS) 0x00000103L)
+#endif
+
+#ifndef STATUS_CANCELLED
+#define STATUS_CANCELLED ((NTSTATUS) 0xC0000120L)
+#endif
+
+typedef struct _IO_STATUS_BLOCK {
+  NTSTATUS Status;
+  ULONG_PTR Information;
+} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
+
+typedef VOID(NTAPI* PIO_APC_ROUTINE)(PVOID ApcContext,
+                                     PIO_STATUS_BLOCK IoStatusBlock,
+                                     ULONG Reserved);
+
+typedef struct _UNICODE_STRING {
+  USHORT Length;
+  USHORT MaximumLength;
+  PWSTR Buffer;
+} UNICODE_STRING, *PUNICODE_STRING;
+
+#define RTL_CONSTANT_STRING(s) \
+  { sizeof(s) - sizeof((s)[0]), sizeof(s), s }
+
+typedef struct _OBJECT_ATTRIBUTES {
+  ULONG Length;
+  HANDLE RootDirectory;
+  PUNICODE_STRING ObjectName;
+  ULONG Attributes;
+  PVOID SecurityDescriptor;
+  PVOID SecurityQualityOfService;
+} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
+
+#define RTL_CONSTANT_OBJECT_ATTRIBUTES(ObjectName, Attributes) \
+  { sizeof(OBJECT_ATTRIBUTES), NULL, ObjectName, Attributes, NULL, NULL }
+
+#ifndef FILE_OPEN
+#define FILE_OPEN 0x00000001UL
+#endif
+
+#define KEYEDEVENT_WAIT 0x00000001UL
+#define KEYEDEVENT_WAKE 0x00000002UL
+#define KEYEDEVENT_ALL_ACCESS \
+  (STANDARD_RIGHTS_REQUIRED | KEYEDEVENT_WAIT | KEYEDEVENT_WAKE)
+
+#define NT_NTDLL_IMPORT_LIST(X)           \
+  X(NTSTATUS,                             \
+    NTAPI,                                \
+    NtCreateFile,                         \
+    (PHANDLE FileHandle,                  \
+     ACCESS_MASK DesiredAccess,           \
+     POBJECT_ATTRIBUTES ObjectAttributes, \
+     PIO_STATUS_BLOCK IoStatusBlock,      \
+     PLARGE_INTEGER AllocationSize,       \
+     ULONG FileAttributes,                \
+     ULONG ShareAccess,                   \
+     ULONG CreateDisposition,             \
+     ULONG CreateOptions,                 \
+     PVOID EaBuffer,                      \
+     ULONG EaLength))                     \
+                                          \
+  X(NTSTATUS,                             \
+    NTAPI,                                \
+    NtCreateKeyedEvent,                   \
+    (PHANDLE KeyedEventHandle,            \
+     ACCESS_MASK DesiredAccess,           \
+     POBJECT_ATTRIBUTES ObjectAttributes, \
+     ULONG Flags))                        \
+                                          \
+  X(NTSTATUS,                             \
+    NTAPI,                                \
+    NtDeviceIoControlFile,                \
+    (HANDLE FileHandle,                   \
+     HANDLE Event,                        \
+     PIO_APC_ROUTINE ApcRoutine,          \
+     PVOID ApcContext,                    \
+     PIO_STATUS_BLOCK IoStatusBlock,      \
+     ULONG IoControlCode,                 \
+     PVOID InputBuffer,                   \
+     ULONG InputBufferLength,             \
+     PVOID OutputBuffer,                  \
+     ULONG OutputBufferLength))           \
+                                          \
+  X(NTSTATUS,                             \
+    NTAPI,                                \
+    NtReleaseKeyedEvent,                  \
+    (HANDLE KeyedEventHandle,             \
+     PVOID KeyValue,                      \
+     BOOLEAN Alertable,                   \
+     PLARGE_INTEGER Timeout))             \
+                                          \
+  X(NTSTATUS,                             \
+    NTAPI,                                \
+    NtWaitForKeyedEvent,                  \
+    (HANDLE KeyedEventHandle,             \
+     PVOID KeyValue,                      \
+     BOOLEAN Alertable,                   \
+     PLARGE_INTEGER Timeout))             \
+                                          \
+  X(ULONG, WINAPI, RtlNtStatusToDosError, (NTSTATUS Status))
+
+#define X(return_type, attributes, name, parameters) \
+  WEPOLL_INTERNAL_VAR return_type(attributes* name) parameters;
+NT_NTDLL_IMPORT_LIST(X)
+#undef X
+
+#include <assert.h>
+#include <stddef.h>
+
+#ifndef _SSIZE_T_DEFINED
+typedef intptr_t ssize_t;
+#endif
+
+#define array_count(a) (sizeof(a) / (sizeof((a)[0])))
+
+#define container_of(ptr, type, member) \
+  ((type*) ((uintptr_t) (ptr) - offsetof(type, member)))
+
+#define unused_var(v) ((void) (v))
+
+/* Polyfill `inline` for older versions of msvc (up to Visual Studio 2013) */
+#if defined(_MSC_VER) && _MSC_VER < 1900
+#define inline __inline
+#endif
+
+#define AFD_POLL_RECEIVE           0x0001
+#define AFD_POLL_RECEIVE_EXPEDITED 0x0002
+#define AFD_POLL_SEND              0x0004
+#define AFD_POLL_DISCONNECT        0x0008
+#define AFD_POLL_ABORT             0x0010
+#define AFD_POLL_LOCAL_CLOSE       0x0020
+#define AFD_POLL_ACCEPT            0x0080
+#define AFD_POLL_CONNECT_FAIL      0x0100
+
+typedef struct _AFD_POLL_HANDLE_INFO {
+  HANDLE Handle;
+  ULONG Events;
+  NTSTATUS Status;
+} AFD_POLL_HANDLE_INFO, *PAFD_POLL_HANDLE_INFO;
+
+typedef struct _AFD_POLL_INFO {
+  LARGE_INTEGER Timeout;
+  ULONG NumberOfHandles;
+  ULONG Exclusive;
+  AFD_POLL_HANDLE_INFO Handles[1];
+} AFD_POLL_INFO, *PAFD_POLL_INFO;
+
+WEPOLL_INTERNAL int afd_create_helper_handle(HANDLE iocp,
+                                             HANDLE* afd_helper_handle_out);
+
+WEPOLL_INTERNAL int afd_poll(HANDLE afd_helper_handle,
+                             AFD_POLL_INFO* poll_info,
+                             OVERLAPPED* overlapped);
+
+#define return_map_error(value) \
+  do {                          \
+    err_map_win_error();        \
+    return (value);             \
+  } while (0)
+
+#define return_set_error(value, error) \
+  do {                                 \
+    err_set_win_error(error);          \
+    return (value);                    \
+  } while (0)
+
+WEPOLL_INTERNAL void err_map_win_error(void);
+WEPOLL_INTERNAL void err_set_win_error(DWORD error);
+WEPOLL_INTERNAL int err_check_handle(HANDLE handle);
+
+WEPOLL_INTERNAL int ws_global_init(void);
+WEPOLL_INTERNAL SOCKET ws_get_base_socket(SOCKET socket);
+
+#define IOCTL_AFD_POLL 0x00012024
+
+static UNICODE_STRING afd__helper_name =
+    RTL_CONSTANT_STRING(L"\\Device\\Afd\\Wepoll");
+
+static OBJECT_ATTRIBUTES afd__helper_attributes =
+    RTL_CONSTANT_OBJECT_ATTRIBUTES(&afd__helper_name, 0);
+
+int afd_create_helper_handle(HANDLE iocp, HANDLE* afd_helper_handle_out) {
+  HANDLE afd_helper_handle;
+  IO_STATUS_BLOCK iosb;
+  NTSTATUS status;
+
+  /* By opening \Device\Afd without specifying any extended attributes, we'll
+   * get a handle that lets us talk to the AFD driver, but that doesn't have an
+   * associated endpoint (so it's not a socket). */
+  status = NtCreateFile(&afd_helper_handle,
+                        SYNCHRONIZE,
+                        &afd__helper_attributes,
+                        &iosb,
+                        NULL,
+                        0,
+                        FILE_SHARE_READ | FILE_SHARE_WRITE,
+                        FILE_OPEN,
+                        0,
+                        NULL,
+                        0);
+  if (status != STATUS_SUCCESS)
+    return_set_error(-1, RtlNtStatusToDosError(status));
+
+  if (CreateIoCompletionPort(afd_helper_handle, iocp, 0, 0) == NULL)
+    goto error;
+
+  if (!SetFileCompletionNotificationModes(afd_helper_handle,
+                                          FILE_SKIP_SET_EVENT_ON_HANDLE))
+    goto error;
+
+  *afd_helper_handle_out = afd_helper_handle;
+  return 0;
+
+error:
+  CloseHandle(afd_helper_handle);
+  return_map_error(-1);
+}
+
+int afd_poll(HANDLE afd_helper_handle,
+             AFD_POLL_INFO* poll_info,
+             OVERLAPPED* overlapped) {
+  IO_STATUS_BLOCK* iosb;
+  HANDLE event;
+  void* apc_context;
+  NTSTATUS status;
+
+  /* Blocking operation is not supported. */
+  assert(overlapped != NULL);
+
+  iosb = (IO_STATUS_BLOCK*) &overlapped->Internal;
+  event = overlapped->hEvent;
+
+  /* Do what other windows APIs would do: if hEvent has it's lowest bit set,
+   * don't post a completion to the completion port. */
+  if ((uintptr_t) event & 1) {
+    event = (HANDLE)((uintptr_t) event & ~(uintptr_t) 1);
+    apc_context = NULL;
+  } else {
+    apc_context = overlapped;
+  }
+
+  iosb->Status = STATUS_PENDING;
+  status = NtDeviceIoControlFile(afd_helper_handle,
+                                 event,
+                                 NULL,
+                                 apc_context,
+                                 iosb,
+                                 IOCTL_AFD_POLL,
+                                 poll_info,
+                                 sizeof *poll_info,
+                                 poll_info,
+                                 sizeof *poll_info);
+
+  if (status == STATUS_SUCCESS)
+    return 0;
+  else if (status == STATUS_PENDING)
+    return_set_error(-1, ERROR_IO_PENDING);
+  else
+    return_set_error(-1, RtlNtStatusToDosError(status));
+}
+
+WEPOLL_INTERNAL int epoll_global_init(void);
+
+WEPOLL_INTERNAL int init(void);
+
+#include <stdbool.h>
+
+typedef struct queue_node queue_node_t;
+
+typedef struct queue_node {
+  queue_node_t* prev;
+  queue_node_t* next;
+} queue_node_t;
+
+typedef struct queue {
+  queue_node_t head;
+} queue_t;
+
+WEPOLL_INTERNAL void queue_init(queue_t* queue);
+WEPOLL_INTERNAL void queue_node_init(queue_node_t* node);
+
+WEPOLL_INTERNAL queue_node_t* queue_first(const queue_t* queue);
+WEPOLL_INTERNAL queue_node_t* queue_last(const queue_t* queue);
+
+WEPOLL_INTERNAL void queue_prepend(queue_t* queue, queue_node_t* node);
+WEPOLL_INTERNAL void queue_append(queue_t* queue, queue_node_t* node);
+WEPOLL_INTERNAL void queue_move_first(queue_t* queue, queue_node_t* node);
+WEPOLL_INTERNAL void queue_move_last(queue_t* queue, queue_node_t* node);
+WEPOLL_INTERNAL void queue_remove(queue_node_t* node);
+
+WEPOLL_INTERNAL bool queue_empty(const queue_t* queue);
+WEPOLL_INTERNAL bool queue_enqueued(const queue_node_t* node);
+
+typedef struct port_state port_state_t;
+typedef struct poll_group poll_group_t;
+
+WEPOLL_INTERNAL poll_group_t* poll_group_acquire(port_state_t* port);
+WEPOLL_INTERNAL void poll_group_release(poll_group_t* poll_group);
+
+WEPOLL_INTERNAL void poll_group_delete(poll_group_t* poll_group);
+
+WEPOLL_INTERNAL poll_group_t* poll_group_from_queue_node(
+    queue_node_t* queue_node);
+WEPOLL_INTERNAL HANDLE
+    poll_group_get_afd_helper_handle(poll_group_t* poll_group);
+
+/* N.b.: the tree functions do not set errno or LastError when they fail. Each
+ * of the API functions has at most one failure mode. It is up to the caller to
+ * set an appropriate error code when necessary. */
+
+typedef struct tree tree_t;
+typedef struct tree_node tree_node_t;
+
+typedef struct tree {
+  tree_node_t* root;
+} tree_t;
+
+typedef struct tree_node {
+  tree_node_t* left;
+  tree_node_t* right;
+  tree_node_t* parent;
+  uintptr_t key;
+  bool red;
+} tree_node_t;
+
+WEPOLL_INTERNAL void tree_init(tree_t* tree);
+WEPOLL_INTERNAL void tree_node_init(tree_node_t* node);
+
+WEPOLL_INTERNAL int tree_add(tree_t* tree, tree_node_t* node, uintptr_t key);
+WEPOLL_INTERNAL void tree_del(tree_t* tree, tree_node_t* node);
+
+WEPOLL_INTERNAL tree_node_t* tree_find(const tree_t* tree, uintptr_t key);
+WEPOLL_INTERNAL tree_node_t* tree_root(const tree_t* tree);
+
+typedef struct port_state port_state_t;
+typedef struct sock_state sock_state_t;
+
+WEPOLL_INTERNAL sock_state_t* sock_new(port_state_t* port_state,
+                                       SOCKET socket);
+WEPOLL_INTERNAL void sock_delete(port_state_t* port_state,
+                                 sock_state_t* sock_state);
+WEPOLL_INTERNAL void sock_force_delete(port_state_t* port_state,
+                                       sock_state_t* sock_state);
+
+WEPOLL_INTERNAL int sock_set_event(port_state_t* port_state,
+                                   sock_state_t* sock_state,
+                                   const struct epoll_event* ev);
+
+WEPOLL_INTERNAL int sock_update(port_state_t* port_state,
+                                sock_state_t* sock_state);
+WEPOLL_INTERNAL int sock_feed_event(port_state_t* port_state,
+                                    OVERLAPPED* overlapped,
+                                    struct epoll_event* ev);
+
+WEPOLL_INTERNAL sock_state_t* sock_state_from_queue_node(
+    queue_node_t* queue_node);
+WEPOLL_INTERNAL queue_node_t* sock_state_to_queue_node(
+    sock_state_t* sock_state);
+WEPOLL_INTERNAL sock_state_t* sock_state_from_tree_node(
+    tree_node_t* tree_node);
+WEPOLL_INTERNAL tree_node_t* sock_state_to_tree_node(sock_state_t* sock_state);
+
+/* The reflock is a special kind of lock that normally prevents a chunk of
+ * memory from being freed, but does allow the chunk of memory to eventually be
+ * released in a coordinated fashion.
+ *
+ * Under normal operation, threads increase and decrease the reference count,
+ * which are wait-free operations.
+ *
+ * Exactly once during the reflock's lifecycle, a thread holding a reference to
+ * the lock may "destroy" the lock; this operation blocks until all other
+ * threads holding a reference to the lock have dereferenced it. After
+ * "destroy" returns, the calling thread may assume that no other threads have
+ * a reference to the lock.
+ *
+ * Attemmpting to lock or destroy a lock after reflock_unref_and_destroy() has
+ * been called is invalid and results in undefined behavior. Therefore the user
+ * should use another lock to guarantee that this can't happen.
+ */
+
+typedef struct reflock {
+  volatile long state; /* 32-bit Interlocked APIs operate on `long` values. */
+} reflock_t;
+
+WEPOLL_INTERNAL int reflock_global_init(void);
+
+WEPOLL_INTERNAL void reflock_init(reflock_t* reflock);
+WEPOLL_INTERNAL void reflock_ref(reflock_t* reflock);
+WEPOLL_INTERNAL void reflock_unref(reflock_t* reflock);
+WEPOLL_INTERNAL void reflock_unref_and_destroy(reflock_t* reflock);
+
+typedef struct ts_tree {
+  tree_t tree;
+  SRWLOCK lock;
+} ts_tree_t;
+
+typedef struct ts_tree_node {
+  tree_node_t tree_node;
+  reflock_t reflock;
+} ts_tree_node_t;
+
+WEPOLL_INTERNAL void ts_tree_init(ts_tree_t* rtl);
+WEPOLL_INTERNAL void ts_tree_node_init(ts_tree_node_t* node);
+
+WEPOLL_INTERNAL int ts_tree_add(ts_tree_t* ts_tree,
+                                ts_tree_node_t* node,
+                                uintptr_t key);
+
+WEPOLL_INTERNAL ts_tree_node_t* ts_tree_del_and_ref(ts_tree_t* ts_tree,
+                                                    uintptr_t key);
+WEPOLL_INTERNAL ts_tree_node_t* ts_tree_find_and_ref(ts_tree_t* ts_tree,
+                                                     uintptr_t key);
+
+WEPOLL_INTERNAL void ts_tree_node_unref(ts_tree_node_t* node);
+WEPOLL_INTERNAL void ts_tree_node_unref_and_destroy(ts_tree_node_t* node);
+
+typedef struct port_state port_state_t;
+typedef struct sock_state sock_state_t;
+
+typedef struct port_state {
+  HANDLE iocp;
+  tree_t sock_tree;
+  queue_t sock_update_queue;
+  queue_t sock_deleted_queue;
+  queue_t poll_group_queue;
+  ts_tree_node_t handle_tree_node;
+  CRITICAL_SECTION lock;
+  size_t active_poll_count;
+} port_state_t;
+
+WEPOLL_INTERNAL port_state_t* port_new(HANDLE* iocp_out);
+WEPOLL_INTERNAL int port_close(port_state_t* port_state);
+WEPOLL_INTERNAL int port_delete(port_state_t* port_state);
+
+WEPOLL_INTERNAL int port_wait(port_state_t* port_state,
+                              struct epoll_event* events,
+                              int maxevents,
+                              int timeout);
+
+WEPOLL_INTERNAL int port_ctl(port_state_t* port_state,
+                             int op,
+                             SOCKET sock,
+                             struct epoll_event* ev);
+
+WEPOLL_INTERNAL int port_register_socket_handle(port_state_t* port_state,
+                                                sock_state_t* sock_state,
+                                                SOCKET socket);
+WEPOLL_INTERNAL void port_unregister_socket_handle(port_state_t* port_state,
+                                                   sock_state_t* sock_state);
+WEPOLL_INTERNAL sock_state_t* port_find_socket(port_state_t* port_state,
+                                               SOCKET socket);
+
+WEPOLL_INTERNAL void port_request_socket_update(port_state_t* port_state,
+                                                sock_state_t* sock_state);
+WEPOLL_INTERNAL void port_cancel_socket_update(port_state_t* port_state,
+                                               sock_state_t* sock_state);
+
+WEPOLL_INTERNAL void port_add_deleted_socket(port_state_t* port_state,
+                                             sock_state_t* sock_state);
+WEPOLL_INTERNAL void port_remove_deleted_socket(port_state_t* port_state,
+                                                sock_state_t* sock_state);
+
+static ts_tree_t epoll__handle_tree;
+
+static inline port_state_t* epoll__handle_tree_node_to_port(
+    ts_tree_node_t* tree_node) {
+  return container_of(tree_node, port_state_t, handle_tree_node);
+}
+
+int epoll_global_init(void) {
+  ts_tree_init(&epoll__handle_tree);
+  return 0;
+}
+
+static HANDLE epoll__create(void) {
+  port_state_t* port_state;
+  HANDLE ephnd;
+
+  if (init() < 0)
+    return NULL;
+
+  port_state = port_new(&ephnd);
+  if (port_state == NULL)
+    return NULL;
+
+  if (ts_tree_add(&epoll__handle_tree,
+                  &port_state->handle_tree_node,
+                  (uintptr_t) ephnd) < 0) {
+    /* This should never happen. */
+    port_delete(port_state);
+    return_set_error(NULL, ERROR_ALREADY_EXISTS);
+  }
+
+  return ephnd;
+}
+
+HANDLE epoll_create(int size) {
+  if (size <= 0)
+    return_set_error(NULL, ERROR_INVALID_PARAMETER);
+
+  return epoll__create();
+}
+
+HANDLE epoll_create1(int flags) {
+  if (flags != 0)
+    return_set_error(NULL, ERROR_INVALID_PARAMETER);
+
+  return epoll__create();
+}
+
+int epoll_close(HANDLE ephnd) {
+  ts_tree_node_t* tree_node;
+  port_state_t* port_state;
+
+  if (init() < 0)
+    return -1;
+
+  tree_node = ts_tree_del_and_ref(&epoll__handle_tree, (uintptr_t) ephnd);
+  if (tree_node == NULL) {
+    err_set_win_error(ERROR_INVALID_PARAMETER);
+    goto err;
+  }
+
+  port_state = epoll__handle_tree_node_to_port(tree_node);
+  port_close(port_state);
+
+  ts_tree_node_unref_and_destroy(tree_node);
+
+  return port_delete(port_state);
+
+err:
+  err_check_handle(ephnd);
+  return -1;
+}
+
+int epoll_ctl(HANDLE ephnd, int op, SOCKET sock, struct epoll_event* ev) {
+  ts_tree_node_t* tree_node;
+  port_state_t* port_state;
+  int r;
+
+  if (init() < 0)
+    return -1;
+
+  tree_node = ts_tree_find_and_ref(&epoll__handle_tree, (uintptr_t) ephnd);
+  if (tree_node == NULL) {
+    err_set_win_error(ERROR_INVALID_PARAMETER);
+    goto err;
+  }
+
+  port_state = epoll__handle_tree_node_to_port(tree_node);
+  r = port_ctl(port_state, op, sock, ev);
+
+  ts_tree_node_unref(tree_node);
+
+  if (r < 0)
+    goto err;
+
+  return 0;
+
+err:
+  /* On Linux, in the case of epoll_ctl_mod(), EBADF takes priority over other
+   * errors. Wepoll mimics this behavior. */
+  err_check_handle(ephnd);
+  err_check_handle((HANDLE) sock);
+  return -1;
+}
+
+int epoll_wait(HANDLE ephnd,
+               struct epoll_event* events,
+               int maxevents,
+               int timeout) {
+  ts_tree_node_t* tree_node;
+  port_state_t* port_state;
+  int num_events;
+
+  if (maxevents <= 0)
+    return_set_error(-1, ERROR_INVALID_PARAMETER);
+
+  if (init() < 0)
+    return -1;
+
+  tree_node = ts_tree_find_and_ref(&epoll__handle_tree, (uintptr_t) ephnd);
+  if (tree_node == NULL) {
+    err_set_win_error(ERROR_INVALID_PARAMETER);
+    goto err;
+  }
+
+  port_state = epoll__handle_tree_node_to_port(tree_node);
+  num_events = port_wait(port_state, events, maxevents, timeout);
+
+  ts_tree_node_unref(tree_node);
+
+  if (num_events < 0)
+    goto err;
+
+  return num_events;
+
+err:
+  err_check_handle(ephnd);
+  return -1;
+}
+
+#include <errno.h>
+
+#define ERR__ERRNO_MAPPINGS(X)               \
+  X(ERROR_ACCESS_DENIED, EACCES)             \
+  X(ERROR_ALREADY_EXISTS, EEXIST)            \
+  X(ERROR_BAD_COMMAND, EACCES)               \
+  X(ERROR_BAD_EXE_FORMAT, ENOEXEC)           \
+  X(ERROR_BAD_LENGTH, EACCES)                \
+  X(ERROR_BAD_NETPATH, ENOENT)               \
+  X(ERROR_BAD_NET_NAME, ENOENT)              \
+  X(ERROR_BAD_NET_RESP, ENETDOWN)            \
+  X(ERROR_BAD_PATHNAME, ENOENT)              \
+  X(ERROR_BROKEN_PIPE, EPIPE)                \
+  X(ERROR_CANNOT_MAKE, EACCES)               \
+  X(ERROR_COMMITMENT_LIMIT, ENOMEM)          \
+  X(ERROR_CONNECTION_ABORTED, ECONNABORTED)  \
+  X(ERROR_CONNECTION_ACTIVE, EISCONN)        \
+  X(ERROR_CONNECTION_REFUSED, ECONNREFUSED)  \
+  X(ERROR_CRC, EACCES)                       \
+  X(ERROR_DIR_NOT_EMPTY, ENOTEMPTY)          \
+  X(ERROR_DISK_FULL, ENOSPC)                 \
+  X(ERROR_DUP_NAME, EADDRINUSE)              \
+  X(ERROR_FILENAME_EXCED_RANGE, ENOENT)      \
+  X(ERROR_FILE_NOT_FOUND, ENOENT)            \
+  X(ERROR_GEN_FAILURE, EACCES)               \
+  X(ERROR_GRACEFUL_DISCONNECT, EPIPE)        \
+  X(ERROR_HOST_DOWN, EHOSTUNREACH)           \
+  X(ERROR_HOST_UNREACHABLE, EHOSTUNREACH)    \
+  X(ERROR_INSUFFICIENT_BUFFER, EFAULT)       \
+  X(ERROR_INVALID_ADDRESS, EADDRNOTAVAIL)    \
+  X(ERROR_INVALID_FUNCTION, EINVAL)          \
+  X(ERROR_INVALID_HANDLE, EBADF)             \
+  X(ERROR_INVALID_NETNAME, EADDRNOTAVAIL)    \
+  X(ERROR_INVALID_PARAMETER, EINVAL)         \
+  X(ERROR_INVALID_USER_BUFFER, EMSGSIZE)     \
+  X(ERROR_IO_PENDING, EINPROGRESS)           \
+  X(ERROR_LOCK_VIOLATION, EACCES)            \
+  X(ERROR_MORE_DATA, EMSGSIZE)               \
+  X(ERROR_NETNAME_DELETED, ECONNABORTED)     \
+  X(ERROR_NETWORK_ACCESS_DENIED, EACCES)     \
+  X(ERROR_NETWORK_BUSY, ENETDOWN)            \
+  X(ERROR_NETWORK_UNREACHABLE, ENETUNREACH)  \
+  X(ERROR_NOACCESS, EFAULT)                  \
+  X(ERROR_NONPAGED_SYSTEM_RESOURCES, ENOMEM) \
+  X(ERROR_NOT_ENOUGH_MEMORY, ENOMEM)         \
+  X(ERROR_NOT_ENOUGH_QUOTA, ENOMEM)          \
+  X(ERROR_NOT_FOUND, ENOENT)                 \
+  X(ERROR_NOT_LOCKED, EACCES)                \
+  X(ERROR_NOT_READY, EACCES)                 \
+  X(ERROR_NOT_SAME_DEVICE, EXDEV)            \
+  X(ERROR_NOT_SUPPORTED, ENOTSUP)            \
+  X(ERROR_NO_MORE_FILES, ENOENT)             \
+  X(ERROR_NO_SYSTEM_RESOURCES, ENOMEM)       \
+  X(ERROR_OPERATION_ABORTED, EINTR)          \
+  X(ERROR_OUT_OF_PAPER, EACCES)              \
+  X(ERROR_PAGED_SYSTEM_RESOURCES, ENOMEM)    \
+  X(ERROR_PAGEFILE_QUOTA, ENOMEM)            \
+  X(ERROR_PATH_NOT_FOUND, ENOENT)            \
+  X(ERROR_PIPE_NOT_CONNECTED, EPIPE)         \
+  X(ERROR_PORT_UNREACHABLE, ECONNRESET)      \
+  X(ERROR_PROTOCOL_UNREACHABLE, ENETUNREACH) \
+  X(ERROR_REM_NOT_LIST, ECONNREFUSED)        \
+  X(ERROR_REQUEST_ABORTED, EINTR)            \
+  X(ERROR_REQ_NOT_ACCEP, EWOULDBLOCK)        \
+  X(ERROR_SECTOR_NOT_FOUND, EACCES)          \
+  X(ERROR_SEM_TIMEOUT, ETIMEDOUT)            \
+  X(ERROR_SHARING_VIOLATION, EACCES)         \
+  X(ERROR_TOO_MANY_NAMES, ENOMEM)            \
+  X(ERROR_TOO_MANY_OPEN_FILES, EMFILE)       \
+  X(ERROR_UNEXP_NET_ERR, ECONNABORTED)       \
+  X(ERROR_WAIT_NO_CHILDREN, ECHILD)          \
+  X(ERROR_WORKING_SET_QUOTA, ENOMEM)         \
+  X(ERROR_WRITE_PROTECT, EACCES)             \
+  X(ERROR_WRONG_DISK, EACCES)                \
+  X(WSAEACCES, EACCES)                       \
+  X(WSAEADDRINUSE, EADDRINUSE)               \
+  X(WSAEADDRNOTAVAIL, EADDRNOTAVAIL)         \
+  X(WSAEAFNOSUPPORT, EAFNOSUPPORT)           \
+  X(WSAECONNABORTED, ECONNABORTED)           \
+  X(WSAECONNREFUSED, ECONNREFUSED)           \
+  X(WSAECONNRESET, ECONNRESET)               \
+  X(WSAEDISCON, EPIPE)                       \
+  X(WSAEFAULT, EFAULT)                       \
+  X(WSAEHOSTDOWN, EHOSTUNREACH)              \
+  X(WSAEHOSTUNREACH, EHOSTUNREACH)           \
+  X(WSAEINPROGRESS, EBUSY)                   \
+  X(WSAEINTR, EINTR)                         \
+  X(WSAEINVAL, EINVAL)                       \
+  X(WSAEISCONN, EISCONN)                     \
+  X(WSAEMSGSIZE, EMSGSIZE)                   \
+  X(WSAENETDOWN, ENETDOWN)                   \
+  X(WSAENETRESET, EHOSTUNREACH)              \
+  X(WSAENETUNREACH, ENETUNREACH)             \
+  X(WSAENOBUFS, ENOMEM)                      \
+  X(WSAENOTCONN, ENOTCONN)                   \
+  X(WSAENOTSOCK, ENOTSOCK)                   \
+  X(WSAEOPNOTSUPP, EOPNOTSUPP)               \
+  X(WSAEPROCLIM, ENOMEM)                     \
+  X(WSAESHUTDOWN, EPIPE)                     \
+  X(WSAETIMEDOUT, ETIMEDOUT)                 \
+  X(WSAEWOULDBLOCK, EWOULDBLOCK)             \
+  X(WSANOTINITIALISED, ENETDOWN)             \
+  X(WSASYSNOTREADY, ENETDOWN)                \
+  X(WSAVERNOTSUPPORTED, ENOSYS)
+
+static errno_t err__map_win_error_to_errno(DWORD error) {
+  switch (error) {
+#define X(error_sym, errno_sym) \
+  case error_sym:               \
+    return errno_sym;
+    ERR__ERRNO_MAPPINGS(X)
+#undef X
+  }
+  return EINVAL;
+}
+
+void err_map_win_error(void) {
+  errno = err__map_win_error_to_errno(GetLastError());
+}
+
+void err_set_win_error(DWORD error) {
+  SetLastError(error);
+  errno = err__map_win_error_to_errno(error);
+}
+
+int err_check_handle(HANDLE handle) {
+  DWORD flags;
+
+  /* GetHandleInformation() succeeds when passed INVALID_HANDLE_VALUE, so check
+   * for this condition explicitly. */
+  if (handle == INVALID_HANDLE_VALUE)
+    return_set_error(-1, ERROR_INVALID_HANDLE);
+
+  if (!GetHandleInformation(handle, &flags))
+    return_map_error(-1);
+
+  return 0;
+}
+
+static bool init__done = false;
+static INIT_ONCE init__once = INIT_ONCE_STATIC_INIT;
+
+static BOOL CALLBACK init__once_callback(INIT_ONCE* once,
+                                         void* parameter,
+                                         void** context) {
+  unused_var(once);
+  unused_var(parameter);
+  unused_var(context);
+
+  /* N.b. that initialization order matters here. */
+  if (ws_global_init() < 0 || nt_global_init() < 0 ||
+      reflock_global_init() < 0 || epoll_global_init() < 0)
+    return FALSE;
+
+  init__done = true;
+  return TRUE;
+}
+
+int init(void) {
+  if (!init__done &&
+      !InitOnceExecuteOnce(&init__once, init__once_callback, NULL, NULL))
+    /* `InitOnceExecuteOnce()` itself is infallible, and it doesn't set any
+     * error code when the once-callback returns FALSE. We return -1 here to
+     * indicate that global initialization failed; the failing init function is
+     * resposible for setting `errno` and calling `SetLastError()`. */
+    return -1;
+
+  return 0;
+}
+
+/* Set up a workaround for the following problem:
+ *   FARPROC addr = GetProcAddress(...);
+ *   MY_FUNC func = (MY_FUNC) addr;          <-- GCC 8 warning/error.
+ *   MY_FUNC func = (MY_FUNC) (void*) addr;  <-- MSVC  warning/error.
+ * To compile cleanly with either compiler, do casts with this "bridge" type:
+ *   MY_FUNC func = (MY_FUNC) (nt__fn_ptr_cast_t) addr; */
+#ifdef __GNUC__
+typedef void* nt__fn_ptr_cast_t;
+#else
+typedef FARPROC nt__fn_ptr_cast_t;
+#endif
+
+#define X(return_type, attributes, name, parameters) \
+  WEPOLL_INTERNAL return_type(attributes* name) parameters = NULL;
+NT_NTDLL_IMPORT_LIST(X)
+#undef X
+
+int nt_global_init(void) {
+  HMODULE ntdll;
+  FARPROC fn_ptr;
+
+  ntdll = GetModuleHandleW(L"ntdll.dll");
+  if (ntdll == NULL)
+    return -1;
+
+#define X(return_type, attributes, name, parameters) \
+  fn_ptr = GetProcAddress(ntdll, #name);             \
+  if (fn_ptr == NULL)                                \
+    return -1;                                       \
+  name = (return_type(attributes*) parameters)(nt__fn_ptr_cast_t) fn_ptr;
+  NT_NTDLL_IMPORT_LIST(X)
+#undef X
+
+  return 0;
+}
+
+#include <string.h>
+
+static const size_t POLL_GROUP__MAX_GROUP_SIZE = 32;
+
+typedef struct poll_group {
+  port_state_t* port_state;
+  queue_node_t queue_node;
+  HANDLE afd_helper_handle;
+  size_t group_size;
+} poll_group_t;
+
+static poll_group_t* poll_group__new(port_state_t* port_state) {
+  poll_group_t* poll_group = malloc(sizeof *poll_group);
+  if (poll_group == NULL)
+    return_set_error(NULL, ERROR_NOT_ENOUGH_MEMORY);
+
+  memset(poll_group, 0, sizeof *poll_group);
+
+  queue_node_init(&poll_group->queue_node);
+  poll_group->port_state = port_state;
+
+  if (afd_create_helper_handle(port_state->iocp,
+                               &poll_group->afd_helper_handle) < 0) {
+    free(poll_group);
+    return NULL;
+  }
+
+  queue_append(&port_state->poll_group_queue, &poll_group->queue_node);
+
+  return poll_group;
+}
+
+void poll_group_delete(poll_group_t* poll_group) {
+  assert(poll_group->group_size == 0);
+  CloseHandle(poll_group->afd_helper_handle);
+  queue_remove(&poll_group->queue_node);
+  free(poll_group);
+}
+
+poll_group_t* poll_group_from_queue_node(queue_node_t* queue_node) {
+  return container_of(queue_node, poll_group_t, queue_node);
+}
+
+HANDLE poll_group_get_afd_helper_handle(poll_group_t* poll_group) {
+  return poll_group->afd_helper_handle;
+}
+
+poll_group_t* poll_group_acquire(port_state_t* port_state) {
+  queue_t* queue = &port_state->poll_group_queue;
+  poll_group_t* poll_group =
+      !queue_empty(queue)
+          ? container_of(queue_last(queue), poll_group_t, queue_node)
+          : NULL;
+
+  if (poll_group == NULL ||
+      poll_group->group_size >= POLL_GROUP__MAX_GROUP_SIZE)
+    poll_group = poll_group__new(port_state);
+  if (poll_group == NULL)
+    return NULL;
+
+  if (++poll_group->group_size == POLL_GROUP__MAX_GROUP_SIZE)
+    queue_move_first(&port_state->poll_group_queue, &poll_group->queue_node);
+
+  return poll_group;
+}
+
+void poll_group_release(poll_group_t* poll_group) {
+  port_state_t* port_state = poll_group->port_state;
+
+  poll_group->group_size--;
+  assert(poll_group->group_size < POLL_GROUP__MAX_GROUP_SIZE);
+
+  queue_move_last(&port_state->poll_group_queue, &poll_group->queue_node);
+
+  /* Poll groups are currently only freed when the epoll port is closed. */
+}
+
+#define PORT__MAX_ON_STACK_COMPLETIONS 256
+
+static port_state_t* port__alloc(void) {
+  port_state_t* port_state = malloc(sizeof *port_state);
+  if (port_state == NULL)
+    return_set_error(NULL, ERROR_NOT_ENOUGH_MEMORY);
+
+  return port_state;
+}
+
+static void port__free(port_state_t* port) {
+  assert(port != NULL);
+  free(port);
+}
+
+static HANDLE port__create_iocp(void) {
+  HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
+  if (iocp == NULL)
+    return_map_error(NULL);
+
+  return iocp;
+}
+
+port_state_t* port_new(HANDLE* iocp_out) {
+  port_state_t* port_state;
+  HANDLE iocp;
+
+  port_state = port__alloc();
+  if (port_state == NULL)
+    goto err1;
+
+  iocp = port__create_iocp();
+  if (iocp == NULL)
+    goto err2;
+
+  memset(port_state, 0, sizeof *port_state);
+
+  port_state->iocp = iocp;
+  tree_init(&port_state->sock_tree);
+  queue_init(&port_state->sock_update_queue);
+  queue_init(&port_state->sock_deleted_queue);
+  queue_init(&port_state->poll_group_queue);
+  ts_tree_node_init(&port_state->handle_tree_node);
+  InitializeCriticalSection(&port_state->lock);
+
+  *iocp_out = iocp;
+  return port_state;
+
+err2:
+  port__free(port_state);
+err1:
+  return NULL;
+}
+
+static int port__close_iocp(port_state_t* port_state) {
+  HANDLE iocp = port_state->iocp;
+  port_state->iocp = NULL;
+
+  if (!CloseHandle(iocp))
+    return_map_error(-1);
+
+  return 0;
+}
+
+int port_close(port_state_t* port_state) {
+  int result;
+
+  EnterCriticalSection(&port_state->lock);
+  result = port__close_iocp(port_state);
+  LeaveCriticalSection(&port_state->lock);
+
+  return result;
+}
+
+int port_delete(port_state_t* port_state) {
+  tree_node_t* tree_node;
+  queue_node_t* queue_node;
+
+  /* At this point the IOCP port should have been closed. */
+  assert(port_state->iocp == NULL);
+
+  while ((tree_node = tree_root(&port_state->sock_tree)) != NULL) {
+    sock_state_t* sock_state = sock_state_from_tree_node(tree_node);
+    sock_force_delete(port_state, sock_state);
+  }
+
+  while ((queue_node = queue_first(&port_state->sock_deleted_queue)) != NULL) {
+    sock_state_t* sock_state = sock_state_from_queue_node(queue_node);
+    sock_force_delete(port_state, sock_state);
+  }
+
+  while ((queue_node = queue_first(&port_state->poll_group_queue)) != NULL) {
+    poll_group_t* poll_group = poll_group_from_queue_node(queue_node);
+    poll_group_delete(poll_group);
+  }
+
+  assert(queue_empty(&port_state->sock_update_queue));
+
+  DeleteCriticalSection(&port_state->lock);
+
+  port__free(port_state);
+
+  return 0;
+}
+
+static int port__update_events(port_state_t* port_state) {
+  queue_t* sock_update_queue = &port_state->sock_update_queue;
+
+  /* Walk the queue, submitting new poll requests for every socket that needs
+   * it. */
+  while (!queue_empty(sock_update_queue)) {
+    queue_node_t* queue_node = queue_first(sock_update_queue);
+    sock_state_t* sock_state = sock_state_from_queue_node(queue_node);
+
+    if (sock_update(port_state, sock_state) < 0)
+      return -1;
+
+    /* sock_update() removes the socket from the update queue. */
+  }
+
+  return 0;
+}
+
+static void port__update_events_if_polling(port_state_t* port_state) {
+  if (port_state->active_poll_count > 0)
+    port__update_events(port_state);
+}
+
+static int port__feed_events(port_state_t* port_state,
+                             struct epoll_event* epoll_events,
+                             OVERLAPPED_ENTRY* iocp_events,
+                             DWORD iocp_event_count) {
+  int epoll_event_count = 0;
+  DWORD i;
+
+  for (i = 0; i < iocp_event_count; i++) {
+    OVERLAPPED* overlapped = iocp_events[i].lpOverlapped;
+    struct epoll_event* ev = &epoll_events[epoll_event_count];
+
+    epoll_event_count += sock_feed_event(port_state, overlapped, ev);
+  }
+
+  return epoll_event_count;
+}
+
+static int port__poll(port_state_t* port_state,
+                      struct epoll_event* epoll_events,
+                      OVERLAPPED_ENTRY* iocp_events,
+                      DWORD maxevents,
+                      DWORD timeout) {
+  DWORD completion_count;
+
+  if (port__update_events(port_state) < 0)
+    return -1;
+
+  port_state->active_poll_count++;
+
+  LeaveCriticalSection(&port_state->lock);
+
+  BOOL r = GetQueuedCompletionStatusEx(port_state->iocp,
+                                       iocp_events,
+                                       maxevents,
+                                       &completion_count,
+                                       timeout,
+                                       FALSE);
+
+  EnterCriticalSection(&port_state->lock);
+
+  port_state->active_poll_count--;
+
+  if (!r)
+    return_map_error(-1);
+
+  return port__feed_events(
+      port_state, epoll_events, iocp_events, completion_count);
+}
+
+int port_wait(port_state_t* port_state,
+              struct epoll_event* events,
+              int maxevents,
+              int timeout) {
+  OVERLAPPED_ENTRY stack_iocp_events[PORT__MAX_ON_STACK_COMPLETIONS];
+  OVERLAPPED_ENTRY* iocp_events;
+  uint64_t due = 0;
+  DWORD gqcs_timeout;
+  int result;
+
+  /* Check whether `maxevents` is in range. */
+  if (maxevents <= 0)
+    return_set_error(-1, ERROR_INVALID_PARAMETER);
+
+  /* Decide whether the IOCP completion list can live on the stack, or allocate
+   * memory for it on the heap. */
+  if ((size_t) maxevents <= array_count(stack_iocp_events)) {
+    iocp_events = stack_iocp_events;
+  } else if ((iocp_events =
+                  malloc((size_t) maxevents * sizeof *iocp_events)) == NULL) {
+    iocp_events = stack_iocp_events;
+    maxevents = array_count(stack_iocp_events);
+  }
+
+  /* Compute the timeout for GetQueuedCompletionStatus, and the wait end
+   * time, if the user specified a timeout other than zero or infinite. */
+  if (timeout > 0) {
+    due = GetTickCount64() + (uint64_t) timeout;
+    gqcs_timeout = (DWORD) timeout;
+  } else if (timeout == 0) {
+    gqcs_timeout = 0;
+  } else {
+    gqcs_timeout = INFINITE;
+  }
+
+  EnterCriticalSection(&port_state->lock);
+
+  /* Dequeue completion packets until either at least one interesting event
+   * has been discovered, or the timeout is reached. */
+  for (;;) {
+    uint64_t now;
+
+    result = port__poll(
+        port_state, events, iocp_events, (DWORD) maxevents, gqcs_timeout);
+    if (result < 0 || result > 0)
+      break; /* Result, error, or time-out. */
+
+    if (timeout < 0)
+      continue; /* When timeout is negative, never time out. */
+
+    /* Update time. */
+    now = GetTickCount64();
+
+    /* Do not allow the due time to be in the past. */
+    if (now >= due) {
+      SetLastError(WAIT_TIMEOUT);
+      break;
+    }
+
+    /* Recompute time-out argument for GetQueuedCompletionStatus. */
+    gqcs_timeout = (DWORD)(due - now);
+  }
+
+  port__update_events_if_polling(port_state);
+
+  LeaveCriticalSection(&port_state->lock);
+
+  if (iocp_events != stack_iocp_events)
+    free(iocp_events);
+
+  if (result >= 0)
+    return result;
+  else if (GetLastError() == WAIT_TIMEOUT)
+    return 0;
+  else
+    return -1;
+}
+
+static int port__ctl_add(port_state_t* port_state,
+                         SOCKET sock,
+                         struct epoll_event* ev) {
+  sock_state_t* sock_state = sock_new(port_state, sock);
+  if (sock_state == NULL)
+    return -1;
+
+  if (sock_set_event(port_state, sock_state, ev) < 0) {
+    sock_delete(port_state, sock_state);
+    return -1;
+  }
+
+  port__update_events_if_polling(port_state);
+
+  return 0;
+}
+
+static int port__ctl_mod(port_state_t* port_state,
+                         SOCKET sock,
+                         struct epoll_event* ev) {
+  sock_state_t* sock_state = port_find_socket(port_state, sock);
+  if (sock_state == NULL)
+    return -1;
+
+  if (sock_set_event(port_state, sock_state, ev) < 0)
+    return -1;
+
+  port__update_events_if_polling(port_state);
+
+  return 0;
+}
+
+static int port__ctl_del(port_state_t* port_state, SOCKET sock) {
+  sock_state_t* sock_state = port_find_socket(port_state, sock);
+  if (sock_state == NULL)
+    return -1;
+
+  sock_delete(port_state, sock_state);
+
+  return 0;
+}
+
+static int port__ctl_op(port_state_t* port_state,
+                        int op,
+                        SOCKET sock,
+                        struct epoll_event* ev) {
+  switch (op) {
+    case EPOLL_CTL_ADD:
+      return port__ctl_add(port_state, sock, ev);
+    case EPOLL_CTL_MOD:
+      return port__ctl_mod(port_state, sock, ev);
+    case EPOLL_CTL_DEL:
+      return port__ctl_del(port_state, sock);
+    default:
+      return_set_error(-1, ERROR_INVALID_PARAMETER);
+  }
+}
+
+int port_ctl(port_state_t* port_state,
+             int op,
+             SOCKET sock,
+             struct epoll_event* ev) {
+  int result;
+
+  EnterCriticalSection(&port_state->lock);
+  result = port__ctl_op(port_state, op, sock, ev);
+  LeaveCriticalSection(&port_state->lock);
+
+  return result;
+}
+
+int port_register_socket_handle(port_state_t* port_state,
+                                sock_state_t* sock_state,
+                                SOCKET socket) {
+  if (tree_add(&port_state->sock_tree,
+               sock_state_to_tree_node(sock_state),
+               socket) < 0)
+    return_set_error(-1, ERROR_ALREADY_EXISTS);
+  return 0;
+}
+
+void port_unregister_socket_handle(port_state_t* port_state,
+                                   sock_state_t* sock_state) {
+  tree_del(&port_state->sock_tree, sock_state_to_tree_node(sock_state));
+}
+
+sock_state_t* port_find_socket(port_state_t* port_state, SOCKET socket) {
+  tree_node_t* tree_node = tree_find(&port_state->sock_tree, socket);
+  if (tree_node == NULL)
+    return_set_error(NULL, ERROR_NOT_FOUND);
+  return sock_state_from_tree_node(tree_node);
+}
+
+void port_request_socket_update(port_state_t* port_state,
+                                sock_state_t* sock_state) {
+  if (queue_enqueued(sock_state_to_queue_node(sock_state)))
+    return;
+  queue_append(&port_state->sock_update_queue,
+               sock_state_to_queue_node(sock_state));
+}
+
+void port_cancel_socket_update(port_state_t* port_state,
+                               sock_state_t* sock_state) {
+  unused_var(port_state);
+  if (!queue_enqueued(sock_state_to_queue_node(sock_state)))
+    return;
+  queue_remove(sock_state_to_queue_node(sock_state));
+}
+
+void port_add_deleted_socket(port_state_t* port_state,
+                             sock_state_t* sock_state) {
+  if (queue_enqueued(sock_state_to_queue_node(sock_state)))
+    return;
+  queue_append(&port_state->sock_deleted_queue,
+               sock_state_to_queue_node(sock_state));
+}
+
+void port_remove_deleted_socket(port_state_t* port_state,
+                                sock_state_t* sock_state) {
+  unused_var(port_state);
+  if (!queue_enqueued(sock_state_to_queue_node(sock_state)))
+    return;
+  queue_remove(sock_state_to_queue_node(sock_state));
+}
+
+void queue_init(queue_t* queue) {
+  queue_node_init(&queue->head);
+}
+
+void queue_node_init(queue_node_t* node) {
+  node->prev = node;
+  node->next = node;
+}
+
+static inline void queue__detach_node(queue_node_t* node) {
+  node->prev->next = node->next;
+  node->next->prev = node->prev;
+}
+
+queue_node_t* queue_first(const queue_t* queue) {
+  return !queue_empty(queue) ? queue->head.next : NULL;
+}
+
+queue_node_t* queue_last(const queue_t* queue) {
+  return !queue_empty(queue) ? queue->head.prev : NULL;
+}
+
+void queue_prepend(queue_t* queue, queue_node_t* node) {
+  node->next = queue->head.next;
+  node->prev = &queue->head;
+  node->next->prev = node;
+  queue->head.next = node;
+}
+
+void queue_append(queue_t* queue, queue_node_t* node) {
+  node->next = &queue->head;
+  node->prev = queue->head.prev;
+  node->prev->next = node;
+  queue->head.prev = node;
+}
+
+void queue_move_first(queue_t* queue, queue_node_t* node) {
+  queue__detach_node(node);
+  queue_prepend(queue, node);
+}
+
+void queue_move_last(queue_t* queue, queue_node_t* node) {
+  queue__detach_node(node);
+  queue_append(queue, node);
+}
+
+void queue_remove(queue_node_t* node) {
+  queue__detach_node(node);
+  queue_node_init(node);
+}
+
+bool queue_empty(const queue_t* queue) {
+  return !queue_enqueued(&queue->head);
+}
+
+bool queue_enqueued(const queue_node_t* node) {
+  return node->prev != node;
+}
+
+static const long REFLOCK__REF          = (long) 0x00000001;
+static const long REFLOCK__REF_MASK     = (long) 0x0fffffff;
+static const long REFLOCK__DESTROY      = (long) 0x10000000;
+static const long REFLOCK__DESTROY_MASK = (long) 0xf0000000;
+static const long REFLOCK__POISON       = (long) 0x300dead0;
+
+static HANDLE reflock__keyed_event = NULL;
+
+int reflock_global_init(void) {
+  NTSTATUS status =
+      NtCreateKeyedEvent(&reflock__keyed_event, KEYEDEVENT_ALL_ACCESS, NULL, 0);
+  if (status != STATUS_SUCCESS)
+    return_set_error(-1, RtlNtStatusToDosError(status));
+  return 0;
+}
+
+void reflock_init(reflock_t* reflock) {
+  reflock->state = 0;
+}
+
+static void reflock__signal_event(void* address) {
+  NTSTATUS status =
+      NtReleaseKeyedEvent(reflock__keyed_event, address, FALSE, NULL);
+  if (status != STATUS_SUCCESS)
+    abort();
+}
+
+static void reflock__await_event(void* address) {
+  NTSTATUS status =
+      NtWaitForKeyedEvent(reflock__keyed_event, address, FALSE, NULL);
+  if (status != STATUS_SUCCESS)
+    abort();
+}
+
+void reflock_ref(reflock_t* reflock) {
+  long state = InterlockedAdd(&reflock->state, REFLOCK__REF);
+
+  /* Verify that the counter didn't overflow and the lock isn't destroyed. */
+  assert((state & REFLOCK__DESTROY_MASK) == 0);
+  unused_var(state);
+}
+
+void reflock_unref(reflock_t* reflock) {
+  long state = InterlockedAdd(&reflock->state, -REFLOCK__REF);
+
+  /* Verify that the lock was referenced and not already destroyed. */
+  assert((state & REFLOCK__DESTROY_MASK & ~REFLOCK__DESTROY) == 0);
+
+  if (state == REFLOCK__DESTROY)
+    reflock__signal_event(reflock);
+}
+
+void reflock_unref_and_destroy(reflock_t* reflock) {
+  long state =
+      InterlockedAdd(&reflock->state, REFLOCK__DESTROY - REFLOCK__REF);
+  long ref_count = state & REFLOCK__REF_MASK;
+
+  /* Verify that the lock was referenced and not already destroyed. */
+  assert((state & REFLOCK__DESTROY_MASK) == REFLOCK__DESTROY);
+
+  if (ref_count != 0)
+    reflock__await_event(reflock);
+
+  state = InterlockedExchange(&reflock->state, REFLOCK__POISON);
+  assert(state == REFLOCK__DESTROY);
+}
+
+static const uint32_t SOCK__KNOWN_EPOLL_EVENTS =
+    EPOLLIN | EPOLLPRI | EPOLLOUT | EPOLLERR | EPOLLHUP | EPOLLRDNORM |
+    EPOLLRDBAND | EPOLLWRNORM | EPOLLWRBAND | EPOLLMSG | EPOLLRDHUP;
+
+typedef enum sock__poll_status {
+  SOCK__POLL_IDLE = 0,
+  SOCK__POLL_PENDING,
+  SOCK__POLL_CANCELLED
+} sock__poll_status_t;
+
+typedef struct sock_state {
+  OVERLAPPED overlapped;
+  AFD_POLL_INFO poll_info;
+  queue_node_t queue_node;
+  tree_node_t tree_node;
+  poll_group_t* poll_group;
+  SOCKET base_socket;
+  epoll_data_t user_data;
+  uint32_t user_events;
+  uint32_t pending_events;
+  sock__poll_status_t poll_status;
+  bool delete_pending;
+} sock_state_t;
+
+static inline sock_state_t* sock__alloc(void) {
+  sock_state_t* sock_state = malloc(sizeof *sock_state);
+  if (sock_state == NULL)
+    return_set_error(NULL, ERROR_NOT_ENOUGH_MEMORY);
+  return sock_state;
+}
+
+static inline void sock__free(sock_state_t* sock_state) {
+  free(sock_state);
+}
+
+static int sock__cancel_poll(sock_state_t* sock_state) {
+  HANDLE afd_helper_handle =
+      poll_group_get_afd_helper_handle(sock_state->poll_group);
+  assert(sock_state->poll_status == SOCK__POLL_PENDING);
+
+  /* CancelIoEx() may fail with ERROR_NOT_FOUND if the overlapped operation has
+   * already completed. This is not a problem and we proceed normally. */
+  if (!HasOverlappedIoCompleted(&sock_state->overlapped) &&
+      !CancelIoEx(afd_helper_handle, &sock_state->overlapped) &&
+      GetLastError() != ERROR_NOT_FOUND)
+    return_map_error(-1);
+
+  sock_state->poll_status = SOCK__POLL_CANCELLED;
+  sock_state->pending_events = 0;
+  return 0;
+}
+
+sock_state_t* sock_new(port_state_t* port_state, SOCKET socket) {
+  SOCKET base_socket;
+  poll_group_t* poll_group;
+  sock_state_t* sock_state;
+
+  if (socket == 0 || socket == INVALID_SOCKET)
+    return_set_error(NULL, ERROR_INVALID_HANDLE);
+
+  base_socket = ws_get_base_socket(socket);
+  if (base_socket == INVALID_SOCKET)
+    return NULL;
+
+  poll_group = poll_group_acquire(port_state);
+  if (poll_group == NULL)
+    return NULL;
+
+  sock_state = sock__alloc();
+  if (sock_state == NULL)
+    goto err1;
+
+  memset(sock_state, 0, sizeof *sock_state);
+
+  sock_state->base_socket = base_socket;
+  sock_state->poll_group = poll_group;
+
+  tree_node_init(&sock_state->tree_node);
+  queue_node_init(&sock_state->queue_node);
+
+  if (port_register_socket_handle(port_state, sock_state, socket) < 0)
+    goto err2;
+
+  return sock_state;
+
+err2:
+  sock__free(sock_state);
+err1:
+  poll_group_release(poll_group);
+
+  return NULL;
+}
+
+static int sock__delete(port_state_t* port_state,
+                        sock_state_t* sock_state,
+                        bool force) {
+  if (!sock_state->delete_pending) {
+    if (sock_state->poll_status == SOCK__POLL_PENDING)
+      sock__cancel_poll(sock_state);
+
+    port_cancel_socket_update(port_state, sock_state);
+    port_unregister_socket_handle(port_state, sock_state);
+
+    sock_state->delete_pending = true;
+  }
+
+  /* If the poll request still needs to complete, the sock_state object can't
+   * be free()d yet. `sock_feed_event()` or `port_close()` will take care
+   * of this later. */
+  if (force || sock_state->poll_status == SOCK__POLL_IDLE) {
+    /* Free the sock_state now. */
+    port_remove_deleted_socket(port_state, sock_state);
+    poll_group_release(sock_state->poll_group);
+    sock__free(sock_state);
+  } else {
+    /* Free the socket later. */
+    port_add_deleted_socket(port_state, sock_state);
+  }
+
+  return 0;
+}
+
+void sock_delete(port_state_t* port_state, sock_state_t* sock_state) {
+  sock__delete(port_state, sock_state, false);
+}
+
+void sock_force_delete(port_state_t* port_state, sock_state_t* sock_state) {
+  sock__delete(port_state, sock_state, true);
+}
+
+int sock_set_event(port_state_t* port_state,
+                   sock_state_t* sock_state,
+                   const struct epoll_event* ev) {
+  /* EPOLLERR and EPOLLHUP are always reported, even when not requested by the
+   * caller. However they are disabled after a event has been reported for a
+   * socket for which the EPOLLONESHOT flag as set. */
+  uint32_t events = ev->events | EPOLLERR | EPOLLHUP;
+
+  sock_state->user_events = events;
+  sock_state->user_data = ev->data;
+
+  if ((events & SOCK__KNOWN_EPOLL_EVENTS & ~sock_state->pending_events) != 0)
+    port_request_socket_update(port_state, sock_state);
+
+  return 0;
+}
+
+static inline DWORD sock__epoll_events_to_afd_events(uint32_t epoll_events) {
+  /* Always monitor for AFD_POLL_LOCAL_CLOSE, which is triggered when the
+   * socket is closed with closesocket() or CloseHandle(). */
+  DWORD afd_events = AFD_POLL_LOCAL_CLOSE;
+
+  if (epoll_events & (EPOLLIN | EPOLLRDNORM))
+    afd_events |= AFD_POLL_RECEIVE | AFD_POLL_ACCEPT;
+  if (epoll_events & (EPOLLPRI | EPOLLRDBAND))
+    afd_events |= AFD_POLL_RECEIVE_EXPEDITED;
+  if (epoll_events & (EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND))
+    afd_events |= AFD_POLL_SEND;
+  if (epoll_events & (EPOLLIN | EPOLLRDNORM | EPOLLRDHUP))
+    afd_events |= AFD_POLL_DISCONNECT;
+  if (epoll_events & EPOLLHUP)
+    afd_events |= AFD_POLL_ABORT;
+  if (epoll_events & EPOLLERR)
+    afd_events |= AFD_POLL_CONNECT_FAIL;
+
+  return afd_events;
+}
+
+static inline uint32_t sock__afd_events_to_epoll_events(DWORD afd_events) {
+  uint32_t epoll_events = 0;
+
+  if (afd_events & (AFD_POLL_RECEIVE | AFD_POLL_ACCEPT))
+    epoll_events |= EPOLLIN | EPOLLRDNORM;
+  if (afd_events & AFD_POLL_RECEIVE_EXPEDITED)
+    epoll_events |= EPOLLPRI | EPOLLRDBAND;
+  if (afd_events & AFD_POLL_SEND)
+    epoll_events |= EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND;
+  if (afd_events & AFD_POLL_DISCONNECT)
+    epoll_events |= EPOLLIN | EPOLLRDNORM | EPOLLRDHUP;
+  if (afd_events & AFD_POLL_ABORT)
+    epoll_events |= EPOLLHUP;
+  if (afd_events & AFD_POLL_CONNECT_FAIL)
+    /* Linux reports all these events after connect() has failed. */
+    epoll_events |=
+        EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLRDNORM | EPOLLWRNORM | EPOLLRDHUP;
+
+  return epoll_events;
+}
+
+int sock_update(port_state_t* port_state, sock_state_t* sock_state) {
+  assert(!sock_state->delete_pending);
+
+  if ((sock_state->poll_status == SOCK__POLL_PENDING) &&
+      (sock_state->user_events & SOCK__KNOWN_EPOLL_EVENTS &
+       ~sock_state->pending_events) == 0) {
+    /* All the events the user is interested in are already being monitored by
+     * the pending poll operation. It might spuriously complete because of an
+     * event that we're no longer interested in; when that happens we'll submit
+     * a new poll operation with the updated event mask. */
+
+  } else if (sock_state->poll_status == SOCK__POLL_PENDING) {
+    /* A poll operation is already pending, but it's not monitoring for all the
+     * events that the user is interested in. Therefore, cancel the pending
+     * poll operation; when we receive it's completion package, a new poll
+     * operation will be submitted with the correct event mask. */
+    if (sock__cancel_poll(sock_state) < 0)
+      return -1;
+
+  } else if (sock_state->poll_status == SOCK__POLL_CANCELLED) {
+    /* The poll operation has already been cancelled, we're still waiting for
+     * it to return. For now, there's nothing that needs to be done. */
+
+  } else if (sock_state->poll_status == SOCK__POLL_IDLE) {
+    /* No poll operation is pending; start one. */
+    sock_state->poll_info.Exclusive = FALSE;
+    sock_state->poll_info.NumberOfHandles = 1;
+    sock_state->poll_info.Timeout.QuadPart = INT64_MAX;
+    sock_state->poll_info.Handles[0].Handle = (HANDLE) sock_state->base_socket;
+    sock_state->poll_info.Handles[0].Status = 0;
+    sock_state->poll_info.Handles[0].Events =
+        sock__epoll_events_to_afd_events(sock_state->user_events);
+
+    memset(&sock_state->overlapped, 0, sizeof sock_state->overlapped);
+
+    if (afd_poll(poll_group_get_afd_helper_handle(sock_state->poll_group),
+                 &sock_state->poll_info,
+                 &sock_state->overlapped) < 0) {
+      switch (GetLastError()) {
+        case ERROR_IO_PENDING:
+          /* Overlapped poll operation in progress; this is expected. */
+          break;
+        case ERROR_INVALID_HANDLE:
+          /* Socket closed; it'll be dropped from the epoll set. */
+          return sock__delete(port_state, sock_state, false);
+        default:
+          /* Other errors are propagated to the caller. */
+          return_map_error(-1);
+      }
+    }
+
+    /* The poll request was successfully submitted. */
+    sock_state->poll_status = SOCK__POLL_PENDING;
+    sock_state->pending_events = sock_state->user_events;
+
+  } else {
+    /* Unreachable. */
+    assert(false);
+  }
+
+  port_cancel_socket_update(port_state, sock_state);
+  return 0;
+}
+
+int sock_feed_event(port_state_t* port_state,
+                    OVERLAPPED* overlapped,
+                    struct epoll_event* ev) {
+  sock_state_t* sock_state =
+      container_of(overlapped, sock_state_t, overlapped);
+  AFD_POLL_INFO* poll_info = &sock_state->poll_info;
+  uint32_t epoll_events = 0;
+
+  sock_state->poll_status = SOCK__POLL_IDLE;
+  sock_state->pending_events = 0;
+
+  if (sock_state->delete_pending) {
+    /* Socket has been deleted earlier and can now be freed. */
+    return sock__delete(port_state, sock_state, false);
+
+  } else if ((NTSTATUS) overlapped->Internal == STATUS_CANCELLED) {
+    /* The poll request was cancelled by CancelIoEx. */
+
+  } else if (!NT_SUCCESS(overlapped->Internal)) {
+    /* The overlapped request itself failed in an unexpected way. */
+    epoll_events = EPOLLERR;
+
+  } else if (poll_info->NumberOfHandles < 1) {
+    /* This poll operation succeeded but didn't report any socket events. */
+
+  } else if (poll_info->Handles[0].Events & AFD_POLL_LOCAL_CLOSE) {
+    /* The poll operation reported that the socket was closed. */
+    return sock__delete(port_state, sock_state, false);
+
+  } else {
+    /* Events related to our socket were reported. */
+    epoll_events =
+        sock__afd_events_to_epoll_events(poll_info->Handles[0].Events);
+  }
+
+  /* Requeue the socket so a new poll request will be submitted. */
+  port_request_socket_update(port_state, sock_state);
+
+  /* Filter out events that the user didn't ask for. */
+  epoll_events &= sock_state->user_events;
+
+  /* Return if there are no epoll events to report. */
+  if (epoll_events == 0)
+    return 0;
+
+  /* If the the socket has the EPOLLONESHOT flag set, unmonitor all events,
+   * even EPOLLERR and EPOLLHUP. But always keep looking for closed sockets. */
+  if (sock_state->user_events & EPOLLONESHOT)
+    sock_state->user_events = 0;
+
+  ev->data = sock_state->user_data;
+  ev->events = epoll_events;
+  return 1;
+}
+
+queue_node_t* sock_state_to_queue_node(sock_state_t* sock_state) {
+  return &sock_state->queue_node;
+}
+
+sock_state_t* sock_state_from_tree_node(tree_node_t* tree_node) {
+  return container_of(tree_node, sock_state_t, tree_node);
+}
+
+tree_node_t* sock_state_to_tree_node(sock_state_t* sock_state) {
+  return &sock_state->tree_node;
+}
+
+sock_state_t* sock_state_from_queue_node(queue_node_t* queue_node) {
+  return container_of(queue_node, sock_state_t, queue_node);
+}
+
+void ts_tree_init(ts_tree_t* ts_tree) {
+  tree_init(&ts_tree->tree);
+  InitializeSRWLock(&ts_tree->lock);
+}
+
+void ts_tree_node_init(ts_tree_node_t* node) {
+  tree_node_init(&node->tree_node);
+  reflock_init(&node->reflock);
+}
+
+int ts_tree_add(ts_tree_t* ts_tree, ts_tree_node_t* node, uintptr_t key) {
+  int r;
+
+  AcquireSRWLockExclusive(&ts_tree->lock);
+  r = tree_add(&ts_tree->tree, &node->tree_node, key);
+  ReleaseSRWLockExclusive(&ts_tree->lock);
+
+  return r;
+}
+
+static inline ts_tree_node_t* ts_tree__find_node(ts_tree_t* ts_tree,
+                                                 uintptr_t key) {
+  tree_node_t* tree_node = tree_find(&ts_tree->tree, key);
+  if (tree_node == NULL)
+    return NULL;
+
+  return container_of(tree_node, ts_tree_node_t, tree_node);
+}
+
+ts_tree_node_t* ts_tree_del_and_ref(ts_tree_t* ts_tree, uintptr_t key) {
+  ts_tree_node_t* ts_tree_node;
+
+  AcquireSRWLockExclusive(&ts_tree->lock);
+
+  ts_tree_node = ts_tree__find_node(ts_tree, key);
+  if (ts_tree_node != NULL) {
+    tree_del(&ts_tree->tree, &ts_tree_node->tree_node);
+    reflock_ref(&ts_tree_node->reflock);
+  }
+
+  ReleaseSRWLockExclusive(&ts_tree->lock);
+
+  return ts_tree_node;
+}
+
+ts_tree_node_t* ts_tree_find_and_ref(ts_tree_t* ts_tree, uintptr_t key) {
+  ts_tree_node_t* ts_tree_node;
+
+  AcquireSRWLockShared(&ts_tree->lock);
+
+  ts_tree_node = ts_tree__find_node(ts_tree, key);
+  if (ts_tree_node != NULL)
+    reflock_ref(&ts_tree_node->reflock);
+
+  ReleaseSRWLockShared(&ts_tree->lock);
+
+  return ts_tree_node;
+}
+
+void ts_tree_node_unref(ts_tree_node_t* node) {
+  reflock_unref(&node->reflock);
+}
+
+void ts_tree_node_unref_and_destroy(ts_tree_node_t* node) {
+  reflock_unref_and_destroy(&node->reflock);
+}
+
+void tree_init(tree_t* tree) {
+  memset(tree, 0, sizeof *tree);
+}
+
+void tree_node_init(tree_node_t* node) {
+  memset(node, 0, sizeof *node);
+}
+
+#define TREE__ROTATE(cis, trans)   \
+  tree_node_t* p = node;           \
+  tree_node_t* q = node->trans;    \
+  tree_node_t* parent = p->parent; \
+                                   \
+  if (parent) {                    \
+    if (parent->left == p)         \
+      parent->left = q;            \
+    else                           \
+      parent->right = q;           \
+  } else {                         \
+    tree->root = q;                \
+  }                                \
+                                   \
+  q->parent = parent;              \
+  p->parent = q;                   \
+  p->trans = q->cis;               \
+  if (p->trans)                    \
+    p->trans->parent = p;          \
+  q->cis = p;
+
+static inline void tree__rotate_left(tree_t* tree, tree_node_t* node) {
+  TREE__ROTATE(left, right)
+}
+
+static inline void tree__rotate_right(tree_t* tree, tree_node_t* node) {
+  TREE__ROTATE(right, left)
+}
+
+#define TREE__INSERT_OR_DESCEND(side) \
+  if (parent->side) {                 \
+    parent = parent->side;            \
+  } else {                            \
+    parent->side = node;              \
+    break;                            \
+  }
+
+#define TREE__FIXUP_AFTER_INSERT(cis, trans) \
+  tree_node_t* grandparent = parent->parent; \
+  tree_node_t* uncle = grandparent->trans;   \
+                                             \
+  if (uncle && uncle->red) {                 \
+    parent->red = uncle->red = false;        \
+    grandparent->red = true;                 \
+    node = grandparent;                      \
+  } else {                                   \
+    if (node == parent->trans) {             \
+      tree__rotate_##cis(tree, parent);      \
+      node = parent;                         \
+      parent = node->parent;                 \
+    }                                        \
+    parent->red = false;                     \
+    grandparent->red = true;                 \
+    tree__rotate_##trans(tree, grandparent); \
+  }
+
+int tree_add(tree_t* tree, tree_node_t* node, uintptr_t key) {
+  tree_node_t* parent;
+
+  parent = tree->root;
+  if (parent) {
+    for (;;) {
+      if (key < parent->key) {
+        TREE__INSERT_OR_DESCEND(left)
+      } else if (key > parent->key) {
+        TREE__INSERT_OR_DESCEND(right)
+      } else {
+        return -1;
+      }
+    }
+  } else {
+    tree->root = node;
+  }
+
+  node->key = key;
+  node->left = node->right = NULL;
+  node->parent = parent;
+  node->red = true;
+
+  for (; parent && parent->red; parent = node->parent) {
+    if (parent == parent->parent->left) {
+      TREE__FIXUP_AFTER_INSERT(left, right)
+    } else {
+      TREE__FIXUP_AFTER_INSERT(right, left)
+    }
+  }
+  tree->root->red = false;
+
+  return 0;
+}
+
+#define TREE__FIXUP_AFTER_REMOVE(cis, trans)       \
+  tree_node_t* sibling = parent->trans;            \
+                                                   \
+  if (sibling->red) {                              \
+    sibling->red = false;                          \
+    parent->red = true;                            \
+    tree__rotate_##cis(tree, parent);              \
+    sibling = parent->trans;                       \
+  }                                                \
+  if ((sibling->left && sibling->left->red) ||     \
+      (sibling->right && sibling->right->red)) {   \
+    if (!sibling->trans || !sibling->trans->red) { \
+      sibling->cis->red = false;                   \
+      sibling->red = true;                         \
+      tree__rotate_##trans(tree, sibling);         \
+      sibling = parent->trans;                     \
+    }                                              \
+    sibling->red = parent->red;                    \
+    parent->red = sibling->trans->red = false;     \
+    tree__rotate_##cis(tree, parent);              \
+    node = tree->root;                             \
+    break;                                         \
+  }                                                \
+  sibling->red = true;
+
+void tree_del(tree_t* tree, tree_node_t* node) {
+  tree_node_t* parent = node->parent;
+  tree_node_t* left = node->left;
+  tree_node_t* right = node->right;
+  tree_node_t* next;
+  bool red;
+
+  if (!left) {
+    next = right;
+  } else if (!right) {
+    next = left;
+  } else {
+    next = right;
+    while (next->left)
+      next = next->left;
+  }
+
+  if (parent) {
+    if (parent->left == node)
+      parent->left = next;
+    else
+      parent->right = next;
+  } else {
+    tree->root = next;
+  }
+
+  if (left && right) {
+    red = next->red;
+    next->red = node->red;
+    next->left = left;
+    left->parent = next;
+    if (next != right) {
+      parent = next->parent;
+      next->parent = node->parent;
+      node = next->right;
+      parent->left = node;
+      next->right = right;
+      right->parent = next;
+    } else {
+      next->parent = parent;
+      parent = next;
+      node = next->right;
+    }
+  } else {
+    red = node->red;
+    node = next;
+  }
+
+  if (node)
+    node->parent = parent;
+  if (red)
+    return;
+  if (node && node->red) {
+    node->red = false;
+    return;
+  }
+
+  do {
+    if (node == tree->root)
+      break;
+    if (node == parent->left) {
+      TREE__FIXUP_AFTER_REMOVE(left, right)
+    } else {
+      TREE__FIXUP_AFTER_REMOVE(right, left)
+    }
+    node = parent;
+    parent = parent->parent;
+  } while (!node->red);
+
+  if (node)
+    node->red = false;
+}
+
+tree_node_t* tree_find(const tree_t* tree, uintptr_t key) {
+  tree_node_t* node = tree->root;
+  while (node) {
+    if (key < node->key)
+      node = node->left;
+    else if (key > node->key)
+      node = node->right;
+    else
+      return node;
+  }
+  return NULL;
+}
+
+tree_node_t* tree_root(const tree_t* tree) {
+  return tree->root;
+}
+
+#ifndef SIO_BASE_HANDLE
+#define SIO_BASE_HANDLE 0x48000022
+#endif
+
+int ws_global_init(void) {
+  int r;
+  WSADATA wsa_data;
+
+  r = WSAStartup(MAKEWORD(2, 2), &wsa_data);
+  if (r != 0)
+    return_set_error(-1, (DWORD) r);
+
+  return 0;
+}
+
+SOCKET ws_get_base_socket(SOCKET socket) {
+  SOCKET base_socket;
+  DWORD bytes;
+
+  if (WSAIoctl(socket,
+               SIO_BASE_HANDLE,
+               NULL,
+               0,
+               &base_socket,
+               sizeof base_socket,
+               &bytes,
+               NULL,
+               NULL) == SOCKET_ERROR)
+    return_map_error(INVALID_SOCKET);
+
+  return base_socket;
+}
diff --git a/3rdparty/wepoll/include/wepoll.h b/sources/wepoll/wepoll.h
similarity index 52%
rename from 3rdparty/wepoll/include/wepoll.h
rename to sources/wepoll/wepoll.h
index 4cca4c2d0..eebde2111 100644
--- a/3rdparty/wepoll/include/wepoll.h
+++ b/sources/wepoll/wepoll.h
@@ -1,3 +1,34 @@
+/*
+ * wepoll - epoll for Windows
+ * https://github.com/piscisaureus/wepoll
+ *
+ * Copyright 2012-2019, Bert Belder <bertbelder@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
 #ifndef WEPOLL_H_
 #define WEPOLL_H_
 
@@ -7,8 +38,6 @@
 
 #include <stdint.h>
 
-/* clang-format off */
-
 enum EPOLL_EVENTS {
   EPOLLIN      = (int) (1U <<  0),
   EPOLLPRI     = (int) (1U <<  1),
@@ -41,8 +70,6 @@ enum EPOLL_EVENTS {
 #define EPOLL_CTL_MOD 2
 #define EPOLL_CTL_DEL 3
 
-/* clang-format on */
-
 typedef void* HANDLE;
 typedef uintptr_t SOCKET;
 
@@ -69,15 +96,15 @@ WEPOLL_EXPORT HANDLE epoll_create1(int flags);
 
 WEPOLL_EXPORT int epoll_close(HANDLE ephnd);
 
-WEPOLL_EXPORT int epoll_ctl( HANDLE ephnd,
+WEPOLL_EXPORT int epoll_ctl(HANDLE ephnd,
                             int op,
                             SOCKET sock,
-                            struct epoll_event* event );
+                            struct epoll_event* event);
 
-WEPOLL_EXPORT int epoll_wait( HANDLE ephnd,
+WEPOLL_EXPORT int epoll_wait(HANDLE ephnd,
                              struct epoll_event* events,
                              int maxevents,
-                             int timeout );
+                             int timeout);
 
 #ifdef __cplusplus
 } /* extern "C" */
-- 
GitLab