Class: Rigor::LanguageServer::Debouncer
- Inherits:
-
Object
- Object
- Rigor::LanguageServer::Debouncer
- Defined in:
- lib/rigor/language_server/debouncer.rb
Overview
Per-key debouncer. The LSP uses this to defer ‘publishDiagnostics` until the user stops typing (200ms quiet-time floor per LSP UX conventions). Each `schedule(uri, delay:)` cancels the previous task for the same `uri` and queues a new one — only the LAST task in a burst actually runs.
Threading model: each scheduled task runs in its own Thread so the dispatcher loop doesn’t block. Concurrent writes to STDOUT from the Debouncer’s threads + the main dispatch loop are serialised by ‘SynchronizedWriter`.
Cancellation is cooperative: each task carries a ‘cancelled` flag; new schedules flip the prior task’s flag and the prior thread skips the block on wake-up. This is safer than ‘Thread#kill` for in-flight Ruby code and good enough for the “drop stale debounce” use case.
Defined Under Namespace
Classes: Task
Instance Method Summary collapse
-
#cancel_all ⇒ Object
Cancel every pending task (sets the flag; the threads exit without running the block).
-
#flush! ⇒ Object
Wait for every pending task to complete.
-
#initialize ⇒ Debouncer
constructor
A new instance of Debouncer.
-
#pending_size ⇒ Integer
Number of currently-pending tasks.
-
#schedule(key, delay:, &block) ⇒ Object
Schedule ‘block` to run after `delay` seconds, replacing any pending task for the same `key`.
Constructor Details
#initialize ⇒ Debouncer
Returns a new instance of Debouncer.
25 26 27 28 |
# File 'lib/rigor/language_server/debouncer.rb', line 25 def initialize @tasks = {} @mutex = Mutex.new end |
Instance Method Details
#cancel_all ⇒ Object
Cancel every pending task (sets the flag; the threads exit without running the block). Called on ‘shutdown` so in-flight publishes don’t write to a closed STDOUT.
73 74 75 76 77 78 |
# File 'lib/rigor/language_server/debouncer.rb', line 73 def cancel_all @mutex.synchronize do @tasks.each_value { |t| t.cancelled = true } @tasks.clear end end |
#flush! ⇒ Object
Wait for every pending task to complete. Used by specs to synchronise with the async schedule; the production ‘shutdown` path uses `#cancel_all` instead.
61 62 63 64 65 66 67 68 |
# File 'lib/rigor/language_server/debouncer.rb', line 61 def flush! threads = @mutex.synchronize { @tasks.values.map(&:thread) } threads.each do |thread| thread.join rescue StandardError # Threads can die from raised exceptions; ignore. end end |
#pending_size ⇒ Integer
Returns number of currently-pending tasks.
81 82 83 |
# File 'lib/rigor/language_server/debouncer.rb', line 81 def pending_size @mutex.synchronize { @tasks.size } end |
#schedule(key, delay:, &block) ⇒ Object
Schedule ‘block` to run after `delay` seconds, replacing any pending task for the same `key`. `delay: 0` makes the task fire immediately (still on its own thread); tests pair this with `#flush!` for deterministic assertions.
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/rigor/language_server/debouncer.rb', line 34 def schedule(key, delay:, &block) task = Task.new(nil, false) previous = @mutex.synchronize do prev = @tasks[key] @tasks[key] = task prev end previous&.cancelled = true task.thread = Thread.new do sleep(delay) if delay.positive? unless task.cancelled begin block.call rescue StandardError => e warn "Debouncer task #{key.inspect}: #{e.class}: #{e.}" end end @mutex.synchronize { @tasks.delete(key) if @tasks[key] == task } end nil end |