monitor: run log-plugin subprocesses off the tick (cached eval) — fix loop stall - #1660
monitor: run log-plugin subprocesses off the tick (cached eval) — fix loop stall#1660svaroqui wants to merge 1 commit into
Conversation
…slow plugin can't freeze the loop tickBody -> CheckLogPlugins -> RunLogPlugins -> ExternalLogPlugin.Evaluate shells out to the plugin binary (os/exec) INLINE in the monitor tick. A slow/unresponsive server makes that subprocess spike, and with N plugins x M servers run serially the tick's cadence collapses to minutes -> heartbeat stalls -> GWARN001. Proven from a live goroutine dump: tickBody parked in os.Process.Wait inside cmd.Output(). Move only the Evaluate() subprocess off the tick: cache its result per (server, plugin) and refresh it in a background goroutine (cachedPluginEval). The whole apply half -- findings -> Security/Workload/Schema/main state machines, score, WTAG, graphite -- is UNCHANGED and still runs every tick from the cached result, so findings stay asserted between refreshes and never flap open/resolve (those state machines are ClearState'd every tick). Plugin binaries, wire protocol, and Evaluate itself are untouched. - ServerMonitor.pluginEval cache (+ mutex); inFlight guard prevents duplicate concurrent evals; refresh every pluginEvalIntervalTicks (15). - Test: eval runs async (first call empty), then cached, and is not re-run within the interval (no flap).
Code review findingsScope: 1. Data race on
|
Problem (proven from a live goroutine dump on dbaas-fr-2)
tickBody → CheckLogPlugins → RunLogPlugins → ExternalLogPlugin.Evaluateshells out to the plugin binary (os/exec) inline in the monitor tick. The dump showedtickBodyparked inos.Process.Waitinsidecmd.Output(). With N plugins × M servers run serially in the tick, a slow/unresponsive server makes the tick's cadence collapse to minutes → heartbeat stalls →GWARN001(the HB supervision correctly reports it). This is the violation of "the monitor loop must never block on anything."Fix
Move only the
Evaluate()subprocess off the tick:cachedPluginEval()caches the result per (server, plugin) and refreshes it in a background goroutine. The plugin binaries, wire protocol, andEvaluateitself are untouched.The entire apply half is unchanged and still runs every tick from the cached result — findings →
SecurityStateMachine/WorkloadStateMachine/SchemaStateMachine/StateMachine, score, WTAG, graphite.Why it's flap-safe
Those plugin state machines are
ClearState'd every tick (cluster.go:1470-1472). Because the apply still runs every tick (from cache), every finding is re-asserted every tick and never resolves-and-reopens. A naive fire-and-forget of the wholeCheckLogPluginswould have flapped all findings (dynamic per-server composite keys, not listable inpstatesN) — this cached-read design avoids that.Notes
pluginEvalIntervalTicks = 15(const; can become a flag).inFlightguard prevents duplicate concurrent evals; goroutines are bounded by the plugin's own 5s+WaitDelay.Tests
TestCachedPluginEval_OffTickAndNoFlap: eval runs async (first call empty), then cached, and is not re-run within the interval.Delicate state code — should be validated on the OpenSVC topology matrix before it ships.