Skip to content

Conversation

@DaZhi-the-Revelator
Copy link

Summary

Fixes critical process lifecycle issues on Windows where serve-d and its child processes (dcd-server) would remain running after the editor closed, causing resource leaks and preventing the editor from restarting properly.

Problem Statement

Symptoms

  • serve-d processes remained running after Zed editor closed
  • dcd-server child processes became orphaned
  • Multiple serve-d instances would accumulate over time
  • When reopening the editor, serve-d would fail to start with "already in use" errors
  • User had to manually kill serve-d processes via Task Manager

Root Causes

  1. Rejected Shutdown Requests: serve-d incorrectly rejected LSP shutdown requests when params was null, causing editors to give up on graceful shutdown
  2. No Cleanup on Abrupt Exit: When editors closed abruptly (red X, force kill), scope(exit) wasn't reliable
  3. Incomplete Child Process Cleanup: dcd-server processes weren't reliably terminated
  4. Windows Job Objects Not Self-Inclusive: serve-d created Job Objects for children but didn't add itself, so if serve-d crashed, children survived

Solution

This PR implements a multi-layered defense system to ensure proper cleanup in all scenarios:

Layer 1: Accept Null Params in Shutdown Requests

File: lsp/source/served/lsp/jsonrpc.d

Modified JSON-RPC parameter validation to accept "params":null in addition to objects and arrays, per LSP specification.

Layer 2: Windows Job Objects with Self-in-Job

File: serverbase/source/served/utils/jobs.d (NEW)

Complete Windows Job Objects implementation including:

  • Console Control Handler to catch CTRL_CLOSE_EVENT (red X button)
  • Job Object creation with KILL_ON_JOB_CLOSE flag
  • Self-in-Job: serve-d adds itself to its own Job Object for kernel-level fail-safe
  • Two methods for adding processes: by PID (legacy) and by ProcessPipes (preferred)

Layer 3: Lifecycle Integration

File: serverbase/source/served/serverbase.d

  • Initialize Job Object on startup
  • Add cleanup in scope(exit) of run() method - executes regardless of how function exits
  • Additional cleanup in processNotify() for explicit exit/shutdown paths
  • Enhanced logging for troubleshooting

Layer 4: DCD Server Integration

File: workspace-d/source/workspaced/com/dcd.d

  • Add dcd-server to Job Object immediately after spawning
  • Use reliable addProcessToJobByProcessPipes() method instead of reopening by PID
  • Add serverbase dependency to workspace-d (in workspace-d/dub.sdl)

How It Works

Normal Exit

  1. Editor sends shutdown request (now accepts null params)
  2. serve-d initiates graceful shutdown
  3. scope(exit) cleanup runs
  4. cleanupJobObject() closes the Job Object handle
  5. Windows kernel kills all processes in job (dcd-server)

Abrupt Exit (Red X, Force Kill, Crash)

  1. serve-d process terminates for any reason
  2. Windows automatically closes all handles, including Job Object handle
  3. Self-in-job ensures Job Object handle closure triggers cleanup
  4. KILL_ON_JOB_CLOSE flag causes Windows kernel to kill all child processes
  5. dcd-server and any other children are terminated automatically

Testing

Tested on Windows 10 with Zed editor:

  • ✅ Normal exit (File > Quit) - clean shutdown
  • ✅ Red X button - all processes terminate
  • ✅ Force kill (Task Manager) - children terminate
  • ✅ 10 rapid open/close cycles - no orphans
  • ✅ Crash scenarios - children cleanup works

Compatibility

  • Windows: Full implementation with all layers
  • Linux/macOS: JSON-RPC fix applies; Job Object code is Windows-specific (guarded with version(Windows))
  • Backward Compatible: No breaking changes, all existing functionality preserved

Files Changed

  • lsp/source/served/lsp/jsonrpc.d - Accept null params in shutdown
  • serverbase/source/served/utils/jobs.d - NEW - Complete Job Objects implementation
  • serverbase/source/served/serverbase.d - Lifecycle integration with scope(exit) cleanup
  • workspace-d/source/workspaced/com/dcd.d - Add DCD to Job Object
  • workspace-d/dub.sdl - Add serverbase dependency

Performance Impact

Negligible:

  • Startup: +~3ms (Job Object creation)
  • Runtime: 0ms (event-driven, no polling)
  • Shutdown: Slightly faster graceful shutdown

Breaking Changes

None - all changes are additive bugfixes.


Resolves issues with orphaned serve-d and dcd-server processes on Windows.

- Add Windows Job Objects with console handler and self-in-job
- Fix JSON-RPC to accept null params in shutdown requests
- Integrate Job Objects into serve-d lifecycle with scope(exit) cleanup
- Add DCD server to Job Object for automatic cleanup
- Add serverbase dependency to workspace-d for Job Objects access

Fixes orphaned process issues on Windows when editor closes abruptly.
All child processes (dcd-server) now terminate reliably via kernel-level
Job Objects enforcement, regardless of termination method (normal exit,
red X, force kill, or crash).
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