scheme-langserver

Debugging scheme-langserver

1. Enable Debug Logging

Magic Scheme

As shown in this figure, you may find Magic Scheme > Scheme-langserver: Log Path, and please give it an available path, so that you may know what scheme-langserver did wrong. Config Magic Scheme

Lunar Vim

It’s convenient to add path-to-log-file and re-write file ~/.local/share/lunarvim/site/pack/packer/start/nvim-lspconfig/lua/lspconfig/server_configurations/scheme_langserver.lua as follows:

local util = require 'lspconfig.util'
local bin_name = '{path-to-run}'
local cmd = { bin_name ,"path-to-log-file"}

return {
  default_config = {
    cmd = cmd,
    filetypes = { 'scheme' },
    root_dir = util.find_git_ancestor,
    single_file_support = true,
  },
  docs = {
    description = [[
https://github.com/ufo5260987423/scheme-langserver
`scheme-langserver`, a language server protocol implementation for scheme
]]   ,
  },
}

2. Analyse Logs & Report Bugs

No matter who you are, for example an expert or a new schemer, if you want to debug scheme-langserver or issue a bug to the community, you will firstly analyse log.

Issue A Bug

Ok, you can make an issue here, AND DON’T FORGET

  1. Describe what’s wrong.
  2. Attach your log and project, so that the community can do detailed analysis.
  3. Wait.

Debug

If you’re an expert, you may recur the bug with log {path-to-log} and the replay scripts.

  1. You should have scheme-langserver’s source in {path-to-scheme-langserver} and you can find bin/log-debug.sps (single-threaded) and bin/parallel-log-debug.sps (multi-threaded) there.
  2. Rename {path-to-log}, usually ~/scheme-langserver.log, as ~/ready-for-analyse.log.

    If your log is not ~/ready-for-analyse.log, you should do few modification to the replay script.

  3. Run
    cd {path-to-scheme-langserver}
    scheme --script bin/log-debug.sps          # deterministic, single-threaded
    scheme --script bin/parallel-log-debug.sps # concurrent, closer to real clients
    

    or run them in a Scheme REPL. Mostly bugs will cause exceptions on screen.

See the detailed sections below for choosing between the two scripts, iterative printf debugging, and thread-safe printing tips.

If you’re an old-fashiond schemer, you may find this page helpful. But personally I use pretty-print to print useful informations.

A Better Bug Issue

OK, you may locate which your behavior cause crash or any other wrong, and reduce the size of code and log. And finally attach them in the issue.

scheme-langserver log replay debugging tools

bin/log-debug.sps and bin/parallel-log-debug.sps are log replay utilities. They read a previously captured LSP session log and feed it back into a fresh server instance, allowing you to reproduce crashes or misbehavior offline without a real language client.


1. Log file format

Both scripts expect a plain-text log at ~/ready-for-analyse.log. The log is produced by the server’s own debug logging and follows a simple line-oriented convention:

Because JSON-RPC messages arrive with a Content-Length header in real I/O, the scripts reconstruct that header automatically:

Content-Length: <byte-length>\r\n\r\n<json-payload>

2. bin/log-debug.sps — single-threaded replay

Aspect Behaviour
Mode Single-threaded (enable-multi-thread? #f)
Batch handling No send-message support; each read-message is followed by exactly one payload line
Server args (init-server input-port output-port log-port #f #t 'r6rs)
Expected result Server does reach shutdown state (server-shutdown?#t) when the input is exhausted

Use this when you want a deterministic, linear replay without engine preemption or request-queue concurrency.


3. bin/parallel-log-debug.sps — multi-threaded replay

Aspect Behaviour
Mode Multi-threaded (enable-multi-thread? #t)
Batch handling Supports multi-line payloads delimited by send-message
Server args (init-server input-port output-port log-port #t #t 'r6rs #t)
Expected result Server does reach shutdown state (server-shutdown?#t)

This version runs the full threaded pipeline: thread-pool, request-queue, interval timer for diagnostic publishing, and engine-based time slicing. Use it to reproduce race conditions or cancellation bugs that only appear in multi-threaded mode.


4. Typical workflow

  1. Capture a failing session from a real editor client. The server writes debug logs to ~/scheme-langserver.log when debug mode is enabled.
  2. Copy or symlink the log to ~/ready-for-analyse.log.
  3. Run the appropriate replay script:
    source .akku/bin/activate
    scheme --script bin/log-debug.sps          # single-threaded
    scheme --script bin/parallel-log-debug.sps # multi-threaded
    
  4. The script creates ~/scheme-langserver.log (server output log) and ~/scheme-langserver.out (raw server stdout) for inspection.

5. Iterative printf debugging with replay

The most common way to use these scripts is not a one-shot replay. Instead, you iterate:

Goal: reproduce a crash or wrong result offline, then narrow down the culprit by adding print statements in the source.

Step-by-step

  1. Obtain the log.
    When a user (or your own editor) hits a bug, grab the server’s debug log. If the server was started without a log file, you can often reconstruct the JSON-RPC traffic from the client’s output (e.g. VS Code’s “Scheme Language Server” output panel).

  2. Prepare ~/ready-for-analyse.log.
    cp /path/to/captured.log ~/ready-for-analyse.log
    
  3. Verify reproduction.
    source .akku/bin/activate
    scheme --script bin/log-debug.sps
    

    If the bug involves threading (races, cancellation, request-queue ordering), use parallel-log-debug.sps instead.

  4. Pick a suspect module and add prints.
    For example, if the symptom is a wrong identifier reference, open analysis/identifier/rules/library-import.sls and add:
    (display "DEBUG: processing import ") (display identifier) (newline)
    

    Or use pretty-print for structured data:

    (pretty-print `(DEBUG: index-node= ,index-node references= ,refs))
    
  5. Clear the .so cache.
    This is the #1 gotcha. Chez compiles libraries to .so objects under .akku/libobj/. If you do not delete the cached object for the file you edited, the replay will silently run the old code.
    rm -rf .akku/libobj/scheme-langserver
    

    (See AGENTS.md §3 for a longer explanation.)

  6. Re-run the replay.
    source .akku/bin/activate
    scheme --script bin/log-debug.sps
    

    Your new prints appear on the console (or in ~/scheme-langserver.log if the server redirects them).

  7. Narrow the search.
    • If the print does not fire → the code path you suspected is not taken; move your print upstream.
    • If the print fires and the data looks wrong early → keep moving the print upstream until you find the first place where the value diverges from expectation.
    • If the print fires and the data looks correct here but the final result is still wrong → move the print downstream.
  8. Repeat 4–7 until you locate the root cause.

  9. Clean up.
    Remove or comment out the debug prints before committing. If you added temporary state (e.g. a global counter), remove that too.

Thread-safe printing in parallel mode

When using parallel-log-debug.sps, multiple threads may print simultaneously, causing interleaved output. If this matters, wrap prints in the workspace mutex (or any other convenient mutex):

(with-mutex (workspace-mutex workspace)
  (display "DEBUG: ") (display value) (newline))

For quick-and-dirty debugging, raw interleaved output is usually still readable enough to tell which thread hit which breakpoint.

Choosing between single-threaded and multi-threaded replay

Symptom Use
Crash in abstract interpreter / identifier resolution / type inference log-debug.sps first
Wrong request order, missing cancellation, duplicated publish-diagnostics parallel-log-debug.sps
Hang or infinite loop Try both; if parallel-log-debug.sps hangs but log-debug.sps does not, you have a threading bug
request-queue assertion failure parallel-log-debug.sps

If the single-threaded replay does not reproduce the bug, switch to the multi-threaded version. Conversely, if the multi-threaded version is too noisy to debug, try the single-threaded one to verify the core logic is correct in isolation.


6. Differences at a glance

  log-debug.sps parallel-log-debug.sps
Threading #f #t
Debug mode #t (6th arg) #t (7th arg)
Batch (send-message)
Shutdown assertion #f #t

7. Notes


8. Shutdown / exit handling

Status: Fixed in the kimi branch.

The server’s shutdown/exit lifecycle now complies with the LSP specification.

Message Type Server behaviour (current)
shutdown Request (requires response) Sets server-shutdown? to #t and sends a null result response.
exit Notification (no response) Terminates the Chez process with exit code 0 if shutdown was received earlier, otherwise 1.
Any request after shutdown Returns InvalidRequest (-32600) except for exit, which terminates the process.
initialize after shutdown Rejected with InvalidRequest.

Key fixes applied to scheme-langserver.sls:

  1. Removed the special shutdown/exit branch in the main I/O loop; both messages now flow through the normal request processor.
  2. process-request handles shutdown inside the case method dispatch: it sets the flag and sends (success-response id 'null).
  3. process-request handles exit with (exit (if (server-shutdown? server-instance) 0 1)).
  4. Post-shutdown guard now rejects all methods except exit with InvalidRequest (previously it exempted initialize and used the wrong error code server-not-initialized).

Impact on the replay scripts