File size: 2,071 Bytes
e636fbe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
<?php

namespace App\Traits;

use Illuminate\Http\Client\ConnectionException;
use Throwable;

trait Retryable
{
    /**
     * Mencoba menjalankan sebuah callable dengan strategi retry exponential backoff.
     *
     * @param callable $callable Operasi yang akan dieksekusi.
     * @param int $maxAttempts Jumlah maksimal percobaan.
     * @param int $initialDelayMs Waktu tunda awal dalam milidetik.
     * @param float $jitterFactor Faktor untuk menambahkan waktu acak (0 sampai 1).
     * @param array $retryableExceptions Daftar exception yang akan memicu retry. Jika kosong, semua Throwable akan dicoba lagi.
     * @return mixed
     * @throws Throwable
     */
    protected function retry(
        callable $callable,
        int $maxAttempts = 8,
        int $initialDelayMs = 1000,
        float $jitterFactor = 0.2,
        array $retryableExceptions = [ConnectionException::class]
    ) {
        $lastException = null;

        for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
            try {
                return $callable();
            } catch (Throwable $e) {
                $lastException = $e;

                if ($attempt >= $maxAttempts || !$this->shouldRetry($e, $retryableExceptions)) {
                    throw $e;
                }

                $delay = (int) ($initialDelayMs * (2 ** ($attempt - 1)));
                $jitter = (int) ($delay * $jitterFactor * (random_int(0, 100) / 100));
                $wait = $delay + $jitter;

                usleep($wait * 1000); // usleep menggunakan mikrosdetik
            }
        }

        throw $lastException ?? new \RuntimeException('Operasi retry gagal tanpa exception spesifik.');
    }

    private function shouldRetry(Throwable $e, array $retryableExceptions): bool
    {
        if (empty($retryableExceptions)) {
            return true; // Selalu retry jika daftar kosong
        }

        foreach ($retryableExceptions as $retryable) {
            if ($e instanceof $retryable) {
                return true;
            }
        }

        return false;
    }
}