Skip to content

Conversation

@ibigbug
Copy link
Member

@ibigbug ibigbug commented Aug 8, 2025

🤔 This is a ...

  • New feature
  • Bug fix
  • Performance optimization
  • Enhancement feature
  • Refactoring
  • Code style optimization
  • Test Case
  • Branch merge
  • Workflow
  • Other (about what?)

🔗 Related issue link

💡 Background and solution

📝 Changelog

☑️ Self-Check before Merge

⚠️ Please check all items below before requesting a reviewing. ⚠️

  • Doc is updated/provided or not needed
  • Changelog is provided or not needed

@ibigbug ibigbug requested a review from Copilot August 8, 2025 19:46
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR addresses performance degradation in the TUN network stack by replacing spin locks with a lock-free ring buffer implementation. The changes remove synchronization bottlenecks that were causing performance issues in TCP stream handling.

Key changes:

  • Replaced spin lock-based Protected<RingBuffer> with a lock-free LockFreeRingBuffer implementation
  • Simplified async read/write operations by removing lock contention
  • Added debugging capabilities and example improvements for better testing

Reviewed Changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
clash-netstack/src/ring_buffer.rs New lock-free ring buffer implementation using atomic operations
clash-netstack/src/spin_lock.rs Removed spin lock implementation entirely
clash-netstack/src/tcp_stream.rs Updated to use lock-free buffers and simplified async I/O operations
clash-netstack/src/tcp_listener.rs Modified to use new ring buffer type and removed lock usage
clash-netstack/src/stack.rs Added Debug implementation for IfaceEvent enum
clash-netstack/src/lib.rs Updated module imports to reflect structural changes
clash-netstack/examples/with_tun_rs.rs Enhanced example with better error handling and Linux support
clash-netstack/examples/README.md Added performance benchmark results


// Calculate available space
let available = if read_pos <= write_pos {
self.capacity - write_pos + read_pos - 1
Copy link

Copilot AI Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The available space calculation is incorrect when read_pos <= write_pos. It should be self.capacity - (write_pos - read_pos) - 1 to properly account for the one slot that must remain empty to distinguish full from empty.

Suggested change
self.capacity - write_pos + read_pos - 1
self.capacity - (write_pos - read_pos) - 1

Copilot uses AI. Check for mistakes.

std::task::Poll::Ready(Ok(()))
})
if read_buf.is_empty() {
Copy link

Copilot AI Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a potential race condition here. The buffer could become non-empty between the is_empty() check and the return Poll::Pending. Consider using atomic operations or checking again after registering the waker.

Copilot uses AI. Check for mistakes.
let n = buf_lock.enqueue_slice(buf);
std::task::Poll::Ready(Ok(n))
})
if send_buf.is_full() {
Copy link

Copilot AI Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar race condition as in poll_read. The buffer state could change between the is_full() check and returning Poll::Pending. The double-check pattern that was removed provided protection against this.

Copilot uses AI. Check for mistakes.

self.stack_notifier
.send(IfaceEvent::TcpSocketReady)
.expect("Failed to notify TCP socket ready");
Copy link

Copilot AI Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using expect() with a panic on notification failure may be too harsh. Consider logging the error and gracefully handling the failure case instead of panicking.

Suggested change
.expect("Failed to notify TCP socket ready");
if let Err(e) = self.stack_notifier.send(IfaceEvent::TcpSocketReady) {
error!("Failed to notify TCP socket ready: {e}");
}

Copilot uses AI. Check for mistakes.

self.stack_notifier
.send(IfaceEvent::TcpSocketReady)
.expect("Failed to notify TCP socket ready");
Copy link

Copilot AI Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as above - using expect() for notification failures may cause unnecessary panics. Consider error logging instead.

Suggested change
.expect("Failed to notify TCP socket ready");
if let Err(e) = self.stack_notifier.send(IfaceEvent::TcpSocketReady) {
error!("Failed to notify TCP socket ready: {e}");
}

Copilot uses AI. Check for mistakes.

pub fn is_empty(&self) -> bool {
self.read_pos.load(Ordering::Acquire)
== self.write_pos.load(Ordering::Acquire)
Copy link

Copilot AI Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Using Acquire ordering for both loads in is_empty() may be unnecessarily strong. Consider using Relaxed ordering since this is just a snapshot check and exact consistency isn't critical.

Suggested change
== self.write_pos.load(Ordering::Acquire)
self.read_pos.load(Ordering::Relaxed)
== self.write_pos.load(Ordering::Relaxed)

Copilot uses AI. Check for mistakes.

pub fn is_full(&self) -> bool {
let read_pos = self.read_pos.load(Ordering::Acquire);
let write_pos = self.write_pos.load(Ordering::Acquire);
Copy link

Copilot AI Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Similar to is_empty(), is_full() could use Relaxed ordering for better performance since it's a snapshot check.

Suggested change
let write_pos = self.write_pos.load(Ordering::Acquire);
let read_pos = self.read_pos.load(Ordering::Relaxed);
let write_pos = self.write_pos.load(Ordering::Relaxed);

Copilot uses AI. Check for mistakes.
@codecov
Copy link

codecov bot commented Aug 8, 2025

Codecov Report

❌ Patch coverage is 10.52632% with 119 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
clash-netstack/src/ring_buffer.rs 11.59% 61 Missing ⚠️
clash-netstack/src/tcp_stream.rs 0.00% 35 Missing ⚠️
clash-netstack/src/tcp_listener.rs 28.57% 10 Missing and 5 partials ⚠️
clash-netstack/src/stack.rs 0.00% 8 Missing ⚠️

📢 Thoughts on this report? Let us know!

@ibigbug ibigbug enabled auto-merge (squash) August 8, 2025 20:22
@ibigbug ibigbug merged commit c970417 into master Aug 8, 2025
30 of 35 checks passed
@ibigbug ibigbug deleted the stack branch August 8, 2025 20:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants