• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

daycry / jobs / 24568210769

05 Apr 2026 08:48AM UTC coverage: 53.938% (-2.2%) from 56.164%
24568210769

push

github

daycry
Optimize

50 of 192 new or added lines in 14 files covered. (26.04%)

3 existing lines in 3 files now uncovered.

1219 of 2260 relevant lines covered (53.94%)

4.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

30.3
/src/Libraries/CircuitBreaker.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of Daycry Queues.
7
 *
8
 * (c) Daycry <daycry9@proton.me>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace Daycry\Jobs\Libraries;
15

16
/**
17
 * Circuit Breaker pattern for queue backend resilience.
18
 *
19
 * States:
20
 *  - CLOSED: Normal operation. Failures are counted.
21
 *  - OPEN: Backend is considered unavailable. All calls are skipped for a cooldown period.
22
 *  - HALF_OPEN: After cooldown expires, one probe call is allowed to test recovery.
23
 *
24
 * State is stored in the cache service so it persists across worker restarts.
25
 */
26
class CircuitBreaker
27
{
28
    public const STATE_CLOSED    = 'closed';
29
    public const STATE_OPEN      = 'open';
30
    public const STATE_HALF_OPEN = 'half_open';
31

32
    public function __construct(
33
        private string $name,
34
        private int $failureThreshold = 5,
35
        private int $cooldownSeconds = 60,
36
    ) {
37
    }
1✔
38

39
    /**
40
     * Check if the backend is available for use.
41
     * Returns false when the circuit is OPEN (within cooldown period).
42
     */
43
    public function isAvailable(): bool
44
    {
45
        $state = $this->getState();
1✔
46

47
        if ($state === self::STATE_CLOSED) {
1✔
48
            return true;
1✔
49
        }
50

NEW
51
        if ($state === self::STATE_OPEN) {
×
NEW
52
            $openedAt = (int) $this->cacheGet('opened_at');
×
NEW
53
            if (time() - $openedAt >= $this->cooldownSeconds) {
×
54
                // Cooldown expired, transition to half-open
NEW
55
                $this->cacheSet('state', self::STATE_HALF_OPEN, $this->cooldownSeconds * 2);
×
56

NEW
57
                return true; // Allow one probe
×
58
            }
59

NEW
60
            return false; // Still in cooldown
×
61
        }
62

63
        // HALF_OPEN: allow probe
NEW
64
        return true;
×
65
    }
66

67
    /**
68
     * Record a successful operation. Resets the circuit to CLOSED.
69
     */
70
    public function recordSuccess(): void
71
    {
72
        $this->cacheSet('failures', 0, $this->cooldownSeconds * 2);
1✔
73
        $this->cacheSet('state', self::STATE_CLOSED, $this->cooldownSeconds * 2);
1✔
74
    }
75

76
    /**
77
     * Record a failed operation. Increments failure counter and may trip the circuit.
78
     */
79
    public function recordFailure(): void
80
    {
NEW
81
        $state = $this->getState();
×
82

NEW
83
        if ($state === self::STATE_HALF_OPEN) {
×
84
            // Probe failed, re-open the circuit
NEW
85
            $this->tripCircuit();
×
86

NEW
87
            return;
×
88
        }
89

NEW
90
        $failures = (int) $this->cacheGet('failures') + 1;
×
NEW
91
        $this->cacheSet('failures', $failures, $this->cooldownSeconds * 2);
×
92

NEW
93
        if ($failures >= $this->failureThreshold) {
×
NEW
94
            $this->tripCircuit();
×
95
        }
96
    }
97

98
    public function getState(): string
99
    {
100
        return (string) ($this->cacheGet('state') ?: self::STATE_CLOSED);
1✔
101
    }
102

103
    public function getFailureCount(): int
104
    {
NEW
105
        return (int) ($this->cacheGet('failures') ?: 0);
×
106
    }
107

108
    /**
109
     * Manually reset the circuit breaker to CLOSED state.
110
     */
111
    public function reset(): void
112
    {
NEW
113
        $cache = service('cache');
×
NEW
114
        $cache->delete($this->key('failures'));
×
NEW
115
        $cache->delete($this->key('state'));
×
NEW
116
        $cache->delete($this->key('opened_at'));
×
117
    }
118

119
    private function tripCircuit(): void
120
    {
NEW
121
        $this->cacheSet('state', self::STATE_OPEN, $this->cooldownSeconds * 2);
×
NEW
122
        $this->cacheSet('opened_at', time(), $this->cooldownSeconds * 2);
×
NEW
123
        $this->cacheSet('failures', 0, $this->cooldownSeconds * 2);
×
124
    }
125

126
    private function key(string $suffix): string
127
    {
128
        return "circuit_{$this->name}_{$suffix}";
1✔
129
    }
130

131
    private function cacheGet(string $suffix): mixed
132
    {
133
        return service('cache')->get($this->key($suffix));
1✔
134
    }
135

136
    private function cacheSet(string $suffix, mixed $value, int $ttl): void
137
    {
138
        service('cache')->save($this->key($suffix), $value, $ttl);
1✔
139
    }
140
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc