librats 1.0.2 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/README.md +145 -331
  2. package/binding.gyp +16 -3
  3. package/lib/index.d.ts +288 -696
  4. package/lib/index.js +407 -44
  5. package/native-src/3rdparty/android/ifaddrs-android.c +1 -0
  6. package/native-src/3rdparty/android/ifaddrs-android.h +1 -0
  7. package/native-src/CMakeLists.txt +404 -179
  8. package/native-src/LICENSE +1 -1
  9. package/native-src/src/librats/bindings/rats.cpp +762 -0
  10. package/native-src/src/librats/bindings/rats.h +380 -0
  11. package/native-src/src/librats/bittorrent/bencode.cpp +437 -0
  12. package/native-src/src/librats/bittorrent/bencode.h +176 -0
  13. package/native-src/src/librats/bittorrent/bitfield.cpp +97 -0
  14. package/native-src/src/librats/bittorrent/bitfield.h +76 -0
  15. package/native-src/src/librats/bittorrent/byte_io.h +58 -0
  16. package/native-src/src/librats/bittorrent/choker.cpp +25 -0
  17. package/native-src/src/librats/bittorrent/choker.h +46 -0
  18. package/native-src/src/librats/bittorrent/client.cpp +413 -0
  19. package/native-src/src/librats/bittorrent/client.h +227 -0
  20. package/native-src/src/librats/bittorrent/disk_io.cpp +209 -0
  21. package/native-src/src/librats/bittorrent/disk_io.h +150 -0
  22. package/native-src/src/librats/bittorrent/extensions.cpp +191 -0
  23. package/native-src/src/librats/bittorrent/extensions.h +94 -0
  24. package/native-src/src/librats/bittorrent/file_storage.cpp +77 -0
  25. package/native-src/src/librats/bittorrent/file_storage.h +79 -0
  26. package/native-src/src/librats/bittorrent/log.h +42 -0
  27. package/native-src/src/librats/bittorrent/magnet_uri.cpp +98 -0
  28. package/native-src/src/librats/bittorrent/magnet_uri.h +35 -0
  29. package/native-src/src/librats/bittorrent/peer_connection.cpp +502 -0
  30. package/native-src/src/librats/bittorrent/peer_connection.h +194 -0
  31. package/native-src/src/librats/bittorrent/peer_list.cpp +68 -0
  32. package/native-src/src/librats/bittorrent/peer_list.h +75 -0
  33. package/native-src/src/librats/bittorrent/piece_picker.cpp +352 -0
  34. package/native-src/src/librats/bittorrent/piece_picker.h +201 -0
  35. package/native-src/src/librats/bittorrent/reactor.cpp +97 -0
  36. package/native-src/src/librats/bittorrent/reactor.h +89 -0
  37. package/native-src/src/librats/bittorrent/resume_data.cpp +72 -0
  38. package/native-src/src/librats/bittorrent/resume_data.h +41 -0
  39. package/native-src/src/librats/bittorrent/store_buffer.cpp +48 -0
  40. package/native-src/src/librats/bittorrent/store_buffer.h +47 -0
  41. package/native-src/src/librats/bittorrent/torrent.cpp +870 -0
  42. package/native-src/src/librats/bittorrent/torrent.h +260 -0
  43. package/native-src/src/librats/bittorrent/torrent_creator.cpp +129 -0
  44. package/native-src/src/librats/bittorrent/torrent_creator.h +58 -0
  45. package/native-src/src/librats/bittorrent/torrent_info.cpp +314 -0
  46. package/native-src/src/librats/bittorrent/torrent_info.h +118 -0
  47. package/native-src/src/librats/bittorrent/tracker.cpp +374 -0
  48. package/native-src/src/librats/bittorrent/tracker.h +108 -0
  49. package/native-src/src/librats/bittorrent/types.cpp +206 -0
  50. package/native-src/src/librats/bittorrent/types.h +86 -0
  51. package/native-src/src/librats/core/address.cpp +35 -0
  52. package/native-src/src/librats/core/address.h +78 -0
  53. package/native-src/src/librats/core/bytes.h +69 -0
  54. package/native-src/src/librats/core/chained_send_buffer.cpp +172 -0
  55. package/native-src/src/librats/core/chained_send_buffer.h +183 -0
  56. package/native-src/src/librats/core/endpoint_parse.cpp +41 -0
  57. package/native-src/src/librats/core/endpoint_parse.h +31 -0
  58. package/native-src/src/librats/core/event_bus.h +70 -0
  59. package/native-src/src/librats/core/host_endpoint.h +56 -0
  60. package/native-src/src/{io_poller.cpp → librats/core/io_poller.cpp} +520 -65
  61. package/native-src/src/{io_poller.h → librats/core/io_poller.h} +12 -6
  62. package/native-src/src/librats/core/ip_address.cpp +120 -0
  63. package/native-src/src/librats/core/ip_address.h +109 -0
  64. package/native-src/src/librats/core/mpsc_queue.h +47 -0
  65. package/native-src/src/librats/core/notifier.h +74 -0
  66. package/native-src/src/librats/core/receive_buffer.cpp +219 -0
  67. package/native-src/src/librats/core/receive_buffer.h +171 -0
  68. package/native-src/src/librats/core/service_registry.h +58 -0
  69. package/native-src/src/{socket.cpp → librats/core/socket.cpp} +625 -118
  70. package/native-src/src/librats/core/socket.h +496 -0
  71. package/native-src/src/librats/core/timer_queue.h +105 -0
  72. package/native-src/src/librats/core/types.cpp +43 -0
  73. package/native-src/src/librats/core/types.h +103 -0
  74. package/native-src/src/librats/core/wakeup_pipe.h +83 -0
  75. package/native-src/src/{crypto → librats/crypto}/blake2_endian.h +21 -23
  76. package/native-src/src/{crypto → librats/crypto}/blake2b.c +34 -33
  77. package/native-src/src/{crypto → librats/crypto}/blake2b.h +7 -6
  78. package/native-src/src/{crypto → librats/crypto}/blake2s.c +55 -54
  79. package/native-src/src/{crypto → librats/crypto}/blake2s.h +13 -12
  80. package/native-src/src/{crypto → librats/crypto}/chacha.c +22 -21
  81. package/native-src/src/{crypto → librats/crypto}/chacha.h +14 -13
  82. package/native-src/src/{crypto → librats/crypto}/chachapoly.c +56 -56
  83. package/native-src/src/{crypto → librats/crypto}/chachapoly.h +24 -17
  84. package/native-src/src/{crc32.cpp → librats/crypto/crc32.cpp} +1 -1
  85. package/native-src/src/{crc32.h → librats/crypto/crc32.h} +3 -1
  86. package/native-src/src/{crypto → librats/crypto}/curve25519.c +6 -4
  87. package/native-src/src/{crypto → librats/crypto}/curve25519.h +6 -3
  88. package/native-src/src/librats/crypto/hkdf.c +266 -0
  89. package/native-src/src/{crypto → librats/crypto}/hkdf.h +19 -19
  90. package/native-src/src/{noise.cpp → librats/crypto/noise.cpp} +84 -73
  91. package/native-src/src/{noise.h → librats/crypto/noise.h} +18 -8
  92. package/native-src/src/{crypto → librats/crypto}/poly1305.c +47 -46
  93. package/native-src/src/librats/crypto/poly1305.h +37 -0
  94. package/native-src/src/{sha1.cpp → librats/crypto/sha1.cpp} +33 -1
  95. package/native-src/src/{sha1.h → librats/crypto/sha1.h} +14 -6
  96. package/native-src/src/{crypto → librats/crypto}/sha256.c +15 -14
  97. package/native-src/src/{crypto → librats/crypto}/sha256.h +8 -7
  98. package/native-src/src/{crypto → librats/crypto}/sha512.c +15 -14
  99. package/native-src/src/{crypto → librats/crypto}/sha512.h +8 -7
  100. package/native-src/src/librats/dht/announce.cpp +37 -0
  101. package/native-src/src/librats/dht/announce.h +41 -0
  102. package/native-src/src/librats/dht/bep42.cpp +109 -0
  103. package/native-src/src/librats/dht/bep42.h +48 -0
  104. package/native-src/src/librats/dht/dht.cpp +501 -0
  105. package/native-src/src/librats/dht/dht.h +119 -0
  106. package/native-src/src/librats/dht/dht_runner.cpp +103 -0
  107. package/native-src/src/librats/dht/dht_runner.h +71 -0
  108. package/native-src/src/librats/dht/dos_blocker.cpp +42 -0
  109. package/native-src/src/librats/dht/dos_blocker.h +47 -0
  110. package/native-src/src/librats/dht/find_peers.cpp +52 -0
  111. package/native-src/src/librats/dht/find_peers.h +73 -0
  112. package/native-src/src/librats/dht/id.h +167 -0
  113. package/native-src/src/{krpc.cpp → librats/dht/krpc.cpp} +32 -81
  114. package/native-src/src/{krpc.h → librats/dht/krpc.h} +19 -23
  115. package/native-src/src/librats/dht/log.h +38 -0
  116. package/native-src/src/librats/dht/node.cpp +473 -0
  117. package/native-src/src/librats/dht/node.h +164 -0
  118. package/native-src/src/librats/dht/node_entry.h +81 -0
  119. package/native-src/src/librats/dht/observer.h +72 -0
  120. package/native-src/src/librats/dht/persistence.cpp +90 -0
  121. package/native-src/src/librats/dht/persistence.h +32 -0
  122. package/native-src/src/librats/dht/routing_table.cpp +559 -0
  123. package/native-src/src/librats/dht/routing_table.h +185 -0
  124. package/native-src/src/librats/dht/rpc_manager.cpp +127 -0
  125. package/native-src/src/librats/dht/rpc_manager.h +77 -0
  126. package/native-src/src/librats/dht/storage.cpp +92 -0
  127. package/native-src/src/librats/dht/storage.h +74 -0
  128. package/native-src/src/librats/dht/transport.h +27 -0
  129. package/native-src/src/librats/dht/traversal.cpp +326 -0
  130. package/native-src/src/librats/dht/traversal.h +120 -0
  131. package/native-src/src/librats/dht/udp_transport.cpp +49 -0
  132. package/native-src/src/librats/dht/udp_transport.h +51 -0
  133. package/native-src/src/librats/mdns/log.h +22 -0
  134. package/native-src/src/{mdns.cpp → librats/mdns/mdns.cpp} +75 -40
  135. package/native-src/src/{mdns.h → librats/mdns/mdns.h} +9 -8
  136. package/native-src/src/{natpmp.cpp → librats/nat/natpmp.cpp} +12 -9
  137. package/native-src/src/{natpmp.h → librats/nat/natpmp.h} +3 -3
  138. package/native-src/src/{port_mapping.h → librats/nat/port_mapping.h} +3 -2
  139. package/native-src/src/{stun.cpp → librats/nat/stun.cpp} +4 -4
  140. package/native-src/src/{stun.h → librats/nat/stun.h} +1 -1
  141. package/native-src/src/{upnp.cpp → librats/nat/upnp.cpp} +6 -6
  142. package/native-src/src/{upnp.h → librats/nat/upnp.h} +2 -2
  143. package/native-src/src/librats/node/circuit_service.h +84 -0
  144. package/native-src/src/librats/node/config.h +110 -0
  145. package/native-src/src/librats/node/dial_service.h +54 -0
  146. package/native-src/src/librats/node/dialer.cpp +264 -0
  147. package/native-src/src/librats/node/dialer.h +188 -0
  148. package/native-src/src/librats/node/host_events.h +26 -0
  149. package/native-src/src/librats/node/identify.cpp +130 -0
  150. package/native-src/src/librats/node/identify.h +71 -0
  151. package/native-src/src/librats/node/nat_status.cpp +103 -0
  152. package/native-src/src/librats/node/nat_status.h +118 -0
  153. package/native-src/src/librats/node/node.cpp +865 -0
  154. package/native-src/src/librats/node/node.h +344 -0
  155. package/native-src/src/librats/node/node_context.h +33 -0
  156. package/native-src/src/librats/node/peer_network.h +91 -0
  157. package/native-src/src/librats/peer/peer.h +49 -0
  158. package/native-src/src/librats/peer/peer_book.cpp +181 -0
  159. package/native-src/src/librats/peer/peer_book.h +88 -0
  160. package/native-src/src/librats/peer/peer_id.cpp +72 -0
  161. package/native-src/src/librats/peer/peer_id.h +62 -0
  162. package/native-src/src/librats/peer/peer_info.h +37 -0
  163. package/native-src/src/librats/peer/peer_table.cpp +170 -0
  164. package/native-src/src/librats/peer/peer_table.h +148 -0
  165. package/native-src/src/librats/security/handshaker.h +66 -0
  166. package/native-src/src/librats/security/identity.h +43 -0
  167. package/native-src/src/librats/security/noise_security.cpp +122 -0
  168. package/native-src/src/librats/security/noise_security.h +37 -0
  169. package/native-src/src/librats/security/plaintext_security.h +106 -0
  170. package/native-src/src/librats/security/session.h +37 -0
  171. package/native-src/src/{storage.cpp → librats/storage/storage.cpp} +369 -522
  172. package/native-src/src/{storage.h → librats/storage/storage.h} +135 -299
  173. package/native-src/src/librats/subsystems/bittorrent.cpp +211 -0
  174. package/native-src/src/librats/subsystems/bittorrent.h +136 -0
  175. package/native-src/src/librats/subsystems/dht_discovery.cpp +202 -0
  176. package/native-src/src/librats/subsystems/dht_discovery.h +123 -0
  177. package/native-src/src/librats/subsystems/dht_service.h +36 -0
  178. package/native-src/src/librats/subsystems/file_transfer.cpp +972 -0
  179. package/native-src/src/librats/subsystems/file_transfer.h +367 -0
  180. package/native-src/src/librats/subsystems/hole_punch.cpp +605 -0
  181. package/native-src/src/librats/subsystems/hole_punch.h +290 -0
  182. package/native-src/src/librats/subsystems/hole_punch_service.h +38 -0
  183. package/native-src/src/librats/subsystems/mdns_discovery.cpp +66 -0
  184. package/native-src/src/librats/subsystems/mdns_discovery.h +55 -0
  185. package/native-src/src/librats/subsystems/message_json.cpp +112 -0
  186. package/native-src/src/librats/subsystems/message_json.h +88 -0
  187. package/native-src/src/librats/subsystems/peer_exchange.cpp +241 -0
  188. package/native-src/src/librats/subsystems/peer_exchange.h +136 -0
  189. package/native-src/src/librats/subsystems/ping_service.cpp +98 -0
  190. package/native-src/src/librats/subsystems/ping_service.h +66 -0
  191. package/native-src/src/librats/subsystems/port_mapping_service.cpp +192 -0
  192. package/native-src/src/librats/subsystems/port_mapping_service.h +84 -0
  193. package/native-src/src/librats/subsystems/pubsub.cpp +567 -0
  194. package/native-src/src/librats/subsystems/pubsub.h +175 -0
  195. package/native-src/src/librats/subsystems/reconnection.cpp +239 -0
  196. package/native-src/src/librats/subsystems/reconnection.h +126 -0
  197. package/native-src/src/librats/subsystems/relay.cpp +1142 -0
  198. package/native-src/src/librats/subsystems/relay.h +211 -0
  199. package/native-src/src/librats/subsystems/relay_service.h +46 -0
  200. package/native-src/src/librats/transport/connection.cpp +343 -0
  201. package/native-src/src/librats/transport/connection.h +283 -0
  202. package/native-src/src/librats/transport/link.h +96 -0
  203. package/native-src/src/librats/transport/reactor.cpp +588 -0
  204. package/native-src/src/librats/transport/reactor.h +262 -0
  205. package/native-src/src/librats/transport/reactor_pool.h +81 -0
  206. package/native-src/src/librats/transport/relay_link.cpp +208 -0
  207. package/native-src/src/librats/transport/relay_link.h +303 -0
  208. package/native-src/src/librats/transport/tcp_link.cpp +49 -0
  209. package/native-src/src/librats/transport/tcp_link.h +43 -0
  210. package/native-src/src/librats/transport/udp_mux.cpp +617 -0
  211. package/native-src/src/librats/transport/udp_mux.h +363 -0
  212. package/native-src/src/librats/transport/udp_packet.cpp +121 -0
  213. package/native-src/src/librats/transport/udp_packet.h +190 -0
  214. package/native-src/src/librats/transport/udp_stream.cpp +1194 -0
  215. package/native-src/src/librats/transport/udp_stream.h +614 -0
  216. package/native-src/src/librats/util/features.h.in +51 -0
  217. package/native-src/src/{fs.cpp → librats/util/fs.cpp} +51 -3
  218. package/native-src/src/librats/util/fs.h +136 -0
  219. package/native-src/src/librats/util/json.cpp +1002 -0
  220. package/native-src/src/librats/util/json.h +444 -0
  221. package/native-src/src/{logger.cpp → librats/util/logger.cpp} +1 -1
  222. package/native-src/src/{logger.h → librats/util/logger.h} +43 -31
  223. package/native-src/src/{network_monitor.cpp → librats/util/network_monitor.cpp} +12 -4
  224. package/native-src/src/{network_monitor.h → librats/util/network_monitor.h} +2 -1
  225. package/native-src/src/{network_utils.cpp → librats/util/network_utils.cpp} +38 -23
  226. package/native-src/src/{network_utils.h → librats/util/network_utils.h} +15 -8
  227. package/native-src/src/{os.cpp → librats/util/os.cpp} +48 -18
  228. package/native-src/src/librats/util/rats_export.h +69 -0
  229. package/native-src/src/{version.cpp → librats/util/version.cpp} +2 -2
  230. package/native-src/src/{version.h.in → librats/util/version.h.in} +1 -1
  231. package/native-src/src/librats/wire/frame.cpp +76 -0
  232. package/native-src/src/librats/wire/frame.h +111 -0
  233. package/native-src/src/librats/wire/message_router.cpp +45 -0
  234. package/native-src/src/librats/wire/message_router.h +47 -0
  235. package/package.json +5 -4
  236. package/scripts/build-librats.js +1 -0
  237. package/scripts/postinstall.js +3 -3
  238. package/scripts/prepare-package.js +4 -4
  239. package/scripts/verify-installation.js +63 -105
  240. package/src/librats_node.cpp +1067 -1323
  241. package/native-src/src/bencode.cpp +0 -485
  242. package/native-src/src/bencode.h +0 -145
  243. package/native-src/src/bittorrent.cpp +0 -14
  244. package/native-src/src/bittorrent.h +0 -74
  245. package/native-src/src/bt_bitfield.cpp +0 -372
  246. package/native-src/src/bt_bitfield.h +0 -316
  247. package/native-src/src/bt_choker.cpp +0 -228
  248. package/native-src/src/bt_choker.h +0 -147
  249. package/native-src/src/bt_client.cpp +0 -1047
  250. package/native-src/src/bt_client.h +0 -445
  251. package/native-src/src/bt_create_torrent.cpp +0 -677
  252. package/native-src/src/bt_create_torrent.h +0 -473
  253. package/native-src/src/bt_extension.cpp +0 -469
  254. package/native-src/src/bt_extension.h +0 -309
  255. package/native-src/src/bt_file_storage.cpp +0 -261
  256. package/native-src/src/bt_file_storage.h +0 -298
  257. package/native-src/src/bt_handshake.cpp +0 -134
  258. package/native-src/src/bt_handshake.h +0 -157
  259. package/native-src/src/bt_messages.cpp +0 -364
  260. package/native-src/src/bt_messages.h +0 -324
  261. package/native-src/src/bt_network.cpp +0 -1007
  262. package/native-src/src/bt_network.h +0 -417
  263. package/native-src/src/bt_peer_connection.cpp +0 -742
  264. package/native-src/src/bt_peer_connection.h +0 -592
  265. package/native-src/src/bt_piece_picker.cpp +0 -786
  266. package/native-src/src/bt_piece_picker.h +0 -473
  267. package/native-src/src/bt_resume_data.cpp +0 -410
  268. package/native-src/src/bt_resume_data.h +0 -249
  269. package/native-src/src/bt_torrent.cpp +0 -2120
  270. package/native-src/src/bt_torrent.h +0 -641
  271. package/native-src/src/bt_torrent_info.cpp +0 -659
  272. package/native-src/src/bt_torrent_info.h +0 -418
  273. package/native-src/src/bt_types.h +0 -621
  274. package/native-src/src/chained_send_buffer.cpp +0 -75
  275. package/native-src/src/chained_send_buffer.h +0 -137
  276. package/native-src/src/crypto/hkdf.c +0 -266
  277. package/native-src/src/crypto/poly1305.h +0 -36
  278. package/native-src/src/dht.cpp +0 -3311
  279. package/native-src/src/dht.h +0 -717
  280. package/native-src/src/disk_io.cpp +0 -632
  281. package/native-src/src/disk_io.h +0 -315
  282. package/native-src/src/file_transfer.cpp +0 -1415
  283. package/native-src/src/file_transfer.h +0 -286
  284. package/native-src/src/fs.h +0 -108
  285. package/native-src/src/gossipsub.cpp +0 -1139
  286. package/native-src/src/gossipsub.h +0 -403
  287. package/native-src/src/ice.cpp +0 -893
  288. package/native-src/src/ice.h +0 -559
  289. package/native-src/src/json.hpp +0 -25526
  290. package/native-src/src/librats.cpp +0 -2378
  291. package/native-src/src/librats.h +0 -2324
  292. package/native-src/src/librats_bittorrent.cpp +0 -601
  293. package/native-src/src/librats_c.cpp +0 -1557
  294. package/native-src/src/librats_c.h +0 -323
  295. package/native-src/src/librats_discovery.cpp +0 -402
  296. package/native-src/src/librats_encryption.cpp +0 -275
  297. package/native-src/src/librats_file_transfer.cpp +0 -144
  298. package/native-src/src/librats_gossipsub.cpp +0 -289
  299. package/native-src/src/librats_ice.cpp +0 -213
  300. package/native-src/src/librats_log_macros.h +0 -36
  301. package/native-src/src/librats_logging.cpp +0 -173
  302. package/native-src/src/librats_mdns.cpp +0 -166
  303. package/native-src/src/librats_persistence.cpp +0 -796
  304. package/native-src/src/librats_portmap.cpp +0 -419
  305. package/native-src/src/librats_reconnection.cpp +0 -218
  306. package/native-src/src/librats_statistic.cpp +0 -105
  307. package/native-src/src/librats_storage.cpp +0 -189
  308. package/native-src/src/rats_export.h +0 -17
  309. package/native-src/src/receive_buffer.cpp +0 -82
  310. package/native-src/src/receive_buffer.h +0 -127
  311. package/native-src/src/socket.h +0 -228
  312. package/native-src/src/threadmanager.cpp +0 -105
  313. package/native-src/src/threadmanager.h +0 -53
  314. package/native-src/src/tracker.cpp +0 -1264
  315. package/native-src/src/tracker.h +0 -319
  316. package/native-src/src/turn.cpp +0 -762
  317. package/native-src/src/turn.h +0 -460
  318. package/native-src/src/wakeup_pipe.h +0 -60
  319. /package/native-src/src/{os.h → librats/util/os.h} +0 -0
