Class: Quonfig::DatadirWatcher

Inherits:
Object
  • Object
show all
Defined in:
lib/quonfig/datadir_watcher.rb

Overview

Watches a datadir for changes and fires on_change once per debounced burst. Wraps the ‘listen` gem (github.com/guard/listen), which uses platform-native backends (FSEvents on macOS, inotify on Linux, polling fallback on Windows).

The caller owns parse-then-swap: this class only fires the trigger. Registration failures (read-only fs, immutable container, native backend missing) are surfaced via on_error; in that case start returns false and no listener is held.

Mirrors sdk-node/src/datadirWatcher.ts (qfg-mol-0kr) modulo Ruby idioms: listen does not have an equivalent to Node’s ‘fs.watch(recursive:true)`, but it watches recursively by default.

Constant Summary collapse

LISTEN_FACTORY =

Indirection seam for tests. Production code uses ::Listen; tests can swap in a class that raises from ‘.to` to exercise the registration- failure path without needing a read-only filesystem.

nil

Instance Method Summary collapse

Constructor Details

#initialize(datadir:, debounce_ms:, on_change:, on_error:) ⇒ DatadirWatcher

resolved lazily so the gem can be required late



25
26
27
28
29
30
31
32
33
34
# File 'lib/quonfig/datadir_watcher.rb', line 25

def initialize(datadir:, debounce_ms:, on_change:, on_error:)
  @datadir = datadir
  @debounce_seconds = debounce_ms.to_f / 1000.0
  @on_change = on_change
  @on_error = on_error
  @mutex = Mutex.new
  @scheduled_task = nil
  @listener = nil
  @closed = false
end

Instance Method Details

#startObject

Start the underlying file watcher. Returns true on success, false if registration failed (in which case on_error has already been called and the caller should continue without auto-reload).

Blocks until the listener is in its :processing_events state (or a short safety timeout elapses) so a customer writing to the datadir immediately after the Client constructor returns is detected, rather than racing the listen backend’s async setup.



44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/quonfig/datadir_watcher.rb', line 44

def start
  resolved = File.realpath(@datadir)
  factory = self.class::LISTEN_FACTORY || ::Listen
  @listener = factory.to(resolved) do |_modified, _added, _removed|
    schedule_reload
  end
  @listener.start
  wait_for_listener_ready
  true
rescue StandardError => e
  @on_error.call(e)
  false
end

#stopObject

Stop the watcher and cancel any pending debounce. Idempotent.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/quonfig/datadir_watcher.rb', line 59

def stop
  task, listener = @mutex.synchronize do
    @closed = true
    t = @scheduled_task
    l = @listener
    @scheduled_task = nil
    @listener = nil
    [t, l]
  end
  begin
    task&.cancel
  rescue StandardError
    # best-effort; caller already in shutdown
  end
  begin
    listener&.stop
  rescue StandardError
    # best-effort
  end
end