{"record":{"id":"4f7fb7b7070ae64f","repo":"micro/go-micro","slug":"circuit-breaker-half-open-probe-limit-reached","errorCode":null,"errorMessage":"circuit breaker half-open (probe limit reached)","messagePattern":"circuit breaker half-open \\(probe limit reached\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"gateway/mcp/circuitbreaker.go","lineNumber":103,"sourceCode":"\tcb.mu.Lock()\n\tdefer cb.mu.Unlock()\n\n\tswitch cb.state {\n\tcase circuitClosed:\n\t\treturn nil\n\tcase circuitOpen:\n\t\tif time.Since(cb.lastFailure) > cb.timeout {\n\t\t\tcb.state = circuitHalfOpen\n\t\t\tcb.halfOpenUsed = 0\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"circuit breaker open (consecutive failures: %d)\", cb.failures)\n\tcase circuitHalfOpen:\n\t\tif cb.halfOpenUsed < cb.maxHalfOpen {\n\t\t\tcb.halfOpenUsed++\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"circuit breaker half-open (probe limit reached)\")\n\t}\n\treturn nil\n}\n\n// RecordSuccess records a successful call. If half-open, closes the circuit.\nfunc (cb *circuitBreaker) RecordSuccess() {\n\tcb.mu.Lock()\n\tdefer cb.mu.Unlock()\n\n\tcb.failures = 0\n\tcb.state = circuitClosed\n}\n\n// RecordFailure records a failed call. May trip the circuit open.\nfunc (cb *circuitBreaker) RecordFailure() {\n\tcb.mu.Lock()\n\tdefer cb.mu.Unlock()\n","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/micro/go-micro/blob/24529f140421a11a33b6999ab7944f2021cfd69c/gateway/mcp/circuitbreaker.go#L85-L121","documentation":"After the breaker's timeout, Allow() moves the circuit to half-open and permits at most maxHalfOpen probe calls. Once halfOpenUsed reaches maxHalfOpen, additional calls are rejected with this error until a probe succeeds (closing the circuit) or fails (reopening it).","triggerScenarios":"Calling Allow() while the breaker is half-open and cb.halfOpenUsed >= cb.maxHalfOpen, i.e. more concurrent calls than the probe limit arrive during the recovery window.","commonSituations":"High traffic bursts right after the cool-down expires; maxHalfOpen configured to 1 and multiple goroutines probing; slow probes keeping the breaker half-open for a long stretch.","solutions":["Retry after an in-flight probe completes: a success closes the circuit and normal calls resume.","Increase maxHalfOpen if the target can safely absorb more concurrent probes.","Apply client-side queuing/rate limiting so excess calls wait instead of being rejected.","Speed up probe completion by fixing whatever makes the target slow or flaky."],"exampleFix":"// before\nif err := cb.Allow(); err != nil { call() } // ignoring rejection, hammering half-open\n// after\nif err := cb.Allow(); err != nil {\n    <-probeDone // wait for the allowed probe to finish\n    continue\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"func isBreakerHalfOpen(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"circuit breaker half-open\")\n}","tryCatchPattern":"if err := cb.Allow(); err != nil {\n    if isBreakerHalfOpen(err) {\n        select {\n        case <-probeFinished: // a permitted probe completed; retry\n        case <-time.After(backoff):\n        }\n    }\n    return errRetryLater\n}","preventionTips":["Queue or rate-limit callers so they wait during the half-open window.","Increase maxHalfOpen only if the recovering target can absorb the probes.","Treat half-open rejections as temporary, not fatal.","Keep probe requests lightweight so the circuit closes quickly."],"tags":["circuit-breaker","resilience","rate-limiting"],"backgroundTag":"circuit-breaker-half-open","analyzedSha":"24529f140421a11a33b6999ab7944f2021cfd69c","analyzedAt":"2026-09-01T02:52:24.923Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}