librats 0.7.2 → 0.8.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.
@@ -9,7 +9,6 @@
9
9
  #include <winsock2.h>
10
10
  #include <ws2tcpip.h>
11
11
  #else
12
- #include <sys/select.h>
13
12
  #include <netinet/tcp.h>
14
13
  #include <fcntl.h>
15
14
  #include <errno.h>
@@ -38,6 +37,21 @@ BtNetworkManager::~BtNetworkManager() {
38
37
  stop();
39
38
  }
40
39
 
40
+ //=============================================================================
41
+ // TCP_NODELAY helper
42
+ //=============================================================================
43
+
44
+ bool BtNetworkManager::set_tcp_nodelay(socket_t sock) {
45
+ int flag = 1;
46
+ int result = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
47
+ reinterpret_cast<const char*>(&flag), sizeof(flag));
48
+ if (result < 0) {
49
+ LOG_NET_WARN("Failed to set TCP_NODELAY on socket " + std::to_string(static_cast<int>(sock)));
50
+ return false;
51
+ }
52
+ return true;
53
+ }
54
+
41
55
  //=============================================================================
42
56
  // Lifecycle
43
57
  //=============================================================================
@@ -51,6 +65,14 @@ bool BtNetworkManager::start() {
51
65
  return false;
52
66
  }
53
67
 
68
+ // Create the I/O poller
69
+ poller_ = IOPoller::create();
70
+ if (!poller_) {
71
+ LOG_NET_ERROR("Failed to create I/O poller");
72
+ return false;
73
+ }
74
+ LOG_NET_INFO("Using I/O backend: " + std::string(poller_->name()));
75
+
54
76
  // Create listen socket if incoming enabled
55
77
  if (config_.enable_incoming) {
56
78
  listen_socket_ = create_tcp_server(config_.listen_port, 50);
@@ -74,6 +96,10 @@ bool BtNetworkManager::start() {
74
96
  actual_listen_port_ = config_.listen_port;
75
97
  }
76
98
 
99
+ // Register listen socket with poller (interested in incoming connections)
100
+ poller_->add(listen_socket_, PollIn);
101
+ poller_state_[listen_socket_] = PollIn;
102
+
77
103
  LOG_NET_INFO("Listening on port " + std::to_string(actual_listen_port_));
78
104
  }
79
105
 
@@ -109,12 +135,14 @@ void BtNetworkManager::stop() {
109
135
  event.connection = ctx.connection;
110
136
  disconnected_events.push_back(std::move(event));
111
137
  }
138
+ poller_->remove(socket);
112
139
  close_socket(socket, true);
113
140
  }
114
141
  connections_.clear();
115
142
 
116
143
  // Close connecting sockets
117
144
  for (auto& [socket, pending] : connecting_) {
145
+ poller_->remove(socket);
118
146
  close_socket(socket, true);
119
147
  }
120
148
  connecting_.clear();
@@ -126,10 +154,14 @@ void BtNetworkManager::stop() {
126
154
 
127
155
  // Close listen socket
128
156
  if (is_valid_socket(listen_socket_)) {
157
+ poller_->remove(listen_socket_);
129
158
  close_socket(listen_socket_);
130
159
  listen_socket_ = INVALID_SOCKET_VALUE;
131
160
  }
132
161
 
162
+ poller_state_.clear();
163
+ poller_.reset();
164
+
133
165
  LOG_NET_INFO("Network manager stopped");
134
166
  }
135
167
 
@@ -285,8 +317,22 @@ bool BtNetworkManager::send_to_peer(socket_t socket, const std::vector<uint8_t>&
285
317
  return false;
286
318
  }
287
319
 
320
+ bool was_empty = send_buf.empty();
321
+
288
322
  // Append directly to connection's send buffer (single source of truth)
289
323
  send_buf.append(data.data(), data.size());
324
+
325
+ // If buffer was empty, we need to start watching for writability.
326
+ // Update poller to add PollOut interest.
327
+ if (was_empty && poller_) {
328
+ uint32_t desired = PollIn | PollOut;
329
+ auto ps = poller_state_.find(socket);
330
+ if (ps != poller_state_.end() && ps->second != desired) {
331
+ poller_->modify(socket, desired);
332
+ ps->second = desired;
333
+ }
334
+ }
335
+
290
336
  return true;