@@ -1,8 +1,9 @@
1
- #include "io_poller.h"
2
- #include "logger.h"
1
+ #include "librats/core/io_poller.h"
2
+ #include "librats/util/logger.h"
3
3
 
4
4
  #include <cstring>
5
5
  #include <algorithm>
6
+ #include <mutex>
6
7
  #include <set>
7
8
 
8
9
  //=============================================================================
@@ -37,8 +38,12 @@
37
38
  #elif defined(POLLER_USE_IOCP)
38
39
  #include <winsock2.h>
39
40
  #include <ws2tcpip.h>
41
+ #include "librats/core/notifier.h" // wakes the readiness thread out of WSAPoll
42
+ #include <condition_variable>
40
43
  #include <mutex>
44
+ #include <thread>
41
45
  #include <unordered_map>
46
+ #include <unordered_set>
42
47
  #include <vector>
43
48
  #include <memory>
44
49
  #elif defined(POLLER_USE_POLL)
@@ -193,15 +198,21 @@ public:
193
198
  }
194
199
 
195
200
  bool add(socket_t fd, uint32_t events) override {
201
+ std::lock_guard<std::mutex> lock(mutex_);
202
+ // Already registered — answer as epoll does (EEXIST → false) rather than
203
+ // quietly re-arming under the caller. A second add is a caller bug, and
204
+ // the backends have to agree on what it does; see IocpPoller::add().
205
+ if (registered_.find(fd) != registered_.end()) return false;
196
206
  if (!apply_changes(fd, events, EV_ADD | EV_CLEAR))
197
207
  return false;
198
208
  registered_.insert(fd);
199
209
  return true;
200
210
  }
201
-
211
+
202
212
  bool modify(socket_t fd, uint32_t events) override {
213
+ std::lock_guard<std::mutex> lock(mutex_);
203
214
  if (registered_.find(fd) == registered_.end()) return false;
204
-
215
+
205
216
  // kqueue: adding a filter that already exists replaces it.
206
217
  // We also need to delete filters that are no longer wanted.
207
218
  struct kevent changes[4];
@@ -230,8 +241,9 @@ public:
230
241
  }
231
242
 
232
243
  bool remove(socket_t fd) override {
244
+ std::lock_guard<std::mutex> lock(mutex_);
233
245
  if (registered_.erase(fd) == 0) return false;
234
-
246
+
235
247
  struct kevent changes[2];
236
248
  // Delete both read and write filters. Ignore errors (filter may not exist).
237
249
  EV_SET(&changes[0], fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr);
@@ -308,8 +320,13 @@ public:
308
320
 
309
321
  private:
310
322
  int kqfd_ = -1;
323
+ /// Guards registered_ only. wait() never touches it, so the event loop is
324
+ /// never blocked by a control call — but add/modify/remove are documented as
325
+ /// callable from any thread (io_poller.h), and the concurrency tests take that
326
+ /// literally, so the set itself has to be safe against concurrent mutation.
327
+ std::mutex mutex_;
311
328
  std::set<socket_t> registered_; ///< Track registered fds
312
-
329
+
313
330
  bool apply_changes(socket_t fd, uint32_t events, uint16_t kq_flags) {
314
331
  struct kevent changes[2];
315
332
  int nchanges = 0;
@@ -343,10 +360,27 @@ private:
343
360
  // readiness notifications through the IOCP completion port (proactor
344
361
  // model adapted to reactor semantics).
345
362
  //
346
- // Listen sockets and connecting sockets (where overlapped recv/send
347
- // cannot be used) fall back to a non-blocking WSAPoll check done
348
- // inside wait() typically only 1 listen + ~30 connecting sockets,
349
- // so this is negligible overhead.
363
+ // Listen sockets and connecting sockets cannot carry a zero-byte
364
+ // overlapped operation (WSARecv on either fails with WSAENOTCONN), so
365
+ // IOCP never has anything to tell us about them. They are checked with
366
+ // a non-blocking WSAPoll at the top of wait() instead.
367
+ //
368
+ // That check ALONE is not enough, and getting this wrong is expensive:
369
+ // wait() then blocks in GQCS for the caller's full timeout, so readiness
370
+ // on one of those sockets is not noticed until wait() happens to return
371
+ // for some other reason — up to Reactor::kMaxPollMs (50 ms) late. Every
372
+ // outbound connect and every inbound accept paid that delay. So a small
373
+ // readiness thread (aux_loop) blocks in WSAPoll on exactly those sockets
374
+ // and posts a completion packet the moment one is ready, which is what
375
+ // GQCS is waiting for. The thread is only a WAKE source: the events
376
+ // themselves still come from check_wsapoll_sockets() under the mutex,
377
+ // against the live socket set — so a stale entry in the thread's
378
+ // snapshot can cost a spurious wakeup and nothing more.
379
+ //
380
+ // wsapoll_mode_ indexes those sockets separately from active_. The check
381
+ // runs on every wait() and used to walk every registered socket to find
382
+ // the one or two that are not IOCP-driven; with the index it costs
383
+ // nothing at all when there are none (the common steady state).
350
384
  //
351
385
  // When send_to_peer() on another thread arms a write via modify(),
352
386
  // the zero-byte WSASend completes and wakes GQCS immediately —
@@ -365,14 +399,40 @@ public:
365
399
  IocpPoller() {
366
400
  iocp_ = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1);
367
401
  if (!iocp_) {
368
- LOG_POLLER_ERROR("CreateIoCompletionPort failed: " +
402
+ LOG_POLLER_ERROR("CreateIoCompletionPort failed: " +
369
403
  std::to_string(GetLastError()));
370
- } else {
371
- LOG_POLLER_INFO("Created IOCP poller");
404
+ return;
405
+ }
406
+ LOG_POLLER_INFO("Created IOCP poller");
407
+
408
+ // The readiness thread for listen/connecting sockets. It parks on the
409
+ // condition variable whenever there is nothing in WSAPoll mode, which on a
410
+ // node whose connections are all established is most of the time.
411
+ //
412
+ // Its wakeup socket is built here rather than as a plain member because a
413
+ // poller is constructed with the Reactor, which happens before anything
414
+ // calls init_socket_library() — and a Notifier built before WSAStartup is a
415
+ // Notifier that silently does nothing. Idempotent, so asking again is free.
416
+ init_socket_library();
417
+ aux_wake_ = std::make_unique<Notifier>();
418
+ if (!is_valid_socket(aux_wake_->fd())) {
419
+ LOG_POLLER_WARN("No wakeup socket for the readiness thread; "
420
+ "listen/connect readiness falls back to a short poll timeout");
372
421
  }
422
+ aux_thread_ = std::thread(&IocpPoller::aux_loop, this);
373
423
  }
374
-
424
+
375
425
  ~IocpPoller() override {
426
+ // Stop the readiness thread FIRST: it touches active_ and posts to iocp_,
427
+ // so neither may be torn down while it is still running.
428
+ {
429
+ std::lock_guard<std::mutex> lock(mutex_);
430
+ aux_stop_ = true;
431
+ }
432
+ aux_cv_.notify_all();
433
+ if (aux_wake_) aux_wake_->signal();
434
+ if (aux_thread_.joinable()) aux_thread_.join();
435
+
376
436
  {
377
437
  std::lock_guard<std::mutex> lock(mutex_);
378
438
  // Cancel all pending I/O before destroying
@@ -382,6 +442,8 @@ public:
382
442
  }
383
443
  }
384
444
  active_.clear();
445
+ wsapoll_mode_.clear();
446
+ demoted_.clear();
385
447
  all_states_.clear();
386
448
  }
387
449
  if (iocp_) {
@@ -392,16 +454,31 @@ public:
392
454
 
393
455
  bool add(socket_t fd, uint32_t events) override {
394
456
  std::lock_guard<std::mutex> lock(mutex_);
395
-
457
+
458
+ // Already registered. Overwriting active_[fd] would orphan the old state:
459
+ // it stays in all_states_ with removed == false, so gc_removed_states()
460
+ // never frees it, and its outstanding zero-byte WSARecv keeps completing
461
+ // — reporting events for an fd this poller has since been told to forget,
462
+ // by which time Windows may have handed the descriptor number to a
463
+ // different socket. epoll answers EEXIST here; answer the same and let the
464
+ // caller reach for modify().
465
+ if (active_.find(fd) != active_.end()) {
466
+ LOG_POLLER_DEBUG("add: fd " + std::to_string(fd) +
467
+ " is already registered; use modify()");
468
+ return false;
469
+ }
470
+
396
471
  auto state_ptr = std::make_unique<IocpSocketState>(fd);
397
472
  auto* state = state_ptr.get();
398
473
  state->desired_events = events;
399
474
 
400
475
  // Associate socket with IOCP (needed for overlapped I/O)
401
476
  CreateIoCompletionPort(reinterpret_cast<HANDLE>(fd), iocp_, 0, 0);
402
-
477
+
478
+ state->is_datagram = is_datagram_socket(fd);
479
+
403
480
  // Determine socket mode:
404
- // - Try zero-byte WSARecv to detect if socket is connected (IOCP mode)
481
+ // - Try a readiness probe to detect if socket is connected (IOCP mode)
405
482
  // - Listen sockets and connecting sockets use WSAPoll fallback
406
483
  if (events & PollIn) {
407
484
  if (arm_read(state)) {
@@ -419,10 +496,24 @@ public:
419
496
  if (state->mode == SocketMode::Iocp && (events & PollOut)) {
420
497
  arm_write(state);
421
498
  }
422
-
499
+
423
500
  active_[fd] = state;
424
501
  all_states_.push_back(std::move(state_ptr));
425
-
502
+
503
+ // Which of the two mechanisms a socket ended up on decides how it behaves
504
+ // under load and on error, and it is not visible from anywhere else — so say
505
+ // it once, here, rather than leave the next person to infer it.
506
+ LOG_POLLER_DEBUG("add: fd " + std::to_string(fd) + " → " +
507
+ (state->mode == SocketMode::Iocp ? "IOCP" : "WSAPoll") +
508
+ " mode" + (state->is_datagram ? " (datagram)" : ""));
509
+
510
+ // A socket IOCP cannot watch has to be picked up by the readiness thread,
511
+ // which is very likely blocked in WSAPoll on a set that predates this one.
512
+ if (state->mode == SocketMode::WsaPoll) {
513
+ wsapoll_mode_.insert(fd);
514
+ wake_aux();
515
+ }
516
+
426
517
  return true;
427
518
  }
428
519
 
@@ -441,9 +532,17 @@ public:
441
532
  if (state->mode == SocketMode::WsaPoll && (events & PollIn)) {
442
533
  if (arm_read(state)) {
443
534
  state->mode = SocketMode::Iocp;
535
+ wsapoll_mode_.erase(fd);
536
+ demoted_.erase(fd);
537
+ wake_aux(); // one fewer socket to watch; re-snapshot
444
538
  }
445
539
  }
446
-
540
+ // A socket that stays in WSAPoll mode may have changed which events it
541
+ // wants (a connecting socket disarming PollOut), and the readiness thread
542
+ // is holding the previous mask.
543
+ if (state->mode == SocketMode::WsaPoll) wake_aux();
544
+
545
+
447
546
  // Arm overlapped ops as needed for IOCP-mode sockets
448
547
  if (state->mode == SocketMode::Iocp) {
449
548
  if ((events & PollIn) && !state->read_pending) {
@@ -474,6 +573,12 @@ public:
474
573
  }
475
574
 
476
575
  active_.erase(it);
576
+ // Before the caller closes this socket. The readiness thread may still be
577
+ // polling it from an older snapshot — harmless (a closed handle reports
578
+ // POLLNVAL, and only the live set produces events), but waking it now
579
+ // means it stops looking at a descriptor that is about to be reused.
580
+ if (wsapoll_mode_.erase(fd) > 0) wake_aux();
581
+ demoted_.erase(fd);
477
582
  ++removed_count_;
478
583
  // Note: SocketState is NOT freed yet — it stays in all_states_ so
479
584
  // that the OVERLAPPED pointers remain valid until cancelled
@@ -487,13 +592,28 @@ public:
487
592
 
488
593
  //------------------------------------------------------------------
489
594
  // Step 1: Check WSAPoll-mode sockets (listen + connecting)
490
- // Non-blocking check (timeout=0), typically <= 31 sockets
595
+ // Non-blocking check (timeout=0), and free when there are none
491
596
  //------------------------------------------------------------------
492
597
  {
493
598
  std::lock_guard<std::mutex> lock(mutex_);
599
+ // Re-arm the readiness thread here rather than at the end of the last
600
+ // wait(): the caller coming back for more is what says it has finished
601
+ // handling the previous batch. Re-arming any earlier would have the
602
+ // thread reporting sockets the caller has not dealt with yet.
603
+ if (wake_pending_) {
604
+ wake_pending_ = false;
605
+ aux_cv_.notify_one();
606
+ }
607
+ // Order matters: retry the older demotions first, so a socket demoted by
608
+ // arm_pending() below waits for the next wait() instead of being asked to
609
+ // arm twice in a row microseconds apart.
610
+ promote_demoted();
611
+ arm_pending();
494
612
  count = check_wsapoll_sockets(results, max_results);
495
613
  }
496
-
614
+
615
+ const int wsapoll_count = count;
616
+
497
617
  //------------------------------------------------------------------
498
618
  // Step 2: Wait for IOCP completions (connected data sockets)
499
619
  // If Step 1 already found events, don't block (timeout=0)
@@ -525,10 +645,16 @@ public:
525
645
  //------------------------------------------------------------------
526
646
  if (iocp_count > 0) {
527
647
  std::lock_guard<std::mutex> lock(mutex_);
528
-
648
+ bool woken = false;
649
+
529
650
  for (ULONG i = 0; i < iocp_count && count < max_results; ++i) {
530
- if (!entries[i].lpOverlapped) continue;
531
-
651
+ // The readiness thread's wake packet: no overlapped, and the only
652
+ // completion key this poller ever posts by hand.
653
+ if (!entries[i].lpOverlapped) {
654
+ if (entries[i].lpCompletionKey == kAuxWakeKey) woken = true;
655
+ continue;
656
+ }
657
+
532
658
  auto* io = reinterpret_cast<IocpOverlapped*>(entries[i].lpOverlapped);
533
659
  auto* state = io->state;
534
660
  if (!state) continue;
@@ -543,28 +669,42 @@ public:
543
669
  // Check the NTSTATUS from the overlapped result
544
670
  // 0 = STATUS_SUCCESS, non-zero = error (e.g. connection reset)
545
671
  ULONG_PTR internal_status = io->overlapped.Internal;
546
-
672
+
673
+ // A datagram readiness probe peeks at one byte of what is always a
674
+ // larger datagram, so it reports STATUS_BUFFER_OVERFLOW rather than
675
+ // success. That IS the "data is waiting" signal — the datagram is
676
+ // still queued (MSG_PEEK took nothing), ready for the real read.
677
+ constexpr ULONG_PTR kStatusBufferOverflow = 0x80000005ul;
678
+ if (state->is_datagram && internal_status == kStatusBufferOverflow)
679
+ internal_status = 0;
680
+
547
681
  uint32_t reported_events = 0;
548
682
  if (internal_status == 0) {
549
683
  // Success — report readiness for the event type we were watching
550
684
  reported_events = io->event_type & state->desired_events;
551
-
552
- // Re-arm overlapped I/O for next notification.
553
- // Without this, the socket becomes deaf after the first
554
- // completion because sync_poller() only calls modify()
555
- // when desired_events change — which they don't for a
556
- // socket that stays PollIn-only.
557
- if ((io->event_type & PollIn) && (state->desired_events & PollIn)) {
558
- arm_read(state);
559
- }
560
- if ((io->event_type & PollOut) && (state->desired_events & PollOut)) {
561
- arm_write(state);
562
- }
563
685
  } else {
564
686
  // I/O error (connection reset, cancelled, etc.)
565
687
  reported_events = PollErr;
566
688
  }
567
-
689
+
690
+ // This socket needs a fresh overlapped operation, or it goes deaf:
691
+ // nothing else arms it, since modify() is called only when the
692
+ // watched events change and for a socket that stays PollIn-only they
693
+ // never do. The work is queued rather than done here — see
694
+ // arm_pending(), which is also where a socket that cannot be armed
695
+ // gets handed to the readiness thread.
696
+ //
697
+ // Queued after an ERROR for a datagram socket but not for a stream
698
+ // one, and the asymmetry is the whole point. A stream error is
699
+ // terminal: the owner closes the connection on PollErr, so arming
700
+ // again would only race that close. A datagram error is not — it
701
+ // belongs to ONE datagram (Windows reports an ICMP complaint about an
702
+ // earlier send on the next receive) while the socket stays perfectly
703
+ // usable, and skipping it there costs every datagram that reactor
704
+ // would have received from then on.
705
+ if (internal_status == 0 || state->is_datagram)
706
+ pending_arm_.push_back(state->fd);
707
+
568
708
  if (reported_events == 0) continue;
569
709
 
570
710
  // Merge events for the same fd (read + write may fire together)
@@ -582,7 +722,20 @@ public:
582
722
  ++count;
583
723
  }
584
724
  }
585
-
725
+
726
+ // The wake packet says a listen/connecting socket became ready while
727
+ // we were blocked, so look again now instead of making the caller come
728
+ // back for it. Only when step 1 found nothing there: the two modes are
729
+ // disjoint sets of sockets, so with wsapoll_count == 0 nothing this can
730
+ // add is already in `results`.
731
+ if (woken && wsapoll_count == 0 && count < max_results) {
732
+ if (wake_pending_) {
733
+ wake_pending_ = false;
734
+ aux_cv_.notify_one();
735
+ }
736
+ count += check_wsapoll_sockets(results + count, max_results - count);
737
+ }
738
+
586
739
  // GC removed states whose cancelled I/O has fully drained.
587
740
  // Threshold: at least 64 removed, or removed > half of total —
588
741
  // avoids running the sweep on every wait() call.
@@ -627,12 +780,15 @@ private:
627
780
  bool read_pending; ///< Zero-byte WSARecv in flight
628
781
  bool write_pending; ///< Zero-byte WSASend in flight
629
782
  bool removed; ///< Marked for removal
783
+ bool is_datagram; ///< SOCK_DGRAM — readiness probed with MSG_PEEK
784
+ char peek_byte; ///< one-byte landing pad for that probe
630
785
  IocpOverlapped read_io; ///< Overlapped for read notification
631
786
  IocpOverlapped write_io; ///< Overlapped for write notification
632
-
787
+
633
788
  explicit IocpSocketState(socket_t f)
634
789
  : fd(f), desired_events(0), mode(SocketMode::WsaPoll),
635
- read_pending(false), write_pending(false), removed(false) {
790
+ read_pending(false), write_pending(false), removed(false),
791
+ is_datagram(false), peek_byte(0) {
636
792
  read_io.state = this;
637
793
  read_io.event_type = PollIn;
638
794
  write_io.state = this;
@@ -643,29 +799,188 @@ private:
643
799
  std::mutex mutex_;
644
800
  std::unordered_map<socket_t, IocpSocketState*> active_; ///< fd → state lookup
645
801
  std::vector<std::unique_ptr<IocpSocketState>> all_states_; ///< Owns all states
802
+ /// The subset of active_ that IOCP cannot watch (listen + connecting sockets).
803
+ /// Indexed separately so check_wsapoll_sockets() costs nothing when it is empty
804
+ /// instead of walking every registered socket to discover that.
805
+ std::unordered_set<socket_t> wsapoll_mode_;
806
+ /// The subset of wsapoll_mode_ that belongs on IOCP and is only there because
807
+ /// arming failed — see promote_demoted(). A listen socket is never in here.
808
+ std::unordered_set<socket_t> demoted_;
809
+ /// Sockets whose completion has been handed to the caller and which therefore
810
+ /// need a fresh overlapped operation — posted at the top of the next wait(),
811
+ /// once the caller has drained them. See arm_pending(). Holds fds rather than
812
+ /// state pointers so that a socket removed in the meantime is simply not found.
813
+ std::vector<socket_t> pending_arm_;
646
814
  std::vector<WSAPOLLFD> wsapoll_fds_; ///< Reusable WSAPoll buffer
647
815
  size_t removed_count_ = 0; ///< Number of removed states awaiting GC
648
-
816
+
817
+ //----------------------------------------------------------------------
818
+ // Readiness thread for WSAPoll-mode sockets
819
+ //----------------------------------------------------------------------
820
+
821
+ /// Completion key of the packet the readiness thread posts. Sockets are
822
+ /// associated with key 0, so this cannot collide with a real completion.
823
+ static constexpr ULONG_PTR kAuxWakeKey = 1;
824
+
825
+ /// Backstop timeout for the thread's WSAPoll. Every change to the watched set
826
+ /// signals aux_wake_, so this only has to cover a wakeup that went missing;
827
+ /// without a wakeup socket at all it becomes the actual notification latency,
828
+ /// hence the much shorter degraded value.
829
+ static constexpr int kAuxPollMs = 250;
830
+ static constexpr int kAuxPollDegradedMs = 5;
831
+
832
+ std::thread aux_thread_;
833
+ std::condition_variable aux_cv_;
834
+ std::unique_ptr<Notifier> aux_wake_; ///< breaks the thread out of WSAPoll
835
+ bool aux_stop_ = false; ///< guarded by mutex_
836
+ /// A wake packet is in the completion queue and wait() has not looked yet.
837
+ /// While it is set the thread stays parked, so one ready socket can only ever
838
+ /// produce one packet however long the caller takes to come back. Guarded by
839
+ /// mutex_.
840
+ bool wake_pending_ = false;
841
+
842
+ /// Tell the readiness thread its view of the watched set is out of date.
843
+ /// Called under mutex_; wakes it whether it is parked on the condition
844
+ /// variable or blocked inside WSAPoll.
845
+ void wake_aux() {
846
+ aux_cv_.notify_one();
847
+ if (aux_wake_) aux_wake_->signal();
848
+ }
849
+
850
+ /// The readiness thread's wakeup socket, or INVALID if it has none.
851
+ socket_t aux_wake_fd() const noexcept {
852
+ return aux_wake_ ? aux_wake_->fd() : RATS_INVALID_SOCKET;
853
+ }
854
+
855
+ /// Block in WSAPoll on the sockets IOCP cannot watch, and post a completion
856
+ /// packet the moment one is ready — nothing more. wait() still derives the
857
+ /// events themselves from the live set under the mutex, so everything this
858
+ /// thread knows is allowed to be slightly stale.
859
+ void aux_loop() {
860
+ const socket_t wake_fd = aux_wake_fd();
861
+ const int poll_timeout = is_valid_socket(wake_fd) ? kAuxPollMs
862
+ : kAuxPollDegradedMs;
863
+ std::vector<WSAPOLLFD> fds;
864
+
865
+ for (;;) {
866
+ {
867
+ std::unique_lock<std::mutex> lock(mutex_);
868
+ // Park while there is nothing to watch, or while a wake packet is
869
+ // still outstanding — polling then would only rediscover a socket
870
+ // the caller has already been told about.
871
+ aux_cv_.wait(lock, [this] {
872
+ return aux_stop_ || (!wake_pending_ && !wsapoll_mode_.empty());
873
+ });
874
+ if (aux_stop_) return;
875
+
876
+ fds.clear();
877
+ fds.reserve(wsapoll_mode_.size() + 1);
878
+ for (const socket_t fd : wsapoll_mode_) {
879
+ const auto it = active_.find(fd);
880
+ if (it == active_.end() || it->second->removed) continue;
881
+
882
+ WSAPOLLFD pfd{};
883
+ pfd.fd = fd;
884
+ if (it->second->desired_events & PollIn) pfd.events |= POLLIN;
885
+ if (it->second->desired_events & PollOut) pfd.events |= POLLOUT;
886
+ if (pfd.events == 0) continue;
887
+ fds.push_back(pfd);
888
+ }
889
+ }
890
+
891
+ // Always last, so the set is never empty and WSAPoll always blocks.
892
+ if (is_valid_socket(wake_fd)) {
893
+ WSAPOLLFD pfd{};
894
+ pfd.fd = wake_fd;
895
+ pfd.events = POLLIN;
896
+ fds.push_back(pfd);
897
+ }
898
+ if (fds.empty()) continue; // no wakeup socket and nothing left to watch
899
+
900
+ const int n = WSAPoll(fds.data(), static_cast<ULONG>(fds.size()), poll_timeout);
901
+ if (n <= 0) continue; // timed out, or the set went stale under us
902
+
903
+ // The wakeup itself only means "the set changed, look again", so drain
904
+ // it and drop it before asking whether a watched socket fired.
905
+ if (is_valid_socket(wake_fd)) {
906
+ if (fds.back().revents != 0) aux_wake_->drain();
907
+ fds.pop_back();
908
+ }
909
+
910
+ bool ready = false;
911
+ for (const WSAPOLLFD& pfd : fds) {
912
+ if (pfd.revents != 0) { ready = true; break; }
913
+ }
914
+ if (!ready) continue; // only the wakeup fired: re-snapshot and poll again
915
+
916
+ {
917
+ std::lock_guard<std::mutex> lock(mutex_);
918
+ if (aux_stop_) return;
919
+ if (wake_pending_) continue; // one packet is already owed
920
+ wake_pending_ = true;
921
+ }
922
+ if (!PostQueuedCompletionStatus(iocp_, 0, kAuxWakeKey, nullptr)) {
923
+ // Nothing will consume the packet we just promised, so take the
924
+ // promise back — otherwise this thread parks for good.
925
+ LOG_POLLER_ERROR("PostQueuedCompletionStatus failed: " +
926
+ std::to_string(GetLastError()));
927
+ std::lock_guard<std::mutex> lock(mutex_);
928
+ wake_pending_ = false;
929
+ }
930
+ }
931
+ }
932
+
933
+
649
934
  //----------------------------------------------------------------------
650
935
  // Zero-byte overlapped I/O: readiness notification via IOCP
651
936
  //----------------------------------------------------------------------
652
937
 
938
+ /// Is this a datagram socket?
939
+ ///
940
+ /// It matters because the zero-byte WSARecv this poller uses for readiness
941
+ /// notification is only safe on a stream socket. On a datagram socket the
942
+ /// operation completes with WSAEMSGSIZE — and Windows DISCARDS the datagram
943
+ /// that satisfied it, because it did not fit in the (empty) buffer, losing one
944
+ /// datagram per notification. arm_read() therefore probes a datagram socket
945
+ /// with a one-byte MSG_PEEK instead, which reports the same readiness without
946
+ /// taking anything off the queue.
947
+ static bool is_datagram_socket(socket_t fd) {
948
+ int type = 0;
949
+ int len = static_cast<int>(sizeof(type));
950
+ if (getsockopt(fd, SOL_SOCKET, SO_TYPE, reinterpret_cast<char*>(&type), &len) != 0)
951
+ return false;
952
+ return type == SOCK_DGRAM;
953
+ }
954
+
653
955
  /// Post a zero-byte WSARecv. Completes when data is available to read.
654
956
  /// Returns false if the socket is not connected (listen socket).
655
957
  bool arm_read(IocpSocketState* state) {
656
958
  if (state->read_pending) return true;
657
959
 
658
960
  std::memset(&state->read_io.overlapped, 0, sizeof(OVERLAPPED));
659
-
961
+
660
962
  WSABUF buf;
661
- buf.buf = nullptr;
662
- buf.len = 0;
663
- DWORD flags = 0;
963
+ DWORD flags = 0;
964
+ if (state->is_datagram) {
965
+ // Peek at one byte rather than receiving zero: MSG_PEEK leaves the
966
+ // datagram on the queue for the real recvfrom() that follows, where a
967
+ // zero-byte receive would consume and discard it (see
968
+ // is_datagram_socket). The completion carries STATUS_BUFFER_OVERFLOW
969
+ // because one byte is not the whole datagram — which is precisely the
970
+ // "something is there" signal we are after, and is treated as success
971
+ // when the completion is dequeued.
972
+ buf.buf = &state->peek_byte;
973
+ buf.len = 1;
974
+ flags = MSG_PEEK;
975
+ } else {
976
+ buf.buf = nullptr;
977
+ buf.len = 0;
978
+ }
664
979
  DWORD bytes = 0;
665
-
980
+
666
981
  int ret = WSARecv(state->fd, &buf, 1, &bytes, &flags,
667
982
  &state->read_io.overlapped, nullptr);
668
-
983
+
669
984
  if (ret == 0) {
670
985
  // Completed immediately — completion still posted to IOCP
671
986
  state->read_pending = true;
@@ -687,7 +1002,11 @@ private:
687
1002
  /// Also wakes GQCS when called from another thread (via modify/send_to_peer).
688
1003
  bool arm_write(IocpSocketState* state) {
689
1004
  if (state->write_pending) return true;
690
-
1005
+ // A zero-byte WSASend on a datagram socket would put an empty datagram on
1006
+ // the wire rather than probe writability. Nothing asks for PollOut on one
1007
+ // (a datagram socket is writable essentially always), so simply decline.
1008
+ if (state->is_datagram) return false;
1009
+
691
1010
  std::memset(&state->write_io.overlapped, 0, sizeof(OVERLAPPED));
692
1011
 
693
1012
  WSABUF buf;
@@ -737,46 +1056,181 @@ private:
737
1056
  // WSAPoll fallback for listen + connecting sockets
738
1057
  //----------------------------------------------------------------------
739
1058
 
1059
+ /// Has this socket's connect attempt already failed?
1060
+ ///
1061
+ /// MSDN lists "WSAPoll does not report a failed connection attempt" as a known
1062
+ /// limitation: neither POLLERR nor POLLWRNORM raised, so a refused or
1063
+ /// unreachable dial produces no event at all and sits until the reactor's
1064
+ /// establish deadline reaps it fifteen seconds later. Measured on Windows 11
1065
+ /// that no longer reproduces — a refused loopback dial comes back as
1066
+ /// POLLERR|POLLHUP|POLLWRNORM, and a single WSAPoll spanning the failure sees
1067
+ /// it too — so on a current build this is belt and braces. It stays because
1068
+ /// the limitation is still documented, the affected builds are still out
1069
+ /// there, and the fallback is one getsockopt: the socket carries the error
1070
+ /// either way, so ask it directly rather than depend on which behaviour we got.
1071
+ ///
1072
+ /// Only ever consulted for a socket WSAPoll reported nothing about, so it costs
1073
+ /// one getsockopt per *connecting* socket per wait() — there are only ever a
1074
+ /// handful. A connect still in flight reads back 0, so there are no false
1075
+ /// positives. (SO_ERROR is read-and-clear, but the caller that acts on the
1076
+ /// PollErr never reads it again — Connection::on_error does not.)
1077
+ static bool connect_failed(socket_t fd) {
1078
+ int err = 0;
1079
+ int len = static_cast<int>(sizeof(err));
1080
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&err), &len) != 0)
1081
+ return false;
1082
+ return err != 0;
1083
+ }
1084
+
1085
+ /// Post the overlapped operations the last batch of completions made necessary.
1086
+ /// Called under mutex, at the top of wait().
1087
+ ///
1088
+ /// Deliberately NOT done where the completion is processed. There the data that
1089
+ /// triggered it is still sitting unread in the socket buffer — the caller only
1090
+ /// reads after wait() has returned — so a fresh zero-byte WSARecv completes on
1091
+ /// the spot and queues a second completion with nothing behind it. That is one
1092
+ /// wasted wakeup and one empty read for every burst of data: measured at exactly
1093
+ /// 2.00 readiness reports per message where 1.00 is achievable, half of them
1094
+ /// finding an empty socket.
1095
+ ///
1096
+ /// Waiting until the caller comes back for more is what lets the arm find an
1097
+ /// empty buffer and actually pend, which is the entire point of arming. It is
1098
+ /// the same signal wake_pending_ already relies on: a caller asking for the next
1099
+ /// batch has finished with the one it was given. Nothing is lost in between — if
1100
+ /// data lands while no operation is posted, the arm below completes immediately
1101
+ /// and step 2 of this very wait() dequeues it.
1102
+ void arm_pending() {
1103
+ if (pending_arm_.empty()) return;
1104
+
1105
+ for (const socket_t fd : pending_arm_) {
1106
+ const auto it = active_.find(fd);
1107
+ if (it == active_.end()) continue; // removed while we were away
1108
+ auto* state = it->second;
1109
+ if (state->removed || state->mode != SocketMode::Iocp) continue;
1110
+
1111
+ if ((state->desired_events & PollIn) && !state->read_pending)
1112
+ arm_read(state);
1113
+ if ((state->desired_events & PollOut) && !state->write_pending)
1114
+ arm_write(state);
1115
+
1116
+ // Arming can fail outright: WSARecv hands back a queued error instead of
1117
+ // pending (exactly what a datagram socket does while it still has ICMP
1118
+ // reports to deliver), or the system is out of buffers. Nothing is then
1119
+ // left in flight to bring us back to this socket and nobody arms it
1120
+ // again, so hand it to the readiness thread — which polls instead of
1121
+ // arming and therefore cannot be left holding nothing. Slower per event,
1122
+ // and the only alternative is deaf.
1123
+ if ((state->desired_events & PollIn) && !state->read_pending) {
1124
+ state->mode = SocketMode::WsaPoll;
1125
+ wsapoll_mode_.insert(fd);
1126
+ demoted_.insert(fd); // and try to get it back later
1127
+ wake_aux();
1128
+ LOG_POLLER_DEBUG("fd " + std::to_string(fd) +
1129
+ " could not be armed; demoted to WSAPoll mode");
1130
+ }
1131
+ }
1132
+ pending_arm_.clear();
1133
+ }
1134
+
1135
+ /// Put demoted sockets back on the completion port. Called under mutex.
1136
+ ///
1137
+ /// A socket reaches WSAPoll mode two ways, and only one of them is permanent.
1138
+ /// A listen socket can never carry overlapped I/O and belongs there for good;
1139
+ /// a socket that was demoted because arming failed is there only until the
1140
+ /// condition that failed it clears — a datagram socket with an ICMP report
1141
+ /// still queued arms perfectly well once that queue drains. Nothing else would
1142
+ /// ever move it back: modify() is the only other promotion path and the reactor
1143
+ /// never calls it on the mux socket, so one transient ICMP would otherwise pin
1144
+ /// the busiest socket in the process to the slow path for the rest of its life
1145
+ /// (measured at ~1.3x per datagram, and ~2.7x per readiness notification).
1146
+ ///
1147
+ /// Costs one WSARecv attempt per demoted socket per wait(), and a socket leaves
1148
+ /// the set the moment one succeeds. Sockets that were never on IOCP are not in
1149
+ /// the set at all, so the steady-state cost is one empty-container check.
1150
+ void promote_demoted() {
1151
+ if (demoted_.empty()) return;
1152
+
1153
+ for (auto it = demoted_.begin(); it != demoted_.end(); ) {
1154
+ const socket_t fd = *it;
1155
+ const auto ait = active_.find(fd);
1156
+ if (ait == active_.end() || ait->second->removed) {
1157
+ it = demoted_.erase(it);
1158
+ continue;
1159
+ }
1160
+
1161
+ auto* state = ait->second;
1162
+ if (!(state->desired_events & PollIn) || !arm_read(state)) {
1163
+ ++it; // still cannot be armed; try again next time
1164
+ continue;
1165
+ }
1166
+
1167
+ state->mode = SocketMode::Iocp;
1168
+ wsapoll_mode_.erase(fd);
1169
+ it = demoted_.erase(it);
1170
+ wake_aux(); // one fewer socket for the readiness thread
1171
+ LOG_POLLER_DEBUG("fd " + std::to_string(fd) +
1172
+ " armed again; back on IOCP mode");
1173
+ }
1174
+ }
1175
+
740
1176
  /// Non-blocking check of WSAPoll-mode sockets. Called under mutex.
1177
+ ///
1178
+ /// Walks wsapoll_mode_ rather than active_: these are the one listen socket and
1179
+ /// however many dials are in flight, where active_ is every connection the
1180
+ /// reactor owns. This runs on every wait(), so the difference is the whole cost
1181
+ /// of the fallback at scale — and it is now zero when there is nothing in it.
741
1182
  int check_wsapoll_sockets(PollResult* results, int max_results) {
1183
+ if (wsapoll_mode_.empty() || max_results <= 0) return 0;
1184
+
742
1185
  wsapoll_fds_.clear();
743
-
744
- for (auto& [fd, state] : active_) {
1186
+ wsapoll_fds_.reserve(wsapoll_mode_.size());
1187
+
1188
+ for (const socket_t fd : wsapoll_mode_) {
1189
+ const auto it = active_.find(fd);
1190
+ if (it == active_.end()) continue;
1191
+ const auto* state = it->second;
745
1192
  if (state->removed || state->mode != SocketMode::WsaPoll) continue;
746
-
1193
+
747
1194
  WSAPOLLFD pfd;
748
1195
  pfd.fd = fd;
749
1196
  pfd.events = 0;
750
1197
  if (state->desired_events & PollIn) pfd.events |= POLLIN;
751
1198
  if (state->desired_events & PollOut) pfd.events |= POLLOUT;
752
1199
  pfd.revents = 0;
753
-
1200
+ if (pfd.events == 0) continue;
1201
+
754
1202
  wsapoll_fds_.push_back(pfd);
755
1203
  }
756
-
1204
+
757
1205
  if (wsapoll_fds_.empty()) return 0;
758
-
1206
+
759
1207
  // Non-blocking poll (timeout = 0)
760
- int n = WSAPoll(wsapoll_fds_.data(),
761
- static_cast<ULONG>(wsapoll_fds_.size()), 0);
762
- if (n <= 0) return 0;
763
-
1208
+ const int n = WSAPoll(wsapoll_fds_.data(),
1209
+ static_cast<ULONG>(wsapoll_fds_.size()), 0);
1210
+ if (n < 0) return 0;
1211
+
764
1212
  int count = 0;
765
1213
  for (auto& pfd : wsapoll_fds_) {
766
1214
  if (count >= max_results) break;
767
- if (pfd.revents == 0) continue;
768
-
1215
+
769
1216
  uint32_t events = 0;
770
1217
  if (pfd.revents & POLLIN) events |= PollIn;
771
1218
  if (pfd.revents & POLLOUT) events |= PollOut;
772
1219
  if (pfd.revents & POLLERR) events |= PollErr;
773
1220
  if (pfd.revents & POLLHUP) events |= PollHup;
774
-
1221
+
1222
+ // Silence from a socket waiting to become writable is the one case
1223
+ // WSAPoll is documented not to speak for; see connect_failed().
1224
+ if (events == 0) {
1225
+ if (!(pfd.events & POLLOUT) || !connect_failed(pfd.fd)) continue;
1226
+ events = PollErr;
1227
+ }
1228
+
775
1229
  results[count].fd = pfd.fd;
776
1230
  results[count].events = events;
777
1231
  ++count;
778
1232
  }
779
-
1233
+
780
1234
  return count;
781
1235
  }
782
1236
  };
@@ -803,7 +1257,8 @@ public:
803
1257
 
804
1258
  bool add(socket_t fd, uint32_t events) override {
805
1259
  std::lock_guard<std::mutex> lock(mutex_);
806
- registered_[fd] = events;
1260
+ // Reject a duplicate like every other backend does — see IocpPoller::add().
1261
+ if (!registered_.emplace(fd, events).second) return false;
807
1262
  dirty_ = true;
808
1263
  return true;
809
1264
  }