withLockedTransaction :: forall m a. MonadOrville.MonadOrville m => m a -> m a
withLockedTransaction action = do
  go 0
  where
    go :: Int -> m a
    go attempts = do
      result <- runWithTransaction
      case result of
        Just a -> pure a
        Nothing -> do
          MIO.liftIO $ do
            Monad.when (attempts >= 25) $ do
              throwIO $
                MigrationLockError
                  "Giving up after 25 attempts to aquire the migration lock."
            threadDelay 10000

          go $ attempts + 1
    runWithTransaction =
      Transaction.withTransaction $ do
        tryLockResults <-
          Execute.executeAndDecode tryLockExpr lockedMarshaller

        case tryLockResults of
          [True] ->
            -- If we were able to acquire the lock then we can go ahead and
            -- execute the action.
            Just <$> action
          [False] -> do
            -- If we were not able to acquire the lock, wait for the lock to
            -- become available. However, the state of the database schema may
            -- have changed while we were waiting (for instance, another
            -- Orville process migrating the same schema). We must exit the
            -- current transaction and enter a new one, acquiring the lock
            -- again in that new transaction.
            Execute.executeVoid waitForLockExpr
            pure Nothing
          rows ->
            MIO.liftIO . throwIO . MigrationLockError $
              "Expected exactly one row from attempt to acquire migration lock, but got " <> show (length rows)
withLockedTransaction :: forall m a. MonadOrville.MonadOrville m => m a -> m a
withLockedTransaction action = do
  go 0
  where
    go :: Int -> m a
    go attempts = do
      result <- runWithTransaction
      case result of
        Just a -> pure a
        Nothing -> do
          MIO.liftIO $ do
            Monad.when (attempts >= 25) $ do
              throwIO $
                MigrationLockError
                  "Giving up after 25 attempts to aquire the migration lock."
            threadDelay 10000

          go $ attempts + 1
    runWithTransaction =
      Transaction.withTransaction $ do
        tryLockResults <-
          Execute.executeAndDecode tryLockExpr lockedMarshaller

        case tryLockResults of
          [True] ->
            -- If we were able to acquire the lock then we can go ahead and
            -- execute the action.
            Just <$> action
          [False] -> do
            -- If we were not able to acquire the lock, wait for the lock to
            -- become available. However, the state of the database schema may
            -- have changed while we were waiting (for instance, another
            -- Orville process migrating the same schema). We must exit the
            -- current transaction and enter a new one, acquiring the lock
            -- again in that new transaction.
            Execute.executeVoid waitForLockExpr
            pure Nothing
          rows ->
            MIO.liftIO . throwIO . MigrationLockError $
              "Expected exactly one row from attempt to acquire migration lock, but got " <> show (length rows)
withLockedTransaction action = do
  go 0
  where
    go :: Int -> m a
    go attempts = do
      result <- runWithTransaction
      case result of
        Just a -> pure a
        Nothing -> do
          MIO.liftIO $ do
            Monad.when (attempts >= 25) $ do
              throwIO $
                MigrationLockError
                  "Giving up after 25 attempts to aquire the migration lock."
            threadDelay 10000

          go $ attempts + 1
    runWithTransaction =
      Transaction.withTransaction $ do
        tryLockResults <-
          Execute.executeAndDecode tryLockExpr lockedMarshaller

        case tryLockResults of
          [True] ->
            -- If we were able to acquire the lock then we can go ahead and
            -- execute the action.
            Just <$> action
          [False] -> do
            -- If we were not able to acquire the lock, wait for the lock to
            -- become available. However, the state of the database schema may
            -- have changed while we were waiting (for instance, another
            -- Orville process migrating the same schema). We must exit the
            -- current transaction and enter a new one, acquiring the lock
            -- again in that new transaction.
            Execute.executeVoid waitForLockExpr
            pure Nothing
          rows ->
            MIO.liftIO . throwIO . MigrationLockError $
              "Expected exactly one row from attempt to acquire migration lock, but got " <> show (length rows)