aboutsummaryrefslogtreecommitdiffstats
path: root/client/go/internal/vespa/document/circuit_breaker.go
blob: 7a1d2a37b402df7f1b2e121f2267fb82cfe3a42b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package document

import (
	"math"
	"sync/atomic"
	"time"
)

type CircuitState int

const (
	// CircuitClosed represents a closed circuit. Documents are processed successfully
	CircuitClosed CircuitState = iota
	// CircuitHalfOpen represents a half-open circuit. Some errors have happend, but processing may still recover
	CircuitHalfOpen
	// CircuitOpen represents a open circuit. Something is broken. We should no longer process documents
	CircuitOpen
)

type CircuitBreaker interface {
	Success()
	Failure()
	State() CircuitState
}

type timeCircuitBreaker struct {
	graceDuration time.Duration
	doomDuration  time.Duration

	failingSinceMillis atomic.Int64
	halfOpen           atomic.Bool
	open               atomic.Bool

	now func() time.Time
}

func (b *timeCircuitBreaker) Success() {
	b.failingSinceMillis.Store(math.MaxInt64)
	if !b.open.Load() {
		b.halfOpen.CompareAndSwap(true, false)
	}
}

func (b *timeCircuitBreaker) Failure() {
	b.failingSinceMillis.CompareAndSwap(math.MaxInt64, b.now().UnixMilli())
}

func (b *timeCircuitBreaker) State() CircuitState {
	failingDuration := b.now().Sub(time.UnixMilli(b.failingSinceMillis.Load()))
	if failingDuration > b.graceDuration {
		b.halfOpen.CompareAndSwap(false, true)
	}
	if b.doomDuration > 0 && failingDuration > b.doomDuration {
		b.open.CompareAndSwap(false, true)
	}
	if b.open.Load() {
		return CircuitOpen
	} else if b.halfOpen.Load() {
		return CircuitHalfOpen
	}
	return CircuitClosed
}

func NewCircuitBreaker(graceDuration, doomDuration time.Duration) *timeCircuitBreaker {
	b := &timeCircuitBreaker{
		graceDuration: graceDuration,
		doomDuration:  doomDuration,
		now:           time.Now,
	}
	b.failingSinceMillis.Store(math.MaxInt64)
	b.open.Store(false)
	b.halfOpen.Store(false)
	return b
}