Class: Rigor::LanguageServer::PublishBatcher

Inherits:
Object
  • Object
show all
Defined in:
lib/rigor/language_server/publish_batcher.rb

Overview

Issue #142 — coalesces keys that become "ready" close together in wall-clock time into ONE batched round instead of firing one round per key. DiagnosticPublisher uses this to fold a burst of buffers whose OWN per-URI debounce timers elapse around the same moment (a workspace-wide rename, a git branch switch that touches many open files) into one #publish_many dispatch across the fork-based worker pool — but the mechanism itself carries no LSP- or URI-specific knowledge, so it stays a small, independently testable collaborator rather than inline state on DiagnosticPublisher.

Single-flight: the first #enqueue call to arrive owns the round and runs on_batch with every key currently pending (deduplicated); a call that arrives while a round is running just adds its key and returns — the running round loops once more before releasing ownership, so nothing queued mid-round is dropped. The same claim/consume shape DiagnosticPublisher#run_project_round already uses for the whole-project save round (#246), generalised to an arbitrary key type and an arbitrary batch action.

Instance Method Summary collapse

Constructor Details

#initialize(on_batch:, on_error: nil) ⇒ PublishBatcher

Returns a new instance of PublishBatcher.

Parameters:

  • on_batch (#call)

    (keys) -> void, called with the deduplicated Array of keys pending at the start of one round. May be called more than once in a row when keys keep arriving while a round runs.

  • on_error (#call, nil) (defaults to: nil)

    (exception) -> void, called when on_batch raises. A round must never wedge the coalescing lock for every future #enqueue call — ownership is always released before this fires. Whatever was mid-flight when the round raised is lost; anything enqueued by a concurrent #enqueue call after this round's drain but before the rescue stays pending and rides the NEXT round instead. Defaults to a no-op (the exception is swallowed silently).



26
27
28
29
30
31
32
# File 'lib/rigor/language_server/publish_batcher.rb', line 26

def initialize(on_batch:, on_error: nil)
  @on_batch = on_batch
  @on_error = on_error
  @lock = Mutex.new
  @pending = []
  @running = false
end

Instance Method Details

#enqueue(key) ⇒ Object

Adds key to the pending set and, if no round is currently running, becomes the round and drains every key pending (looping until none remain) before returning. A call that arrives while another is already running the round returns immediately having only enqueued its key.



37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/rigor/language_server/publish_batcher.rb', line 37

def enqueue(key)
  start = false
  @lock.synchronize do
    @pending << key
    unless @running
      @running = true
      start = true
    end
  end
  return unless start

  run_owned_round
end