Module: Pikuri::Finalizers

Defined in:
lib/pikuri/finalizers.rb

Overview

Process-global teardown registry: one at_exit for the whole process, with everything owning a resource that needs orderly shutdown (agents, VectorDb::Server::Chroma, background workers) registering here instead of growing its own at_exit. Agent#on_close promoted per-agent → per-process — the same LIFO + per-handler-rescue + idempotent shape.

Why one chokepoint

Independent at_exit hooks fire in file-load order — invisible and fragile. One registry makes the order explicit: the SIGTERM-the-strays backstop (Subprocess.cleanup!) registers at load time, so it sits at the bottom of the LIFO stack and runs last — after agents and servers (registered at construction time) close gracefully, while the subprocess machinery they shell out to during close (a docker stop) is still live.

Contract

A registrant MUST respond to #close, idempotent and tolerant of running at exit (the host may have closed it earlier). Pass a block instead for teardown with no natural #close (+Finalizers.register { Pikuri::Subprocess.cleanup! }+).

Order: LIFO

Last registered, first closed. A registrant depending on an earlier one (a background indexer writing into VectorDb::Server::Chroma) registers later and tears down first — registration order is dependency order.

Errors are contained

Each #close runs in its own rescue (a raise is logged, the sweep continues). Finalizers.run! drains the registry, so a second call closes nothing.

Defined Under Namespace

Classes: Closer

Constant Summary collapse

LOGGER =

Returns subsystem logger for contained teardown failures.

Returns:

  • (Logger)

    subsystem logger for contained teardown failures.

Pikuri.logger_for('Finalizers')

Class Method Summary collapse

Class Method Details

.register(closeable = nil) { ... } ⇒ #close

Register a closeable (or a block) to tear down at exit. Returns the handle so the caller can later unregister it — a resource closed explicitly before exit should drop out to be GC'd, not pinned alive.

Parameters:

  • closeable (#close, nil) (defaults to: nil)

    resource to close; omit when passing a block

Yields:

  • teardown for resources with no #close

Returns:

  • (#close)

    the registered handle (the object, or the Closer wrapping the block); pass to unregister

Raises:

  • (ArgumentError)

    if neither an object nor a block is given



62
63
64
65
66
67
68
69
70
# File 'lib/pikuri/finalizers.rb', line 62

def register(closeable = nil, &block)
  unless closeable || block
    raise ArgumentError, 'Finalizers.register requires an object or a block'
  end

  handle = closeable || Closer.new(block)
  @mutex.synchronize { @registered << handle }
  handle
end

.run!void

This method returns an undefined value.

Close every registrant in LIFO order, each guarded by its own rescue. Wired to at_exit below. Draining under the lock makes a repeat call a no-op and stays safe against a concurrent caller.



86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/pikuri/finalizers.rb', line 86

def run!
  handles = @mutex.synchronize do
    taken = @registered.reverse
    @registered.clear
    taken
  end

  handles.each do |handle|
    handle.close
  rescue StandardError => e
    LOGGER.warn("finalizer #{handle.class} raised #{e.class}: #{e.message}")
  end
end

.unregister(handle) ⇒ void

This method returns an undefined value.

Drop a previously-registered handle. Idempotent.

Parameters:

  • handle (#close)

    the value returned by register



76
77
78
79
# File 'lib/pikuri/finalizers.rb', line 76

def unregister(handle)
  @mutex.synchronize { @registered.delete(handle) }
  nil
end