dotnet / dotnet/fsharp

P3: `FSharpProjectOptionsReactor` processes requests strictly FIFO — no priority for the active document

Open
#20,122 0 comments 0 reactions 0 assignees View on GitHub
Needs-Triage
Dominant language
F#
Stars
4.3k
Forks
876
Avg merge
4d 22h
Merged PRs (30d)
144

Description

## Summary

`FSharpProjectOptionsReactor` (`vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs`) serializes all project-options requests through a single `MailboxProcessor` and processes them with a plain FIFO loop:

```fsharp
let loop (agent: MailboxProcessor) =
async {
while true do
match! agent.Receive() with
| FSharpProjectOptionsMessage.TryGetOptionsByDocument(document, reply, ct, userOpName) -> ...
| FSharpProjectOptionsMessage.TryGetOptionsByProject(project, reply, ct) -> ...
| FSharpProjectOptionsMessage.ClearOptions(projectId) -> ...
...
}

let reactor = new FSharpProjectOptionsReactor(checker)
```

Every consumer of project options — the active editor tab computing diagnostics/classification/completion, and background services (solution crawler passes, Find All References, unused-opens/unused-declarations analyzers, etc.) — posts into the same mailbox and is served in strict arrival order.

## Problem

When background work enqueues a burst of `TryGetOptionsByDocument` / `TryGetOptionsByProject` messages (e.g. a crawler pass over many documents, or `Find All References` across a large project), a request coming from the **active document** (the one the user is currently typing in) can be queued behind dozens of background requests. Since each request may trigger a real F# Compiler Service computation (`tryComputeOptions`, `tryComputeOptionsBySingleScriptOrFile`), this can noticeably delay diagnostics/IntelliSense responsiveness for the file the user is actively looking at, even though the reactor itself isn't overloaded in absolute terms — it's simply working through older, lower-priority requests first.

This mirrors the general theme of the background-activity-minimization effort: background work should not be allowed to starve foreground/interactive work.

## Proposed solution

Introduce a **priority queue** in front of (or instead of) the plain FIFO `MailboxProcessor`, so that requests associated with the active document are dequeued ahead of background requests:

1. **Two-tier queue.** Replace the single `MailboxProcessor` receive loop with two channels/queues — a small-capacity "foreground" queue and a "background" queue (e.g. `System.Threading.Channels.Channel<'T>` with `UnboundedChannel` for background and a bounded/unbounded high-priority channel for foreground), or a single `MailboxProcessor` combined with an internal `PriorityQueue` that the loop drains with priority ordering (using `agent.TryScan`/`Scan` is not ideal for this since it re-scans the whole mailbox on every call; a dedicated processing loop backed by `System.Threading.Channels` is a cleaner fit for prioritized draining).
2. **Priority classification at post time.** When a message is posted (`TryGetOptionsByDocument`, `TryGetOptionsByProject`), classify it using the existing `ActiveDocumentDetection` helper (see #9-related work in `vsintegration/src/FSharp.Editor/Diagnostics/ActiveDocumentDetection.fs`) — if the request's document/project matches the active document, enqueue into the foreground queue; otherwise the background queue.
3. **Draining order.** The processing loop should always prefer to drain the foreground queue when it is non-empty, falling back to the background queue only when the foreground queue is empty, so active-document requests are never blocked behind an arbitrarily long backlog of background requests. To avoid starving background work entirely under sustained foreground activity, consider a simple weighted/round-robin fallback (e.g., service at most N foreground messages before checking background once) if needed in practice.
4. **Cancellation-awareness.** Preserve existing behavior where messages already carrying a canceled `CancellationToken` are replied to immediately with `ValueNone` without doing any work, for both queues.
5. **No change to computation semantics.** `tryComputeOptions`/`tryComputeOptionsBySingleScriptOrFile` and the existing caches (`cache`, `lastSuccessfulCompilations`, `emitCache`) are unaffected — this is purely a scheduling/ordering change on top of the existing reactor, not a change to what gets computed.

## Alternative considered

A lighter-weight alternative would be to keep the single `MailboxProcessor` but call `agent.Scan` at the head of the loop to look for a foreground message first before falling back to `agent.Receive()`. This avoids introducing `System.Threading.Channels` but has less predictable performance characteristics under a large mailbox backlog (each `Scan` call walks the mailbox), and is likely a reasonable first iteration if the full priority queue is judged too invasive for a first pass.

## Impact

Low risk (isolated to `FSharpProjectOptionsReactor`'s message loop), improves perceived editor responsiveness for the active document during heavy background project-options activity (crawler passes, Find All References, etc.), without changing correctness or caching behavior.

## Related

- Continuation of the background-activity-minimization effort tracked in `docs/ide/background-activity-minimization-plan.md`.
- Complements the Find All References parallel-typecheck throttling change (item #10).

Contributor guide

Open the contributing guide

Research direction

Start in vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs at the FSharpProjectOptionsReactor message loop, then read ActiveDocumentDetection.fs and the related background-activity plan. Trace how TryGetOptionsByDocument, TryGetOptionsByProject, and cancellation are handled. Done means active-document requests are prioritized without changing computation, caching, or canceled-request behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
fsharp
Domain
developer-experience, devtools, performance
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
46/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.