Skip to content

fix(lsp): Omit params field for shutdown/exit methods#5

Closed
ketema wants to merge 1 commit into
mainfrom
fix/shutdown-params-void-pr
Closed

fix(lsp): Omit params field for shutdown/exit methods#5
ketema wants to merge 1 commit into
mainfrom
fix/shutdown-params-void-pr

Conversation

@ketema

@ketema ketema commented Jan 11, 2026

Copy link
Copy Markdown
Owner

Summary

Changes

  • Add _NO_PARAMS_METHODS constant for methods requiring no params
  • Add _build_params_field() helper for three-way compatibility
  • Update make_notification() and make_request() to use helper

Test plan

  • 14 unit tests covering all param handling scenarios
  • Format check passed (Black + Ruff)
  • Type check passed (mypy)

References

WHY:
- HLS (Haskell) and rust-analyzer use Void/unit types for shutdown/exit
- These strict-typed servers reject ANY params field (even empty {})
- PR oraios#851 changed to send params:{} for Delphi/FPC compatibility
- This broke HLS/rust-analyzer with "Cannot parse Void" errors

EXPECTED:
- shutdown/exit methods: omit params field entirely (HLS/rust-analyzer compat)
- Other methods with None: send params:{} (Delphi/FPC compat)
- Other methods with params: include them unchanged (standard behavior)
- Three-way compatibility preserved per JSON-RPC 2.0 spec

Refs: oraios#870
@ketema ketema closed this Jan 11, 2026
@ketema ketema deleted the fix/shutdown-params-void-pr branch February 6, 2026 19:33
ketema added a commit that referenced this pull request Feb 6, 2026
 WHY:
 - Race conditions could cause duplicate server startups
 - Silent exception swallowing made debugging impossible
 - Missing async context manager risked resource leaks
 - Ignored task results hid startup failures
 - Linear search caused performance degradation

 EXPECTED:
 - FIX oraios#1 (CRITICAL): Race condition prevented with asyncio.Lock per language
 - FIX #2 (CRITICAL): All exceptions properly logged with exc_info=True
 - FIX #3 (MAJOR): Async context manager pattern implemented
 - FIX #4 (MAJOR): Task results from asyncio.gather() logged and handled
 - FIX #5 (MINOR): O(1) extension lookup via cached extension→language mapping
 - All tests pass

IMPLEMENTATION DETAILS:

FIX oraios#1 - Race Condition Prevention:
- Added self._startup_locks: dict[Language, asyncio.Lock]
- _start_language_server() uses 'async with lock' pattern
- Double-check after acquiring lock (another task may have started it)

FIX #2 - Proper Error Logging:
- TimeoutError now logs with exc_info=True
- All exceptions include full stack traces
- Clear error messages for debugging

FIX #3 - Async Context Manager:
- Implemented __aenter__() and __aexit__()
- shutdown_all() now async
- _shutdown_single_lsp() helper for concurrent cleanup
- Proper resource cleanup guaranteed

FIX #4 - Task Result Handling:
- start_all() iterates over asyncio.gather() results
- Logs success/failure for each language
- Tracks which languages failed and why

FIX #5 - Performance Optimization:
- _build_extension_cache() creates extension→language mapping
- get_language_for_file() uses cache first (O(1) lookup)
- Fallback to full pattern matching for complex patterns
- Significant speedup for projects with many languages

TESTING:
- Added test_async_context_manager_cleanup()
- All 13 tests pass
- Verified race condition protection
- Verified graceful degradation still works
ketema added a commit that referenced this pull request Feb 6, 2026
WHY:
- 5 critical/major/minor issues in Phase 1.3 Step 2
- Issues ranged from resource leaks to code quality violations

EXPECTED:
- FIX oraios#1 (CRITICAL): Proper async/sync LSPManager shutdown (no resource leaks)
- FIX #2 (ERROR): Comprehensive error handling with validation and rollback
- FIX #3 (WARNING): Race condition prevention via cached references
- FIX #4 (WARNING): DRY compliance via extracted timeout calculation
- FIX #5 (MINOR): Proper TYPE_CHECKING import for LSPManager
- All 20 tests pass (7 agent + 13 lsp_manager)

IMPLEMENTATION:

FIX oraios#1 - Async/Sync Mismatch (CRITICAL):
- Added LSPManager.shutdown_all_sync() synchronous wrapper
- Uses asyncio.run() with fallback for running event loops
- SerenaAgent.reset_lsp_manager() now calls shutdown_all_sync()
- Prevents resource leaks, zombie processes, hanging connections

FIX #2 - Missing Error Handling (ERROR):
- Added try/except around LSPManager creation in reset_lsp_manager()
- Validates LSPManager creation succeeded (not None)
- Rollback mechanism: restores old manager on failure
- Comprehensive logging with exc_info=True for debugging

FIX #3 - Race Conditions (WARNING):
- Cache lsp_manager reference at start of methods
- Prevents AttributeError if manager becomes None between check and access
- Applied to: language_server property, is_language_server_running(),
  get_language_server_for_file(), reset_lsp_manager()

FIX #4 - DRY Violation (WARNING):
- Extracted _calculate_ls_timeout() method
- Eliminates duplicated timeout logic in reset_lsp_manager() and reset_language_server()
- Single source of truth for timeout calculation
- Reduces maintenance burden and prevents inconsistency

FIX #5 - Type Annotation (MINOR):
- Added LSPManager to TYPE_CHECKING imports
- Proper forward reference instead of string literal
- Improves IDE support and type checking

TESTING:
- All 7 agent polyglot tests pass
- All 13 LSPManager tests pass
- No regressions in existing functionality
- Backward compatibility maintained
ketema added a commit that referenced this pull request Feb 6, 2026
WHY:
- add_workspace_root and remove_workspace_root were STUBS returning True
  without sending any LSP notification (CL3 violation)
- Multi-root LSPs (rust-analyzer, pylsp, gopls) share instances across
  sessions but the second session's workspace was never registered
- Tests were THEATER tests that only checked return value, not behavior
  (CL12-E, THEATER violation)

EXPECTED:
- add_workspace_root now sends workspace/didChangeWorkspaceFolders
  notification with the new folder in the 'added' list
- remove_workspace_root now sends notification with folder in 'removed' list
- LSP instances track workspace_roots list for path resolution
- Tests now verify: return value, notification sent, correct params, tracking

EVIDENCE:
- T:test_lsp_capability_adapter::* = 36/36 PASS
- POST-3 contract now enforced with behavioral assertions
- CL12-E compliance: Tests cite "Enforces: POST-3"

CONSTITUTIONAL FIX: Iteration 1 of ralph-loop for commit b4471d3
Fixes oraios#1-2 (stubs), #3-4 (missing clause IDs), #5-6 (theater tests)
ketema added a commit that referenced this pull request Feb 6, 2026
WHY:
- Constitutional audit of GREEN phase found 5 documentation/contract
  inconsistencies between contracts and actual implementation

EXPECTED:
- Finding oraios#1 (HIGH): INV-05 in contract updated to reflect that 7
  subclass files with method overrides need workspace_root, while
  _start_server implementations remain unchanged
- Finding #2 (HIGH): INV-05 in requirements updated with same correction
- Finding #3 (MEDIUM): retrieve_content_around_line contract signature
  now matches implementation (context_lines_before/after, workspace_root
  position, MatchedConsecutiveLines return type)
- Finding #4 (MEDIUM): get_root_path() in symbol.py now documents the
  explicit-LSP edge case as acknowledged exception to CALLER-INV-1
- Finding #5 (MEDIUM): Log message at ls.py:1381 now references
  effective_root_str (workspace_root) instead of repository_root_path
- 70/70 path resolution tests remain GREEN
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.

LSP shutdown error: "Failed to deserialize shutdown: invalid type: map, expected unit"

1 participant