{- |
  Create a new RateManager.
-}
newRateManager :: IO RateManager
newRateManager = do
  countT <- atomically (newTVar minBound)
  throttledT <- atomically (newTVar [])
  return R {countT, throttledT}

{- |
  Perform a job in the context of the `RateManager`. The job blocks
  until the rate limit logic says it can go. If the job gets throttled,
  then it re-tries until it is successful (where "successful" means
  anything except `HitLimit`. Throwing an exception counts as "success"
  in this case).

  The job that's executed in each retry is created from the
  client-provided `mkNextJob` function, which accepts the result of the previous
  HitLimit as an input.

  The idea is that the oldest throttled job must complete before any other jobs
  (throttled or not) are allowed to start. Because of concurrency, "oldest" in
  this case means when we discovered the job was throttled, not when it was
  started.

  If there are no jobs that have been throttled, then it is a
  free-for-all. All jobs are executed immediately.

  The 'mkNextJob' argument is responsible for returning a tuple that
  both controls how the timeout is computed, and also specifies a new
  job to try. If the first element of the tuple is 'Nothing', then the
  job will be re-tried using an internally computed backoff
  timeout. If the first element of the tuple is 'Just Int', the 'Int'
  specifies explicitly the number of milliseconds to timeout before trying
  the new job.
-}
newRateManager :: IO RateManager
newRateManager = do
  countT <- atomically (newTVar minBound)
  throttledT <- atomically (newTVar [])
  return R {countT, throttledT}

{- |
  Perform a job in the context of the `RateManager`. The job blocks
  until the rate limit logic says it can go. If the job gets throttled,
  then it re-tries until it is successful (where "successful" means
  anything except `HitLimit`. Throwing an exception counts as "success"
  in this case).

  The job that's executed in each retry is created from the
  client-provided `mkNextJob` function, which accepts the result of the previous
  HitLimit as an input.

  The idea is that the oldest throttled job must complete before any other jobs
  (throttled or not) are allowed to start. Because of concurrency, "oldest" in
  this case means when we discovered the job was throttled, not when it was
  started.

  If there are no jobs that have been throttled, then it is a
  free-for-all. All jobs are executed immediately.

  The 'mkNextJob' argument is responsible for returning a tuple that
  both controls how the timeout is computed, and also specifies a new
  job to try. If the first element of the tuple is 'Nothing', then the
  job will be re-tried using an internally computed backoff
  timeout. If the first element of the tuple is 'Just Int', the 'Int'
  specifies explicitly the number of milliseconds to timeout before trying
  the new job.
-}
newRateManager = do
  countT <- atomically (newTVar minBound)
  throttledT <- atomically (newTVar [])
  return R {countT, throttledT}

{- |
  Perform a job in the context of the `RateManager`. The job blocks
  until the rate limit logic says it can go. If the job gets throttled,
  then it re-tries until it is successful (where "successful" means
  anything except `HitLimit`. Throwing an exception counts as "success"
  in this case).

  The job that's executed in each retry is created from the
  client-provided `mkNextJob` function, which accepts the result of the previous
  HitLimit as an input.

  The idea is that the oldest throttled job must complete before any other jobs
  (throttled or not) are allowed to start. Because of concurrency, "oldest" in
  this case means when we discovered the job was throttled, not when it was
  started.

  If there are no jobs that have been throttled, then it is a
  free-for-all. All jobs are executed immediately.

  The 'mkNextJob' argument is responsible for returning a tuple that
  both controls how the timeout is computed, and also specifies a new
  job to try. If the first element of the tuple is 'Nothing', then the
  job will be re-tried using an internally computed backoff
  timeout. If the first element of the tuple is 'Just Int', the 'Int'
  specifies explicitly the number of milliseconds to timeout before trying
  the new job.
-}