Skip to content

Add MCP Ping Support with Test Infrastructure Improvements#9

Open
abigagli wants to merge 6 commits into
zeenix:mainfrom
abigagli:main
Open

Add MCP Ping Support with Test Infrastructure Improvements#9
abigagli wants to merge 6 commits into
zeenix:mainfrom
abigagli:main

Conversation

@abigagli

Copy link
Copy Markdown

Implements the MCP ping utility method as specified in the Model Context Protocol specification, along with fixes to the test infrastructure that improve reliability across the test suite.

Implementation Details

Ping Handler

  • Added ping method handler in src/mcp/server.rs
  • Returns empty object {} per MCP specification requirements
  • Stateless, synchronous operation with no parameters
  • Enables clients to verify server responsiveness and measure round-trip latency

Test Infrastructure Fixes

Socket Blocking Mode Fix:
Fixed a critical issue in the test IPC server where accepted connections inherited non-blocking mode from the listener socket, causing spurious EAGAIN/EWOULDBLOCK errors during request processing.

Root Cause:

  • The listener socket uses non-blocking mode to enable graceful shutdown (checking should_shutdown between accept() calls without blocking indefinitely)
  • On Unix systems, accept() returns connected sockets that inherit the listener's non-blocking flag (POSIX behavior)
  • When the server attempts to read requests via BufReader::read_line(), non-blocking mode causes reads to fail with WouldBlock if data isn't immediately available in the kernel buffer
  • This manifested as intermittent failures on sequential requests, where timing variations in pipe/socket buffering would cause reads to fail even though data was in transit

Solution:
Explicitly set accepted connections to blocking mode with stream.set_nonblocking(false) immediately after accept(). This allows the listener to remain non-blocking for shutdown responsiveness while ensuring connected sockets reliably wait for complete request data.

Additional Infrastructure Improvements:

  • Path Canonicalization: Added workspace path canonicalization in test-support/src/ipc/client.rs to ensure consistent absolute path handling
  • Unix Socket Path Length: Shortened socket paths to comply with SUN_LEN limits (104 bytes on macOS) by using abbreviated names
  • Timeout Support: Added configurable timeout to IpcClient::send_request for better failure detection

Test Coverage

Integration Tests (tests/integration/mcp_server_test.rs):

  • Response format validation
  • Idempotency verification (5 sequential pings)
  • Rapid-fire throughput testing (10 sequential pings)
  • Post-ping server responsiveness verification

Stress Tests (tests/stress/concurrent_requests.rs):

  • test_ping_rapid_fire: Sequential throughput (100-500 requests)
  • test_ping_concurrent: Concurrent request handling (10-50 parallel)
  • test_ping_interleaved_with_tools: Ping/tool call alternation (20-50 iterations)
  • test_ping_mixed_concurrent_workload: Mixed concurrent workload with 40% ping ratio (15-30 concurrent)
  • test_ping_burst_pattern: Sustained burst patterns (5-10 bursts of 20-50 requests)
  • test_ping_connection_stability: Connection lifecycle testing (50-100 iterations)

All tests include CI-specific tuning with reduced iteration counts for GitHub Actions environment.

- Implement ping handler in MCP server per specification
  - Returns empty object {} as required by MCP spec
  - No parameters needed, synchronous operation

- Fix critical IPC server bug preventing multiple requests
  - Set accepted streams to blocking mode explicitly
  - Prevents EAGAIN/EWOULDBLOCK errors on sequential requests
  - Improves overall test infrastructure reliability

- Add comprehensive integration tests for ping
  - Test basic ping response format
  - Verify idempotency with multiple pings
  - Test rapid-fire throughput
  - Ensure server responsiveness after pings

Closes: MCP ping utility specification compliance
Spec: https://modelcontextprotocol.io/specification/2025-03-26/basic/utilities/ping
- Canonicalize workspace paths before passing to server
  - Ensures absolute paths are used consistently
  - Fixes file not found errors in wait_for_ready

- Shorten Unix socket paths to avoid SUN_LEN limit
  - Use 'ra-mcp' instead of 'rust-analyzer-mcp-sockets'
  - Map project types to short socket names (e.g., 'diag.sock')
  - Fixes 'path must be shorter than SUN_LEN' error on macOS
  - SUN_LEN limit is typically 104 bytes, was exceeding with long paths

These fixes enable all diagnostics integration tests to pass:
- test_file_diagnostics
- test_file_diagnostics_clean_file
- test_diagnostics_severity_levels
- test_workspace_diagnostics

All 11 integration tests now pass successfully.
Add 6 new stress tests for ping functionality:

1. test_ping_rapid_fire
   - Tests sequential ping throughput (100-500 pings)
   - Measures requests per second performance

2. test_ping_concurrent
   - Tests concurrent ping handling (10-50 concurrent)
   - Verifies parallel request processing

3. test_ping_interleaved_with_tools
   - Alternates ping with actual tool calls (20-50 iterations)
   - Ensures ping doesn't interfere with LSP operations

4. test_ping_mixed_concurrent_workload
   - Concurrent mix of pings and tool calls (15-30 concurrent)
   - Tests realistic usage patterns with 40% pings

5. test_ping_burst_pattern
   - Multiple bursts of rapid pings (5-10 bursts of 20-50 pings)
   - Tests sustained high-frequency ping patterns

6. test_ping_connection_stability
   - Creates/destroys connections with pings (50-100 iterations)
   - Verifies no resource leaks or connection issues

All tests include CI-specific tuning with lower iteration counts
and validate that ping returns empty object per MCP spec.

All 12 stress tests (6 ping + 6 original) pass successfully.
When set_workspace was called as the first tool invocation,
ensure_client_started() would spawn rust-analyzer with the default
workspace root (often '/') before set_workspace had a chance to update
the path. This caused rust-analyzer to attempt indexing the entire
filesystem, leading to inevitable timeouts on large projects.

Skip the eager client start for set_workspace since it manages the
client lifecycle itself (shutdown old client, update path, start new).
Add a Drop implementation for RustAnalyzerClient that synchronously
kills the child rust-analyzer process via start_kill(). This prevents
orphaned rust-analyzer processes when the MCP server exits unexpectedly.

Also handle SIGTERM in addition to SIGINT so the graceful shutdown path
(LSP shutdown/exit + process kill + wait) is reached when the parent
process (e.g., Windsurf) terminates the MCP server.
Multiple concurrent tests calling IpcClient::get_or_create for the same
project type would all fail to find an existing socket, then race to
spawn their own server instance. Only one server wins the socket bind;
the others die immediately, causing broken pipe errors in the tests.

Fix by using an O_CREAT|O_EXCL lockfile as an atomic mutex around the
check+spawn sequence. Waiting processes spin on the lock and re-check
the socket once they acquire it, so only one server is ever started.
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.

1 participant