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.
- package/README.md +145 -331
- package/binding.gyp +16 -3
- package/lib/index.d.ts +288 -696
- package/lib/index.js +407 -44
- package/native-src/3rdparty/android/ifaddrs-android.c +1 -0
- package/native-src/3rdparty/android/ifaddrs-android.h +1 -0
- package/native-src/CMakeLists.txt +404 -179
- package/native-src/LICENSE +1 -1
- package/native-src/src/librats/bindings/rats.cpp +762 -0
- package/native-src/src/librats/bindings/rats.h +380 -0
- package/native-src/src/librats/bittorrent/bencode.cpp +437 -0
- package/native-src/src/librats/bittorrent/bencode.h +176 -0
- package/native-src/src/librats/bittorrent/bitfield.cpp +97 -0
- package/native-src/src/librats/bittorrent/bitfield.h +76 -0
- package/native-src/src/librats/bittorrent/byte_io.h +58 -0
- package/native-src/src/librats/bittorrent/choker.cpp +25 -0
- package/native-src/src/librats/bittorrent/choker.h +46 -0
- package/native-src/src/librats/bittorrent/client.cpp +413 -0
- package/native-src/src/librats/bittorrent/client.h +227 -0
- package/native-src/src/librats/bittorrent/disk_io.cpp +209 -0
- package/native-src/src/librats/bittorrent/disk_io.h +150 -0
- package/native-src/src/librats/bittorrent/extensions.cpp +191 -0
- package/native-src/src/librats/bittorrent/extensions.h +94 -0
- package/native-src/src/librats/bittorrent/file_storage.cpp +77 -0
- package/native-src/src/librats/bittorrent/file_storage.h +79 -0
- package/native-src/src/librats/bittorrent/log.h +42 -0
- package/native-src/src/librats/bittorrent/magnet_uri.cpp +98 -0
- package/native-src/src/librats/bittorrent/magnet_uri.h +35 -0
- package/native-src/src/librats/bittorrent/peer_connection.cpp +502 -0
- package/native-src/src/librats/bittorrent/peer_connection.h +194 -0
- package/native-src/src/librats/bittorrent/peer_list.cpp +68 -0
- package/native-src/src/librats/bittorrent/peer_list.h +75 -0
- package/native-src/src/librats/bittorrent/piece_picker.cpp +352 -0
- package/native-src/src/librats/bittorrent/piece_picker.h +201 -0
- package/native-src/src/librats/bittorrent/reactor.cpp +97 -0
- package/native-src/src/librats/bittorrent/reactor.h +89 -0
- package/native-src/src/librats/bittorrent/resume_data.cpp +72 -0
- package/native-src/src/librats/bittorrent/resume_data.h +41 -0
- package/native-src/src/librats/bittorrent/store_buffer.cpp +48 -0
- package/native-src/src/librats/bittorrent/store_buffer.h +47 -0
- package/native-src/src/librats/bittorrent/torrent.cpp +870 -0
- package/native-src/src/librats/bittorrent/torrent.h +260 -0
- package/native-src/src/librats/bittorrent/torrent_creator.cpp +129 -0
- package/native-src/src/librats/bittorrent/torrent_creator.h +58 -0
- package/native-src/src/librats/bittorrent/torrent_info.cpp +314 -0
- package/native-src/src/librats/bittorrent/torrent_info.h +118 -0
- package/native-src/src/librats/bittorrent/tracker.cpp +374 -0
- package/native-src/src/librats/bittorrent/tracker.h +108 -0
- package/native-src/src/librats/bittorrent/types.cpp +206 -0
- package/native-src/src/librats/bittorrent/types.h +86 -0
- package/native-src/src/librats/core/address.cpp +35 -0
- package/native-src/src/librats/core/address.h +78 -0
- package/native-src/src/librats/core/bytes.h +69 -0
- package/native-src/src/librats/core/chained_send_buffer.cpp +172 -0
- package/native-src/src/librats/core/chained_send_buffer.h +183 -0
- package/native-src/src/librats/core/endpoint_parse.cpp +41 -0
- package/native-src/src/librats/core/endpoint_parse.h +31 -0
- package/native-src/src/librats/core/event_bus.h +70 -0
- package/native-src/src/librats/core/host_endpoint.h +56 -0
- package/native-src/src/{io_poller.cpp → librats/core/io_poller.cpp} +520 -65
- package/native-src/src/{io_poller.h → librats/core/io_poller.h} +12 -6
- package/native-src/src/librats/core/ip_address.cpp +120 -0
- package/native-src/src/librats/core/ip_address.h +109 -0
- package/native-src/src/librats/core/mpsc_queue.h +47 -0
- package/native-src/src/librats/core/notifier.h +74 -0
- package/native-src/src/librats/core/receive_buffer.cpp +219 -0
- package/native-src/src/librats/core/receive_buffer.h +171 -0
- package/native-src/src/librats/core/service_registry.h +58 -0
- package/native-src/src/{socket.cpp → librats/core/socket.cpp} +625 -118
- package/native-src/src/librats/core/socket.h +496 -0
- package/native-src/src/librats/core/timer_queue.h +105 -0
- package/native-src/src/librats/core/types.cpp +43 -0
- package/native-src/src/librats/core/types.h +103 -0
- package/native-src/src/librats/core/wakeup_pipe.h +83 -0
- package/native-src/src/{crypto → librats/crypto}/blake2_endian.h +21 -23
- package/native-src/src/{crypto → librats/crypto}/blake2b.c +34 -33
- package/native-src/src/{crypto → librats/crypto}/blake2b.h +7 -6
- package/native-src/src/{crypto → librats/crypto}/blake2s.c +55 -54
- package/native-src/src/{crypto → librats/crypto}/blake2s.h +13 -12
- package/native-src/src/{crypto → librats/crypto}/chacha.c +22 -21
- package/native-src/src/{crypto → librats/crypto}/chacha.h +14 -13
- package/native-src/src/{crypto → librats/crypto}/chachapoly.c +56 -56
- package/native-src/src/{crypto → librats/crypto}/chachapoly.h +24 -17
- package/native-src/src/{crc32.cpp → librats/crypto/crc32.cpp} +1 -1
- package/native-src/src/{crc32.h → librats/crypto/crc32.h} +3 -1
- package/native-src/src/{crypto → librats/crypto}/curve25519.c +6 -4
- package/native-src/src/{crypto → librats/crypto}/curve25519.h +6 -3
- package/native-src/src/librats/crypto/hkdf.c +266 -0
- package/native-src/src/{crypto → librats/crypto}/hkdf.h +19 -19
- package/native-src/src/{noise.cpp → librats/crypto/noise.cpp} +84 -73
- package/native-src/src/{noise.h → librats/crypto/noise.h} +18 -8
- package/native-src/src/{crypto → librats/crypto}/poly1305.c +47 -46
- package/native-src/src/librats/crypto/poly1305.h +37 -0
- package/native-src/src/{sha1.cpp → librats/crypto/sha1.cpp} +33 -1
- package/native-src/src/{sha1.h → librats/crypto/sha1.h} +14 -6
- package/native-src/src/{crypto → librats/crypto}/sha256.c +15 -14
- package/native-src/src/{crypto → librats/crypto}/sha256.h +8 -7
- package/native-src/src/{crypto → librats/crypto}/sha512.c +15 -14
- package/native-src/src/{crypto → librats/crypto}/sha512.h +8 -7
- package/native-src/src/librats/dht/announce.cpp +37 -0
- package/native-src/src/librats/dht/announce.h +41 -0
- package/native-src/src/librats/dht/bep42.cpp +109 -0
- package/native-src/src/librats/dht/bep42.h +48 -0
- package/native-src/src/librats/dht/dht.cpp +501 -0
- package/native-src/src/librats/dht/dht.h +119 -0
- package/native-src/src/librats/dht/dht_runner.cpp +103 -0
- package/native-src/src/librats/dht/dht_runner.h +71 -0
- package/native-src/src/librats/dht/dos_blocker.cpp +42 -0
- package/native-src/src/librats/dht/dos_blocker.h +47 -0
- package/native-src/src/librats/dht/find_peers.cpp +52 -0
- package/native-src/src/librats/dht/find_peers.h +73 -0
- package/native-src/src/librats/dht/id.h +167 -0
- package/native-src/src/{krpc.cpp → librats/dht/krpc.cpp} +32 -81
- package/native-src/src/{krpc.h → librats/dht/krpc.h} +19 -23
- package/native-src/src/librats/dht/log.h +38 -0
- package/native-src/src/librats/dht/node.cpp +473 -0
- package/native-src/src/librats/dht/node.h +164 -0
- package/native-src/src/librats/dht/node_entry.h +81 -0
- package/native-src/src/librats/dht/observer.h +72 -0
- package/native-src/src/librats/dht/persistence.cpp +90 -0
- package/native-src/src/librats/dht/persistence.h +32 -0
- package/native-src/src/librats/dht/routing_table.cpp +559 -0
- package/native-src/src/librats/dht/routing_table.h +185 -0
- package/native-src/src/librats/dht/rpc_manager.cpp +127 -0
- package/native-src/src/librats/dht/rpc_manager.h +77 -0
- package/native-src/src/librats/dht/storage.cpp +92 -0
- package/native-src/src/librats/dht/storage.h +74 -0
- package/native-src/src/librats/dht/transport.h +27 -0
- package/native-src/src/librats/dht/traversal.cpp +326 -0
- package/native-src/src/librats/dht/traversal.h +120 -0
- package/native-src/src/librats/dht/udp_transport.cpp +49 -0
- package/native-src/src/librats/dht/udp_transport.h +51 -0
- package/native-src/src/librats/mdns/log.h +22 -0
- package/native-src/src/{mdns.cpp → librats/mdns/mdns.cpp} +75 -40
- package/native-src/src/{mdns.h → librats/mdns/mdns.h} +9 -8
- package/native-src/src/{natpmp.cpp → librats/nat/natpmp.cpp} +12 -9
- package/native-src/src/{natpmp.h → librats/nat/natpmp.h} +3 -3
- package/native-src/src/{port_mapping.h → librats/nat/port_mapping.h} +3 -2
- package/native-src/src/{stun.cpp → librats/nat/stun.cpp} +4 -4
- package/native-src/src/{stun.h → librats/nat/stun.h} +1 -1
- package/native-src/src/{upnp.cpp → librats/nat/upnp.cpp} +6 -6
- package/native-src/src/{upnp.h → librats/nat/upnp.h} +2 -2
- package/native-src/src/librats/node/circuit_service.h +84 -0
- package/native-src/src/librats/node/config.h +110 -0
- package/native-src/src/librats/node/dial_service.h +54 -0
- package/native-src/src/librats/node/dialer.cpp +264 -0
- package/native-src/src/librats/node/dialer.h +188 -0
- package/native-src/src/librats/node/host_events.h +26 -0
- package/native-src/src/librats/node/identify.cpp +130 -0
- package/native-src/src/librats/node/identify.h +71 -0
- package/native-src/src/librats/node/nat_status.cpp +103 -0
- package/native-src/src/librats/node/nat_status.h +118 -0
- package/native-src/src/librats/node/node.cpp +865 -0
- package/native-src/src/librats/node/node.h +344 -0
- package/native-src/src/librats/node/node_context.h +33 -0
- package/native-src/src/librats/node/peer_network.h +91 -0
- package/native-src/src/librats/peer/peer.h +49 -0
- package/native-src/src/librats/peer/peer_book.cpp +181 -0
- package/native-src/src/librats/peer/peer_book.h +88 -0
- package/native-src/src/librats/peer/peer_id.cpp +72 -0
- package/native-src/src/librats/peer/peer_id.h +62 -0
- package/native-src/src/librats/peer/peer_info.h +37 -0
- package/native-src/src/librats/peer/peer_table.cpp +170 -0
- package/native-src/src/librats/peer/peer_table.h +148 -0
- package/native-src/src/librats/security/handshaker.h +66 -0
- package/native-src/src/librats/security/identity.h +43 -0
- package/native-src/src/librats/security/noise_security.cpp +122 -0
- package/native-src/src/librats/security/noise_security.h +37 -0
- package/native-src/src/librats/security/plaintext_security.h +106 -0
- package/native-src/src/librats/security/session.h +37 -0
- package/native-src/src/{storage.cpp → librats/storage/storage.cpp} +369 -522
- package/native-src/src/{storage.h → librats/storage/storage.h} +135 -299
- package/native-src/src/librats/subsystems/bittorrent.cpp +211 -0
- package/native-src/src/librats/subsystems/bittorrent.h +136 -0
- package/native-src/src/librats/subsystems/dht_discovery.cpp +202 -0
- package/native-src/src/librats/subsystems/dht_discovery.h +123 -0
- package/native-src/src/librats/subsystems/dht_service.h +36 -0
- package/native-src/src/librats/subsystems/file_transfer.cpp +972 -0
- package/native-src/src/librats/subsystems/file_transfer.h +367 -0
- package/native-src/src/librats/subsystems/hole_punch.cpp +605 -0
- package/native-src/src/librats/subsystems/hole_punch.h +290 -0
- package/native-src/src/librats/subsystems/hole_punch_service.h +38 -0
- package/native-src/src/librats/subsystems/mdns_discovery.cpp +66 -0
- package/native-src/src/librats/subsystems/mdns_discovery.h +55 -0
- package/native-src/src/librats/subsystems/message_json.cpp +112 -0
- package/native-src/src/librats/subsystems/message_json.h +88 -0
- package/native-src/src/librats/subsystems/peer_exchange.cpp +241 -0
- package/native-src/src/librats/subsystems/peer_exchange.h +136 -0
- package/native-src/src/librats/subsystems/ping_service.cpp +98 -0
- package/native-src/src/librats/subsystems/ping_service.h +66 -0
- package/native-src/src/librats/subsystems/port_mapping_service.cpp +192 -0
- package/native-src/src/librats/subsystems/port_mapping_service.h +84 -0
- package/native-src/src/librats/subsystems/pubsub.cpp +567 -0
- package/native-src/src/librats/subsystems/pubsub.h +175 -0
- package/native-src/src/librats/subsystems/reconnection.cpp +239 -0
- package/native-src/src/librats/subsystems/reconnection.h +126 -0
- package/native-src/src/librats/subsystems/relay.cpp +1142 -0
- package/native-src/src/librats/subsystems/relay.h +211 -0
- package/native-src/src/librats/subsystems/relay_service.h +46 -0
- package/native-src/src/librats/transport/connection.cpp +343 -0
- package/native-src/src/librats/transport/connection.h +283 -0
- package/native-src/src/librats/transport/link.h +96 -0
- package/native-src/src/librats/transport/reactor.cpp +588 -0
- package/native-src/src/librats/transport/reactor.h +262 -0
- package/native-src/src/librats/transport/reactor_pool.h +81 -0
- package/native-src/src/librats/transport/relay_link.cpp +208 -0
- package/native-src/src/librats/transport/relay_link.h +303 -0
- package/native-src/src/librats/transport/tcp_link.cpp +49 -0
- package/native-src/src/librats/transport/tcp_link.h +43 -0
- package/native-src/src/librats/transport/udp_mux.cpp +617 -0
- package/native-src/src/librats/transport/udp_mux.h +363 -0
- package/native-src/src/librats/transport/udp_packet.cpp +121 -0
- package/native-src/src/librats/transport/udp_packet.h +190 -0
- package/native-src/src/librats/transport/udp_stream.cpp +1194 -0
- package/native-src/src/librats/transport/udp_stream.h +614 -0
- package/native-src/src/librats/util/features.h.in +51 -0
- package/native-src/src/{fs.cpp → librats/util/fs.cpp} +51 -3
- package/native-src/src/librats/util/fs.h +136 -0
- package/native-src/src/librats/util/json.cpp +1002 -0
- package/native-src/src/librats/util/json.h +444 -0
- package/native-src/src/{logger.cpp → librats/util/logger.cpp} +1 -1
- package/native-src/src/{logger.h → librats/util/logger.h} +43 -31
- package/native-src/src/{network_monitor.cpp → librats/util/network_monitor.cpp} +12 -4
- package/native-src/src/{network_monitor.h → librats/util/network_monitor.h} +2 -1
- package/native-src/src/{network_utils.cpp → librats/util/network_utils.cpp} +38 -23
- package/native-src/src/{network_utils.h → librats/util/network_utils.h} +15 -8
- package/native-src/src/{os.cpp → librats/util/os.cpp} +48 -18
- package/native-src/src/librats/util/rats_export.h +69 -0
- package/native-src/src/{version.cpp → librats/util/version.cpp} +2 -2
- package/native-src/src/{version.h.in → librats/util/version.h.in} +1 -1
- package/native-src/src/librats/wire/frame.cpp +76 -0
- package/native-src/src/librats/wire/frame.h +111 -0
- package/native-src/src/librats/wire/message_router.cpp +45 -0
- package/native-src/src/librats/wire/message_router.h +47 -0
- package/package.json +5 -4
- package/scripts/build-librats.js +1 -0
- package/scripts/postinstall.js +3 -3
- package/scripts/prepare-package.js +4 -4
- package/scripts/verify-installation.js +63 -105
- package/src/librats_node.cpp +1067 -1323
- package/native-src/src/bencode.cpp +0 -485
- package/native-src/src/bencode.h +0 -145
- package/native-src/src/bittorrent.cpp +0 -14
- package/native-src/src/bittorrent.h +0 -74
- package/native-src/src/bt_bitfield.cpp +0 -372
- package/native-src/src/bt_bitfield.h +0 -316
- package/native-src/src/bt_choker.cpp +0 -228
- package/native-src/src/bt_choker.h +0 -147
- package/native-src/src/bt_client.cpp +0 -1047
- package/native-src/src/bt_client.h +0 -445
- package/native-src/src/bt_create_torrent.cpp +0 -677
- package/native-src/src/bt_create_torrent.h +0 -473
- package/native-src/src/bt_extension.cpp +0 -469
- package/native-src/src/bt_extension.h +0 -309
- package/native-src/src/bt_file_storage.cpp +0 -261
- package/native-src/src/bt_file_storage.h +0 -298
- package/native-src/src/bt_handshake.cpp +0 -134
- package/native-src/src/bt_handshake.h +0 -157
- package/native-src/src/bt_messages.cpp +0 -364
- package/native-src/src/bt_messages.h +0 -324
- package/native-src/src/bt_network.cpp +0 -1007
- package/native-src/src/bt_network.h +0 -417
- package/native-src/src/bt_peer_connection.cpp +0 -742
- package/native-src/src/bt_peer_connection.h +0 -592
- package/native-src/src/bt_piece_picker.cpp +0 -786
- package/native-src/src/bt_piece_picker.h +0 -473
- package/native-src/src/bt_resume_data.cpp +0 -410
- package/native-src/src/bt_resume_data.h +0 -249
- package/native-src/src/bt_torrent.cpp +0 -2120
- package/native-src/src/bt_torrent.h +0 -641
- package/native-src/src/bt_torrent_info.cpp +0 -659
- package/native-src/src/bt_torrent_info.h +0 -418
- package/native-src/src/bt_types.h +0 -621
- package/native-src/src/chained_send_buffer.cpp +0 -75
- package/native-src/src/chained_send_buffer.h +0 -137
- package/native-src/src/crypto/hkdf.c +0 -266
- package/native-src/src/crypto/poly1305.h +0 -36
- package/native-src/src/dht.cpp +0 -3311
- package/native-src/src/dht.h +0 -717
- package/native-src/src/disk_io.cpp +0 -632
- package/native-src/src/disk_io.h +0 -315
- package/native-src/src/file_transfer.cpp +0 -1415
- package/native-src/src/file_transfer.h +0 -286
- package/native-src/src/fs.h +0 -108
- package/native-src/src/gossipsub.cpp +0 -1139
- package/native-src/src/gossipsub.h +0 -403
- package/native-src/src/ice.cpp +0 -893
- package/native-src/src/ice.h +0 -559
- package/native-src/src/json.hpp +0 -25526
- package/native-src/src/librats.cpp +0 -2378
- package/native-src/src/librats.h +0 -2324
- package/native-src/src/librats_bittorrent.cpp +0 -601
- package/native-src/src/librats_c.cpp +0 -1557
- package/native-src/src/librats_c.h +0 -323
- package/native-src/src/librats_discovery.cpp +0 -402
- package/native-src/src/librats_encryption.cpp +0 -275
- package/native-src/src/librats_file_transfer.cpp +0 -144
- package/native-src/src/librats_gossipsub.cpp +0 -289
- package/native-src/src/librats_ice.cpp +0 -213
- package/native-src/src/librats_log_macros.h +0 -36
- package/native-src/src/librats_logging.cpp +0 -173
- package/native-src/src/librats_mdns.cpp +0 -166
- package/native-src/src/librats_persistence.cpp +0 -796
- package/native-src/src/librats_portmap.cpp +0 -419
- package/native-src/src/librats_reconnection.cpp +0 -218
- package/native-src/src/librats_statistic.cpp +0 -105
- package/native-src/src/librats_storage.cpp +0 -189
- package/native-src/src/rats_export.h +0 -17
- package/native-src/src/receive_buffer.cpp +0 -82
- package/native-src/src/receive_buffer.h +0 -127
- package/native-src/src/socket.h +0 -228
- package/native-src/src/threadmanager.cpp +0 -105
- package/native-src/src/threadmanager.h +0 -53
- package/native-src/src/tracker.cpp +0 -1264
- package/native-src/src/tracker.h +0 -319
- package/native-src/src/turn.cpp +0 -762
- package/native-src/src/turn.h +0 -460
- package/native-src/src/wakeup_pipe.h +0 -60
- /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
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
//
|
|
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
|
-
|
|
371
|
-
|
|
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
|
|
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),
|
|
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
|
-
|
|
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
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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
|
-
|
|
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
|
-
|
|
762
|
-
if (n
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|