# `SuperCache.Cluster.NodeMonitor`
[🔗](https://github.com/ohhi-vn/super_cache/blob/main/lib/cluster/node_monitor.ex#L1)

Monitors a declared set of nodes and notifies `SuperCache.Cluster.Manager`
when they join or leave, so partition maps are updated.

## Node-source options

| Option         | Type                     | Description                                          |
|----------------|--------------------------|------------------------------------------------------|
| `:nodes`       | `[node()]`               | Static list evaluated once at start-up.              |
| `:nodes_mfa`   | `{module, atom, [term]}` | Called at init and then every `:refresh_ms`.         |
| *(none)*       | —                        | Falls back to watching **all** Erlang nodes (legacy).|

## Other options

| Option        | Type    | Default | Description                                               |
|---------------|---------|---------|-----------------------------------------------------------|
| `:refresh_ms` | pos_int | `5_000` | Re-evaluation interval for `:nodes_mfa`. Ignored for the |
|               |         |         | other two sources.                                        |

## Source behaviour

* **`:nodes` (static)** — the managed set is fixed at start-up; no refresh
  timer is scheduled. `:nodeup` / `:nodedown` kernel messages are forwarded
  to `Manager` only for nodes in the set.
* **`:nodes_mfa` (dynamic)** — the MFA is called at init and on every
  `:refresh` tick. Nodes that appear in the new result are connected and
  announced as up; nodes that disappear are announced as down.
  If the MFA raises an exception **or exits** (e.g. a named process it calls
  has been stopped), the error is logged, an empty list is used, and the
  monitor continues running — it does **not** crash.
* **`:all` (fallback)** — behaviour identical to the original implementation:
  all `:nodeup` / `:nodedown` kernel events are forwarded unconditionally.
  This is the default when neither `:nodes` nor `:nodes_mfa` is supplied,
  which allows the application supervisor to start the monitor with `[]`
  during normal boot without error.

## Runtime reconfiguration

`reconfigure/1` swaps the source and managed set atomically via a
`GenServer.call/2` without restarting the process or touching the OTP
supervisor. Any pending MFA `:refresh` timers from the previous source
are superseded; stale timer messages that arrive after reconfiguration
are silently ignored because they carry the old source ref in a tagged
tuple `{:refresh, ref}`.

When switching sources the monitor computes the symmetric difference
between the old and new managed sets and immediately announces:
- nodes that *appeared* in the new set → `Manager.node_up/1`
- nodes that *disappeared* from the old set → `Manager.node_down/1`

## Examples

    # Started by the application supervisor with no configuration (legacy mode)
    NodeMonitor.start_link([])

    # Static list
    NodeMonitor.start_link(nodes: [:"a@host", :"b@host"])

    # MFA — e.g. read from a config server or service-discovery endpoint
    NodeMonitor.start_link(nodes_mfa: {MyApp.Discovery, :cache_nodes, []})
    NodeMonitor.start_link(nodes_mfa: {MyApp.Discovery, :cache_nodes, []}, refresh_ms: 10_000)

    # Reconfigure at runtime (no supervisor restart needed)
    NodeMonitor.reconfigure(nodes: [:"a@host", :"b@host"])

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `reconfigure`

```elixir
@spec reconfigure(keyword()) :: :ok
```

Reconfigure the node source at runtime without restarting the process.

Accepts the same opts as `start_link/1`. Computes the diff between the
old and new managed sets and immediately calls `Manager.node_up/1` /
`Manager.node_down/1` for nodes that joined or left the managed set.

MFA refresh timers from the previous configuration are superseded; any
stale `{:refresh, ref}` messages that fire after reconfiguration are
ignored because they carry a ref that no longer matches `state.refresh_ref`.

Primarily useful in tests that need to switch between static lists or MFA
sources without touching the OTP supervisor.

# `start_link`

---

*Consult [api-reference.md](api-reference.md) for complete listing*
