Skip to content

Conversation

@Moosieus
Copy link
Collaborator

@Moosieus Moosieus commented Dec 16, 2025

This pull request overhauls the way progress reporting is handled in Expert. The top-level API remains largely the same for callers.

The prior architecture started a per-project stateful GenServer that handled progress tracking logic internally:

  • It was difficult to reason about due to the depth and statefulness of the call stack.
    • Or at least, I found it difficult. YMMV, but my brain works better with shallow call stacks.
  • The Progress GenServers were initiated per-project, even though workDoneTokens aren’t scoped as such in LSP.
  • Had no means to handle client-initiated workDoneTokens
    • I had a with_client_progress function incorporated here, but I removed it for the sake of review. There’s already a lot here.
  • Had no means to deallocate workDoneTokens should they be cancel-able.
    • Requests for long-running tasks can be canceled by the client, in which case it cancels the request by ID, but not any workDoneTokens associated.
      • Hence why the work-done-tokens now live in the caller’s context.

The new progress reporting modules are organized as follows:

  • Forge.Progress - a behaviour that defines the with_progress and with_tracked_progress helper functions, permitting the implementing module provides the following functions:
    • begin/2
    • report/2
    • complete/2
  • Expert.Progress - a process-less module that directly handles progress reporting, implements the aforementioned
  • Engine.Progress - a thin wrapper that calls Expert.Progress via erpc.
  • Forge.Progress.Tracker - An ephemeral GenServer for reporting progress between concurrently running tasks.
    • Supports straight percentage reporting by default, or custom callbacks like turbo-encabulating files: 3/25 should that be preferable.

All in all: Less processes, more sequential Elixir, more flexibility, better alignment with LSP semantics.

Some additional changes:

  • Make tests that wait the project_compiled message less flaky by increasing the timeout from 100ms -> 5s.
  • Debounce namespacing logs
  • When the engine starts, store its manager name in a persistent term and use that, instead of building it.

* Overhaul `Expert.Project.Progress` with a new progress reporting API.
  * Progress reporting only informs users of long-running work and doesn’t drive behavior. New interface accordingly KISS.
* Removed releveant `__using__` macros in favor of simple `alias` + function calls.
* Use plain tuples instead of `defrecord` for progress report messages between the server node and engine node.
* Debounce namespace build logs to minimize spam.
* Store the manager node name on startup in the engine node. This fixes up some tests that were faltering due to the introduced rpc calls. This is more stable anyway, imo.
* give project compilation more generous timeouts in testing (5s instead of 100ms)
@Moosieus Moosieus changed the title Moo/work done progress Make progress tracking better follow LSP semantics Dec 16, 2025
@Moosieus Moosieus closed this Dec 16, 2025
@Moosieus Moosieus reopened this Dec 16, 2025
@Moosieus Moosieus changed the title Make progress tracking better follow LSP semantics refactor: Make progress tracking better follow LSP semantics Dec 16, 2025
@Moosieus Moosieus changed the title refactor: Make progress tracking better follow LSP semantics chore: Make progress tracking better follow LSP semantics Dec 16, 2025
@Moosieus Moosieus changed the title chore: Make progress tracking better follow LSP semantics chore: make progress tracking better follow LSP semantics Dec 16, 2025
@Moosieus Moosieus marked this pull request as ready for review December 16, 2025 04:21
@mhanberg mhanberg changed the title chore: make progress tracking better follow LSP semantics refactor: make progress tracking better follow LSP semantics Dec 16, 2025
Copy link
Collaborator

@doorgan doorgan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks good, and from what I understand about the changes and from my testing it seems we're getting pretty much the same results with simpler code, so it's a +1 from me.

For my own understanding: we are moving from the engine node sending progress messages to the server to instead unifying everything under the new Progress API(so, we report progress directly to the LSP via erpc calls to the server, instead of sending progress to the server and having it create the messages). Is this correct?

def with_tracked_progress(title, total, work_fn, report_fn)
when is_function(work_fn, 1) and is_function(report_fn, 4) do
run_with_progress(title, [percentage: 0], fn token ->
{:ok, tracker} = Tracker.start_link(token: token, total: total, report_fn: report_fn)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use a DynamicSupervisor for this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it'd add much.

@Moosieus
Copy link
Collaborator Author

For my own understanding: we are moving from the engine node sending progress messages to the server to instead unifying everything under the new Progress API(so, we report progress directly to the LSP via erpc calls to the server, instead of sending progress to the server and having it create the messages). Is this correct?

That's correct. Tried to make it as simple as can be w/o being simplistic.

Copy link
Collaborator

@doorgan doorgan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Moosieus got it, I'm on board with these changes then!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants