From c5240ad31225fdb482713f8799016bde87ffcc2f Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone <exarkun@twistedmatrix.com> Date: Wed, 3 Jun 2020 10:58:24 -0400 Subject: [PATCH] Take a shot at this with the `retrying` function --- PaymentServer.cabal | 1 + nix/PaymentServer.nix | 1 + src/PaymentServer/Redemption.hs | 29 ++++++++++++++++++++++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/PaymentServer.cabal b/PaymentServer.cabal index 8e6fee7..2db7528 100644 --- a/PaymentServer.cabal +++ b/PaymentServer.cabal @@ -42,6 +42,7 @@ library , containers , cryptonite , sqlite-simple + , retry default-language: Haskell2010 ghc-options: -Wmissing-import-lists -Wunused-imports pkgconfig-depends: libchallenge_bypass_ristretto_ffi diff --git a/nix/PaymentServer.nix b/nix/PaymentServer.nix index 5416280..af085ed 100644 --- a/nix/PaymentServer.nix +++ b/nix/PaymentServer.nix @@ -76,6 +76,7 @@ in { system, compiler, flags, pkgs, hsPkgs, pkgconfPkgs, ... }: (hsPkgs."containers" or (buildDepError "containers")) (hsPkgs."cryptonite" or (buildDepError "cryptonite")) (hsPkgs."sqlite-simple" or (buildDepError "sqlite-simple")) + (hsPkgs."retry" or (buildDepError "retry")) ]; pkgconfig = [ (pkgconfPkgs."libchallenge_bypass_ristretto_ffi" or (pkgConfDepError "libchallenge_bypass_ristretto_ffi")) diff --git a/src/PaymentServer/Redemption.hs b/src/PaymentServer/Redemption.hs index 701677d..ca7ec93 100644 --- a/src/PaymentServer/Redemption.hs +++ b/src/PaymentServer/Redemption.hs @@ -14,6 +14,13 @@ module PaymentServer.Redemption import GHC.Generics ( Generic ) + +import Control.Retry + ( retrying + , constantDelay + , limitRetries + ) + import Control.Monad.IO.Class ( liftIO ) @@ -155,6 +162,25 @@ jsonErr400 reason = err400 redemptionServer :: VoucherDatabase d => Issuer -> d -> Server RedemptionAPI redemptionServer = redeem +-- | Try an operation repeatedly for several minutes with a brief delay +-- between tries. +retry :: IO (Either RedeemError()) -> IO (Either RedeemError()) +retry op = + retrying policy shouldRetry (\_ -> op) + where + -- Total duration for which to retry in milliseconds. + totalRetryDuration = 3 * 60 * 1000 + -- Time to delay between each try in milliseconds. + perRetryDelay = 500 + -- Limit on the number of retries. + numRetries = totalRetryDuration `div` totalRetryDuration + + policy = constantDelay (perRetryDelay * 1000) <> limitRetries numRetries + shouldRetry status value = + case value of + Left NotPaid -> return True + _ -> return False + -- | Handler for redemption requests. Use the database to try to redeem the -- voucher and return signatures. Return a failure if this is not possible -- (eg because the voucher was already redeemed). @@ -163,8 +189,9 @@ redeem issue database (Redeem voucher tokens counter) = if counter < 0 || counter >= maxCounter then throwError $ jsonErr400 (CounterOutOfBounds 0 maxCounter counter) else do + let fingerprint = fingerprintFromTokens tokens - result <- liftIO $ redeemVoucherWithCounter database voucher fingerprint counter + result <- liftIO . retry $ redeemVoucherWithCounter database voucher fingerprint counter case result of Left NotPaid -> do throwError $ jsonErr400 Unpaid -- GitLab