Skip to content

coinprices/PriceRetryPolicy.hpp

Namespaces

Name
sgns

Classes

Name
struct sgns::RetryConfig
Fixed retry/backoff schedule (D-12): 1s -> 2s -> (4s, never reached under the default cap of 3 attempts). Tests inject zeros for deterministic zero-backoff runs.
class sgns::RateLimitHoldOff
Client-side CoinGecko rate-limit hold-off (D-13): a 429 sets a hold-until timestamp; while held, the CoinGecko tier is skipped entirely (fallback goes straight to the next tier).

Types

Name
enum class RetryDecision { Retry, GiveUp}
Outcome of a retry decision.

Functions

Name
bool IsTransient(const PriceFetchFailure & error)
True only for transport-class failures (D-11): timeouts, connection resets, DNS failures. NEVER true for a status-bearing failure (403/429/any HTTP status), parse errors, or input errors.
bool IsTransientTransport(http::ClientError error)
Pre-mapping classifier over the transport's own error enum — true for ClientError::TIMEOUT / CONNECT_FAILED / RESOLVE_FAILED only.
RetryDecision ShouldRetry(const PriceFetchFailure & error, int attempt, const RetryConfig & config)
Decide whether to make another attempt after a failure.

Types Documentation

enum RetryDecision

Enumerator Value Description
Retry
GiveUp

Outcome of a retry decision.

Functions Documentation

function IsTransient

bool IsTransient(
    const PriceFetchFailure & error
)

True only for transport-class failures (D-11): timeouts, connection resets, DNS failures. NEVER true for a status-bearing failure (403/429/any HTTP status), parse errors, or input errors.

function IsTransientTransport

bool IsTransientTransport(
    http::ClientError error
)

Pre-mapping classifier over the transport's own error enum — true for ClientError::TIMEOUT / CONNECT_FAILED / RESOLVE_FAILED only.

function ShouldRetry

RetryDecision ShouldRetry(
    const PriceFetchFailure & error,
    int attempt,
    const RetryConfig & config
)

Decide whether to make another attempt after a failure.

Parameters:

  • error The failure from the just-finished attempt
  • attempt 1-based index of the attempt that just failed
  • config The retry schedule in force

Return: Retry iff the error is transient AND attempt < maxAttempts; GiveUp otherwise (403/429 fall through immediately by construction)

Source code

#pragma once

#include "PriceFetchError.hpp"
#include <HTTPTypes.hpp>

#include <array>
#include <chrono>
#include <functional>
#include <mutex>
#include <utility>

namespace sgns
{
    struct RetryConfig
    {
        int maxAttempts = 3;

        std::array<std::chrono::milliseconds, 3> backoffBeforeRetry{ std::chrono::milliseconds( 1000 ),
                                                                     std::chrono::milliseconds( 2000 ),
                                                                     std::chrono::milliseconds( 4000 ) };
    };

    enum class RetryDecision
    {
        Retry,
        GiveUp
    };

    bool IsTransient( const PriceFetchFailure &error );

    bool IsTransientTransport( http::ClientError error );

    RetryDecision ShouldRetry( const PriceFetchFailure &error, int attempt, const RetryConfig &config );

    class RateLimitHoldOff
    {
    public:
        using Clock = std::function<std::chrono::system_clock::time_point()>;

        explicit RateLimitHoldOff( Clock now = [] { return std::chrono::system_clock::now(); } )
            : now_( std::move( now ) )
        {
        }

        void TriggerHoldOff( std::chrono::seconds duration )
        {
            std::lock_guard<std::mutex> lock( mutex_ );
            holdUntil_ = now_() + duration;
        }

        bool IsHeldOff() const
        {
            std::lock_guard<std::mutex> lock( mutex_ );
            return now_() < holdUntil_;
        }

    private:
        mutable std::mutex                        mutex_;
        std::chrono::system_clock::time_point     holdUntil_{};
        Clock                                     now_;
    };
} // namespace sgns

Updated on 2026-10-09 at 04:02:50 +0000