291
337
  }
292
338
 
@@ -300,6 +346,45 @@ size_t BtNetworkManager::pending_connect_count() const {
300
346
  return pending_connects_.size() + connecting_.size();
301
347
  }
302
348
 
349
+ //=============================================================================
350
+ // Poller State Sync
351
+ //=============================================================================
352
+
353
+ void BtNetworkManager::sync_poller() {
354
+ // Called under mutex. Ensures poller registrations match current state.
355
+ // This is the primary sync point for connections whose send buffer
356
+ // state may have changed (e.g., after flush_send_buffer drains it).
357
+
358
+ for (auto& [socket, ctx] : connections_) {
359
+ // Determine desired events
360
+ uint32_t desired = PollIn; // Always interested in reading
361
+ if (ctx.connection && !ctx.connection->send_buffer().empty()) {
362
+ desired |= PollOut; // Also interested in writing
363
+ }
364
+
365
+ auto ps = poller_state_.find(socket);
366
+ if (ps == poller_state_.end()) {
367
+ // Not yet registered (shouldn't happen normally, but be safe)
368
+ poller_->add(socket, desired);
369
+ poller_state_[socket] = desired;
370
+ } else if (ps->second != desired) {
371
+ poller_->modify(socket, desired);
372
+ ps->second = desired;
373
+ }
374
+ }
375
+
376
+ // Connecting sockets: interested in write (connect complete) + error
377
+ for (auto& [socket, pending] : connecting_) {
378
+ auto ps = poller_state_.find(socket);
379
+ uint32_t desired = PollOut;
380
+ if (ps == poller_state_.end()) {
381
+ poller_->add(socket, desired);
382
+ poller_state_[socket] = desired;
383
+ }
384
+ // Connecting sockets don't change their interest, no modify needed
385
+ }
386
+ }
387
+
303
388
  //=============================================================================
304
389
  // I/O Loop
305
390
  //=============================================================================
@@ -312,89 +397,37 @@ void BtNetworkManager::io_loop() {
312
397
  std::vector<DataEvent> data_events;
313
398
  std::vector<DisconnectedEvent> disconnected_events;
314
399
 
400
+ // Poll results buffer
401
+ static constexpr int MAX_POLL_EVENTS = 256;
402
+ PollResult poll_results[MAX_POLL_EVENTS];
403
+
315
404
  while (running_) {
316
405
  // Clear event vectors for this iteration
317
406
  connected_events.clear();
318
407
  data_events.clear();
319
408
  disconnected_events.clear();
320
409
 
321
- // Process pending connect queue
410
+ // Process pending connect queue (adds new sockets to poller)
322
411
  process_pending_connects();
323
412
 
324
- // Build fd_sets
325
- fd_set read_fds, write_fds, error_fds;
326
- FD_ZERO(&read_fds);
327
- FD_ZERO(&write_fds);
328
- FD_ZERO(&error_fds);
329
-
330
- socket_t max_fd = 0;
331
-
413
+ // Sync poller state (update write interest based on send buffer state)
332
414
  {
333
415
  std::lock_guard<std::mutex> lock(mutex_);
334
-
335
- // Add listen socket
336
- if (is_valid_socket(listen_socket_)) {
337
- FD_SET(listen_socket_, &read_fds);
338
- if (listen_socket_ > max_fd) max_fd = listen_socket_;
339
- }
340
-
341
- // Add connected sockets
342
- for (auto& [socket, ctx] : connections_) {
343
- FD_SET(socket, &read_fds);
344
- FD_SET(socket, &error_fds);
345
-
346
- // Only monitor for write if connection has data to send
347
- if (ctx.connection && !ctx.connection->send_buffer().empty()) {
348
- FD_SET(socket, &write_fds);
349
- }
350
-
351
- if (socket > max_fd) max_fd = socket;
352
- }
353
-
354
- // Add connecting sockets (monitor for write = connect complete)
355
- for (auto& [socket, pending] : connecting_) {
356
- FD_SET(socket, &write_fds);
357
- FD_SET(socket, &error_fds);
358
- if (socket > max_fd) max_fd = socket;
359
- }
416
+ sync_poller();
360
417
  }
361
418
 
362
- // If no sockets to monitor, sleep to avoid CPU spinning.
363
- // This happens when enable_incoming=false and no connections yet.
364
- if (max_fd == 0) {
365
- std::this_thread::sleep_for(
366
- std::chrono::milliseconds(config_.select_timeout_ms));
367
- continue;
368
- }
369
-
370
- // Select with timeout
371
- struct timeval timeout;
372
- timeout.tv_sec = 0;
373
- timeout.tv_usec = config_.select_timeout_ms * 1000;
419
+ // Wait for I/O events (NO mutex held this is the blocking call)
420
+ int num_events = poller_->wait(poll_results, MAX_POLL_EVENTS,
421
+ config_.poll_timeout_ms);
374
422
 
375
- int result = select(static_cast<int>(max_fd) + 1,
376
- &read_fds, &write_fds, &error_fds, &timeout);
377
-
378
- if (result < 0) {
379
- #ifdef _WIN32
380
- int err = WSAGetLastError();
381
- if (err != WSAEINTR) {
382
- LOG_NET_ERROR("select() failed: " + std::to_string(err));
383
- // Avoid spinning on persistent errors
384
- std::this_thread::sleep_for(std::chrono::milliseconds(10));
385
- }
386
- #else
387
- if (errno != EINTR) {
388
- LOG_NET_ERROR("select() failed: " + std::string(strerror(errno)));
389
- // Avoid spinning on persistent errors
390
- std::this_thread::sleep_for(std::chrono::milliseconds(10));
391
- }
392
- #endif
423
+ if (num_events < 0) {
424
+ // Error (EINTR already filtered by poller implementations)
425
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
393
426
  continue;
394
427
  }
395
428
 
396
- if (result == 0) {
397
- // Timeout - check for connection timeouts
429
+ if (num_events == 0) {
430
+ // Timeout check for connection timeouts
398
431
  auto now = std::chrono::steady_clock::now();
399
432
 
400
433
  std::lock_guard<std::mutex> lock(mutex_);
@@ -410,6 +443,8 @@ void BtNetworkManager::io_loop() {
410
443
 
411
444
  for (socket_t s : timed_out) {
412
445
  LOG_NET_DEBUG("Connection timed out to " + connecting_[s].ip);
446
+ poller_->remove(s);
447
+ poller_state_.erase(s);
413
448
  close_socket(s);
414
449
  connecting_.erase(s);
415
450
  }
@@ -417,103 +452,149 @@ void BtNetworkManager::io_loop() {
417
452
  continue;
418
453
  }
419
454
 
420
- LOG_NET_DEBUG("process_pending_connects: select() returned " + std::to_string(result) + " ready");
455
+ LOG_NET_DEBUG("I/O poll returned " + std::to_string(num_events) + " events");
421
456
 
422
- // Handle listen socket
423
- if (is_valid_socket(listen_socket_) && FD_ISSET(listen_socket_, &read_fds)) {
424
- accept_incoming();
425
- }
426
-
427
- // Handle connecting sockets
457
+ // Process all events under mutex
428
458
  {
429
459
  std::lock_guard<std::mutex> lock(mutex_);
430
460
 
431
- std::vector<socket_t> completed;
432
- for (auto& [socket, pending] : connecting_) {
433
- if (FD_ISSET(socket, &error_fds)) {
434
- completed.push_back(socket);
435
- } else if (FD_ISSET(socket, &write_fds)) {
436
- // Check if connection succeeded
437
- int sock_error = 0;
438
- socklen_t len = sizeof(sock_error);
439
- getsockopt(socket, SOL_SOCKET, SO_ERROR,
440
- reinterpret_cast<char*>(&sock_error), &len);
461
+ std::vector<socket_t> to_close;
462
+
463
+ for (int i = 0; i < num_events; ++i) {
464
+ socket_t fd = poll_results[i].fd;
465
+ uint32_t events = poll_results[i].events;
466
+
467
+ //--------------------------------------------------------------
468
+ // Listen socket — accept incoming
469
+ //--------------------------------------------------------------
470
+ if (fd == listen_socket_) {
471
+ if (events & PollIn) {
472
+ // NOTE: accept_incoming acquires mutex internally,
473
+ // but we are already holding it. We need to call
474
+ // the internal version without re-locking.
475
+ accept_incoming();
476
+ }
477
+ continue;
478
+ }
479
+
480
+ //--------------------------------------------------------------
481
+ // Connecting socket — check connect completion
482
+ //--------------------------------------------------------------
483
+ auto connecting_it = connecting_.find(fd);
484
+ if (connecting_it != connecting_.end()) {
485
+ auto& pending = connecting_it->second;
441
486
 
442
- if (sock_error == 0) {
443
- LOG_NET_INFO("Connected to peer " + pending.ip + ":" +
444
- std::to_string(pending.port));
445
-
446
- // Create connection object
447
- auto conn = std::make_shared<BtPeerConnection>(
448
- pending.info_hash, pending.peer_id, pending.num_pieces);
449
- conn->set_address(pending.ip, pending.port);
450
- conn->set_socket(static_cast<int>(socket));
451
-
452
- // Create socket context
453
- SocketContext ctx;
454
- ctx.socket = socket;
455
- ctx.info_hash = pending.info_hash;
456
- ctx.connection = conn;
457
- ctx.state = NetConnectionState::Handshaking;
458
- ctx.incoming = false;
459
- ctx.connected_at = std::chrono::steady_clock::now();
460
- ctx.last_activity = ctx.connected_at;
461
-
462
- // Send handshake via connection method (sets handshake_sent_ flag)
463
- LOG_NET_DEBUG("Sending handshake (" +
464
- std::to_string(BT_HANDSHAKE_SIZE) + " bytes) to " +
465
- pending.ip);
466
- conn->start_handshake();
487
+ if (events & PollErr) {
488
+ // Connection failed
489
+ LOG_NET_DEBUG("Connection failed (poll error) to " + pending.ip);
490
+ poller_->remove(fd);
491
+ poller_state_.erase(fd);
492
+ close_socket(fd);
493
+ connecting_.erase(connecting_it);
494
+ continue;
495
+ }
496
+
497
+ if (events & PollOut) {
498
+ // Check if connection succeeded
499
+ int sock_error = 0;
500
+ socklen_t len = sizeof(sock_error);
501
+ getsockopt(fd, SOL_SOCKET, SO_ERROR,
502
+ reinterpret_cast<char*>(&sock_error), &len);
467
503
 
468
- connections_[socket] = std::move(ctx);
504
+ if (sock_error == 0) {
505
+ LOG_NET_INFO("Connected to peer " + pending.ip + ":" +
506
+ std::to_string(pending.port));
507
+
508
+ // Set TCP_NODELAY for low latency
509
+ set_tcp_nodelay(fd);
510
+
511
+ // Create connection object
512
+ auto conn = std::make_shared<BtPeerConnection>(
513
+ pending.info_hash, pending.peer_id, pending.num_pieces);
514
+ conn->set_address(pending.ip, pending.port);
515
+ conn->set_socket(static_cast<int>(fd));
516
+
517
+ // Create socket context
518
+ SocketContext ctx;
519
+ ctx.socket = fd;
520
+ ctx.info_hash = pending.info_hash;
521
+ ctx.connection = conn;
522
+ ctx.state = NetConnectionState::Handshaking;
523
+ ctx.incoming = false;
524
+ ctx.connected_at = std::chrono::steady_clock::now();
525
+ ctx.last_activity = ctx.connected_at;
526
+
527
+ // Send handshake
528
+ LOG_NET_DEBUG("Sending handshake (" +
529
+ std::to_string(BT_HANDSHAKE_SIZE) + " bytes) to " +
530
+ pending.ip);
531
+ conn->start_handshake();
532
+
533
+ connections_[fd] = std::move(ctx);
534
+
535
+ // Update poller: now interested in read (+ write if handshake queued)
536
+ uint32_t desired = PollIn;
537
+ if (!conn->send_buffer().empty()) {
538
+ desired |= PollOut;
539
+ }
540
+ poller_->modify(fd, desired);
541
+ poller_state_[fd] = desired;
542
+
543
+ LOG_NET_DEBUG("Handshake queued to " + pending.ip);
544
+ } else {
545
+ LOG_NET_DEBUG("Connection failed to " + pending.ip + ": " +
546
+ std::to_string(sock_error));
547
+ poller_->remove(fd);
548
+ poller_state_.erase(fd);
549
+ close_socket(fd);
550
+ }
469
551
 
470
- LOG_NET_DEBUG("Handshake queued to " + pending.ip);
471
- } else {
472
- LOG_NET_DEBUG("Connection failed to " + pending.ip + ": " +
473
- std::to_string(sock_error));
474
- close_socket(socket);
552
+ connecting_.erase(connecting_it);
475
553
  }
476
- completed.push_back(socket);
554
+ continue;
477
555
  }
478
- }
479
-
480
- for (socket_t s : completed) {
481
- connecting_.erase(s);
482
- }
483
- }
484
-
485
- // Handle active connections (collect events under mutex)
486
- // IMPORTANT: Never call close_connection_internal() during iteration!
487
- // Collect sockets to close and process AFTER the loop to avoid iterator invalidation.
488
- {
489
- std::lock_guard<std::mutex> lock(mutex_);
490
-
491
- std::vector<socket_t> to_close;
492
-
493
- for (auto& [socket, ctx] : connections_) {
556
+
557
+ //--------------------------------------------------------------
558
+ // Active connection handle read/write/error
559
+ //--------------------------------------------------------------
560
+ auto conn_it = connections_.find(fd);
561
+ if (conn_it == connections_.end()) {
562
+ // Unknown fd — remove from poller
563
+ poller_->remove(fd);
564
+ poller_state_.erase(fd);
565
+ continue;
566
+ }
567
+
494
568
  bool should_close = false;
495
569
 
496
- if (FD_ISSET(socket, &error_fds)) {
497
- if (ctx.connection) {
498
- LOG_NET_DEBUG("Error on socket for " + ctx.connection->ip());
570
+ if (events & PollErr) {
571
+ if (conn_it->second.connection) {
572
+ LOG_NET_DEBUG("Error on socket for " + conn_it->second.connection->ip());
499
573
  }
500
574
  should_close = true;
501
575
  }
502
576
 
503
- if (!should_close && FD_ISSET(socket, &read_fds)) {
504
- should_close = handle_readable(socket, connected_events, data_events);
577
+ if (!should_close && (events & PollHup)) {
578
+ if (conn_it->second.connection) {
579
+ LOG_NET_DEBUG("Peer hung up: " + conn_it->second.connection->ip());
580
+ }
581
+ should_close = true;
505
582
  }
506
583
 
507
- if (!should_close && FD_ISSET(socket, &write_fds)) {
508
- should_close = handle_writable(socket);
584
+ if (!should_close && (events & PollIn)) {
585
+ should_close = handle_readable(fd, connected_events, data_events);
586
+ }
587
+
588
+ if (!should_close && (events & PollOut)) {
589
+ should_close = handle_writable(fd);
509
590
  }
510
591
 
511
592
  if (should_close) {
512
- to_close.push_back(socket);
593
+ to_close.push_back(fd);
513
594
  }
514
595
  }
515
596
 
516
- // Close connections AFTER iteration to avoid iterator invalidation
597
+ // Close connections AFTER processing all events to avoid iterator invalidation
517
598
  for (socket_t s : to_close) {
518
599
  close_connection_internal(s, disconnected_events);
519
600
  }
@@ -565,6 +646,10 @@ void BtNetworkManager::process_pending_connects() {
565
646
  pending.socket = sock;
566
647
  pending.start_time = std::chrono::steady_clock::now();
567
648
 
649
+ // Register with poller: interested in write (connect complete)
650
+ poller_->add(sock, PollOut);
651
+ poller_state_[sock] = PollOut;
652
+
568
653
  connecting_[sock] = std::move(pending);
569
654
  }
570
655
 
@@ -589,6 +674,9 @@ socket_t BtNetworkManager::create_connect_socket(const std::string& ip, uint16_t
589
674
  return INVALID_SOCKET_VALUE;
590
675
  }
591
676
 
677
+ // Set TCP_NODELAY immediately (before connect)
678
+ set_tcp_nodelay(sock);
679
+
592
680
  // Resolve and connect
593
681
  std::string resolved = network_utils::resolve_hostname(ip);
594
682
  if (resolved.empty()) {
@@ -632,19 +720,18 @@ socket_t BtNetworkManager::create_connect_socket(const std::string& ip, uint16_t
632
720
  }
633
721
 
634
722
  void BtNetworkManager::accept_incoming() {
723
+ // NOTE: called with mutex_ already held from io_loop
724
+
635
725
  socket_t client = accept_client(listen_socket_);
636
726
  if (!is_valid_socket(client)) {
637
727
  return;
638
728
  }
639
729
 
640
730
  // Check connection limit
641
- {
642
- std::lock_guard<std::mutex> lock(mutex_);
643
- if (connections_.size() >= config_.max_connections) {
644
- LOG_NET_DEBUG("Connection limit reached, rejecting incoming");
645
- close_socket(client);
646
- return;
647
- }
731
+ if (connections_.size() >= config_.max_connections) {
732
+ LOG_NET_DEBUG("Connection limit reached, rejecting incoming");
733
+ close_socket(client);
734
+ return;
648
735
  }
649
736
 
650
737
  // Set non-blocking
@@ -653,6 +740,9 @@ void BtNetworkManager::accept_incoming() {
653
740
  return;
654
741
  }
655
742
 
743
+ // Set TCP_NODELAY for low latency
744
+ set_tcp_nodelay(client);
745
+
656
746
  // Get peer address
657
747
  std::string peer_addr = get_peer_address(client);
658
748
  std::string ip;
@@ -666,8 +756,6 @@ void BtNetworkManager::accept_incoming() {
666
756
 
667
757
  LOG_NET_INFO("Accepted incoming connection from " + peer_addr);
668
758
 
669
- std::lock_guard<std::mutex> lock(mutex_);
670
-
671
759
  // Create connection object immediately (info_hash unknown until handshake)
672
760
  auto conn = std::make_shared<BtPeerConnection>(config_.peer_id);
673
761
  conn->set_address(ip, port);
@@ -687,6 +775,10 @@ void BtNetworkManager::accept_incoming() {
687
775
  ctx.connection = conn;
688
776
 
689
777
  connections_[client] = std::move(ctx);
778
+
779
+ // Register with poller (interested in reading handshake)
780
+ poller_->add(client, PollIn);
781
+ poller_state_[client] = PollIn;
690
782
  }
691
783
 
692
784
  void BtNetworkManager::on_incoming_info_hash(BtPeerConnection* conn, const BtInfoHash& info_hash) {
@@ -735,48 +827,55 @@ bool BtNetworkManager::handle_readable(socket_t socket,
735
827
  return true; // Should close
736
828
  }
737
829
 
738
- // Receive directly into connection's buffer
830
+ // Drain all available data from the kernel buffer (loop until EWOULDBLOCK)
739
831
  auto& recv_buf = ctx.connection->recv_buffer();
832
+ bool got_data = false;
740
833
 
741
- // Ensure we have space for incoming data
742
- const size_t recv_size = 16384;
743
- recv_buf.ensure_space(recv_size);
744
-
745
- // Receive directly into connection's buffer - NO COPY!
746
- int bytes = recv(socket, reinterpret_cast<char*>(recv_buf.write_ptr()),
747
- static_cast<int>(recv_buf.write_space()), 0);
748
-
749
- if (bytes <= 0) {
834
+ while (true) {
835
+ const size_t recv_size = 16384;
836
+ recv_buf.ensure_space(recv_size);
837
+
838
+ int bytes = recv(socket, reinterpret_cast<char*>(recv_buf.write_ptr()),
839
+ static_cast<int>(recv_buf.write_space()), 0);
840
+
841
+ if (bytes > 0) {
842
+ recv_buf.received(bytes);
843
+ got_data = true;
844
+
845
+ LOG_NET_DEBUG("handle_readable: recv " + std::to_string(bytes) +
846
+ " bytes from " + ctx.connection->ip());
847
+ continue; // Try to read more
848
+ }
849
+
750
850
  if (bytes == 0) {
751
- LOG_NET_DEBUG("Connection closed by peer");
752
- } else {
851
+ // Peer closed connection gracefully
852
+ LOG_NET_DEBUG("Connection closed by peer: " + ctx.connection->ip());
853
+ return true; // Should close
854
+ }
855
+
856
+ // bytes < 0: error
753
857
  #ifdef _WIN32
754
- int err = WSAGetLastError();
755
- if (err != WSAEWOULDBLOCK) {
756
- LOG_NET_DEBUG("Receive error: " + std::to_string(err));
757
- } else {
758
- return false; // Would block, try again later
759
- }
858
+ int err = WSAGetLastError();
859
+ if (err == WSAEWOULDBLOCK) {
860
+ break; // No more data available, exit recv loop
861
+ }
862
+ LOG_NET_DEBUG("Receive error: " + std::to_string(err));
760
863
  #else
761
- if (errno != EAGAIN && errno != EWOULDBLOCK) {
762
- LOG_NET_DEBUG("Receive error: " + std::string(strerror(errno)));
763
- } else {
764
- return false; // Would block, try again later
765
- }
766
- #endif
864
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
865
+ break; // No more data available, exit recv loop
767
866
  }
768
-
769
- return true; // Should close
867
+ LOG_NET_DEBUG("Receive error: " + std::string(strerror(errno)));
868
+ #endif
869
+ return true; // Should close on real errors
770
870
  }
771
871
 
772
- // Mark bytes as received
773
- recv_buf.received(bytes);
774
- ctx.last_activity = std::chrono::steady_clock::now();
872
+ if (!got_data) {
873
+ return false; // Nothing received, no processing needed
874
+ }
775
875
 
776
- LOG_NET_DEBUG("handle_readable: recv " + std::to_string(bytes) +
777
- " bytes from " + ctx.connection->ip());
876
+ ctx.last_activity = std::chrono::steady_clock::now();
778
877
 
779
- // Process the data (handshake and messages)
878
+ // Process all received data (handshake and messages)
780
879
  return handle_peer_data(ctx, connected_events, data_events);
781
880
  }
782
881
 
@@ -855,6 +954,16 @@ bool BtNetworkManager::flush_send_buffer(SocketContext& ctx) {
855
954
  if (sent > 0) {
856
955
  send_buf.pop_front(static_cast<size_t>(sent));
857
956
  ctx.last_activity = std::chrono::steady_clock::now();
957
+
958
+ // If send buffer is now empty, remove PollOut interest to avoid busy-looping
959
+ if (send_buf.empty()) {
960
+ uint32_t desired = PollIn;
961
+ auto ps = poller_state_.find(ctx.socket);
962
+ if (ps != poller_state_.end() && ps->second != desired) {
963
+ poller_->modify(ctx.socket, desired);
964
+ ps->second = desired;
965
+ }
966
+ }
858
967
  } else if (sent < 0) {
859
968
  #ifdef _WIN32
860
969
  int err = WSAGetLastError();
@@ -887,6 +996,10 @@ void BtNetworkManager::close_connection_internal(socket_t socket,
887
996
  disconnected_events.push_back(std::move(event));
888
997
  }
889
998
 
999
+ // Remove from poller before closing socket
1000
+ poller_->remove(socket);
1001
+ poller_state_.erase(socket);
1002
+
890
1003
  close_socket(socket, true);
891
1004
  connections_.erase(it);
892
1005
  }