Skip to content

v4.2.0

Latest

Choose a tag to compare

@cep21 cep21 released this 18 Aug 10:48
1f3ac21

Bug-fix and performance release focused on the open/half-open state machine and rolling-window statistics. No breaking API changes (verified with gorelease; one additive method, SimpleBadRequest.Unwrap). Requires Go 1.24+ (CI covers 1.24, 1.25, 1.26).

Several fixes change observable behavior — see Behavior changes in v4.2 before upgrading, especially if you use ForceOpen, drive a hystrix.Closer directly in tests, or implement custom ClosedToOpen/OpenToClosed logic.

Bug fixes (#159)

Circuit state machine

  • ForceOpen now rejects every request; previously half-open probes still ran (one per SleepWindow, or every request with a permissive closer).
  • OpenCircuit()/CloseCircuit() and automatic closes act on the underlying state while ForceOpen/ForcedClosed is set, instead of silently doing nothing.
  • Metrics.Opened/Closed are delivered exactly once, strictly alternating and in transition order, without locks held — a listener that re-enters the circuit no longer deadlocks and a slow/panicking listener can't wedge later deliveries.
  • ExecutionTimeout is read once per run; a concurrent SetConfigThreadSafe could produce a deadline in the past and spurious ErrTimeouts that opened a healthy circuit.
  • An open circuit already at MaxConcurrentRequests short-circuits without burning the closer's half-open permit.
  • Execute/Run/Go with a nil runFunc no longer panic on nil/empty/Disabled circuits.
  • SetConfigNotThreadSafe on an open circuit informs the new open/close logic so the circuit can still recover.

closers/hystrix

  • Closer honors SleepWindow: probes are admitted only after the window armed by Opened(), and only requests that started after the circuit opened count toward closing it. Previously any in-flight success closed the circuit immediately and a stale in-flight failure could void a healthy probe.
  • Closer.SetConfigThreadSafe no longer races on TimeAfterFunc.
  • Opener.ShouldOpen uses integer math — error rates exactly on the threshold (e.g. 57/100 vs 57%) now trip.
  • Opener.SetConfigNotThreadSafe with unset NumBuckets no longer panics.

faststats

  • RollingBuckets.Advance clears expired buckets before publishing the new index. The old lock-free walk let a burst of writers after an idle gap have their writes wiped, so the opener and RunStats under-counted the first requests after >1 window of quiet.
  • RollingCounter derives its rolling sum from the buckets: never negative, one fewer contended atomic per Inc. JSON output unchanged.
  • RollingPercentile snapshots no longer surface durations from an expired window.
  • UnmarshalJSON rejects negative bucket state instead of panicking later; absolute bucket index is int64 (wrapped after ~24 days of 1ms buckets on 32-bit).

Other

  • Manager.CreateCircuit checks for duplicates before running DefaultCircuitProperties (a failed duplicate create leaked rolling.StatFactory entries) and no longer holds its lock while user constructors run (re-entrant GetCircuit deadlocked).
  • rolling Run/Fallback stats fill defaults instead of dividing by zero on partial configs.
  • metriceventstream sends headers immediately; with no circuits registered clients previously got no response.
  • expvar output keeps collectors whose Var has no Value() method.
  • MockClock.Add is atomic and fires callbacks in deadline order.

Performance (#159, benchstat -cpu=8)

  • IsBadRequest: 75–150 ns + 1 alloc → 2–5 ns, 0 allocs.
  • Shed/error paths (open, failure+fallback, throttled fallback): −30…−48 %, 0 allocs (were 1–3).
  • Parallel success path: −37…−45 % (per-request counters on their own cache line; no atomic RMW in Closer.Success while closed).
  • RollingPercentile snapshot: −71 % time, 13 → 1 allocs.

Toolchain

  • Go 1.24, 1.25 and 1.26 supported; go.mod directive raised to 1.24 (#160).
  • stretchr/testify v1.12.0 (#158); CI action bumps (#156, #157).

Full changelog: v4.1.0...v4.2.0