Nebo15 / sage

A dependency-free tool to run distributed transactions in Elixir, inspired by Sagas pattern.
MIT License
912 stars 40 forks source link

Retry opts problem solve this please #81

Open matheuscamarques opened 3 months ago

matheuscamarques commented 3 months ago

The error is occurring because the retry_opts() function, for retry_limit, is set only once for a specific function. However, this function has two different error cases for two different retry_limit approaches.

In one case, it should be retried 3 times, and in another case, it should be retried 10 times.

image

image

 test "full errors retries but ok in last effect" do

      socket_close_retries = GetPismoCardDetails.retry_info(:socket_close_retries) - 1
      server_error_retries = GetPismoCardDetails.retry_info(:server_error_retries) - 1

      for _i <- 1..server_error_retries do
        expect(PismoMock, :get_card_pci_information, fn _ ->
          {:error, {:server_error, %{"error" => "Internal Server Error"}}}
        end)
      end

      for _i <- 1..socket_close_retries do
        expect(PismoMock, :get_card_pci_information, fn _ ->
          {:error, "socket closed"}
        end)
      end

      expect(PismoMock, :get_card_pci_information, fn _ ->
        {:ok, %{pan: "#####"}}
      end)

      {:ok, "#####"} =
        GetPismoCardDetails.execute(%{
          pismo_card_id: :rand.uniform(10_000),
          pismo_account_id: :rand.uniform(10_000)
        })
    end
  end
  defp get_card_pci_information_compensation(
         {:server_error, %{"error" => "Internal Server Error"}} = _error,
         _effects_so_far,
         %{pismo_card_id: pismo_card_id}
       ) do
    Logger.warning("Failed to fetch pan due to Internal Server Error, retrying it",
      pismo_card_id: pismo_card_id
    )

    {:retry, retry_limit: 3}
  end

  defp get_card_pci_information_compensation(
         "socket closed" = error,
         _effects_so_far,
         %{pismo_card_id: pismo_card_id}
       ) do
    Logger.warning("Failed to fetch card pan due to #{inspect({:error, error})} retrying it",
      pismo_card_id: pismo_card_id
    )

    {:retry, retry_limit: 10}
  end
matheuscamarques commented 3 months ago

Rewrite test for adpt to case

test "full errors retries but ok in last effect" do
      # As the attempt count is global, the lowest number of attempts prevails.
      # In other words, if there are 5 errors, and on the 6th error there's a compensation with a maximum retry of 3,
      # the compensation will indicate that all attempts have been exceeded because 6 is less than 3.
      # Consequently, the execution terminates.

      lower_limit =
        min(
          GetPismoCardDetails.retry_info(:socket_close_retries),
          GetPismoCardDetails.retry_info(:server_error_retries)
        )

      server_error_retries = rem(lower_limit, 2)
      socket_close_retries = rem(lower_limit, 2)

      for _i <- 1..socket_close_retries do
        expect(PismoMock, :get_card_pci_information, fn _ ->
          {:error, "socket closed"}
        end)
      end

      for _i <- 1..server_error_retries do
        expect(PismoMock, :get_card_pci_information, fn _ ->
          {:error, {:server_error, %{"error" => "Internal Server Error"}}}
        end)
      end

      expect(PismoMock, :get_card_pci_information, fn _ ->
        {:ok, %{pan: "#####"}}
      end)

      {:ok, "#####"} =
        GetPismoCardDetails.execute(%{
          pismo_card_id: :rand.uniform(10_000),
          pismo_account_id: :rand.uniform(10_000)
        })
    end
matheuscamarques commented 3 months ago

Test with comments

test "full errors retries but ok in last effect" do
      # As the attempt count is global, the lowest number of attempts prevails.
      # In other words, if there are 5 errors, and on the 6th error there's a compensation with a
      # maximum retry of 3,
      # the compensation will indicate that all attempts have been exceeded because 6 is less than 3.
      # Consequently, the execution terminates.
      # Determine the lower and upper retry limits
      lower_limit =
        min(
          GetPismoCardDetails.retry_info(:socket_close_retries),
          GetPismoCardDetails.retry_info(:server_error_retries)
        )

      upper_limit =
        max(
          GetPismoCardDetails.retry_info(:socket_close_retries),
          GetPismoCardDetails.retry_info(:server_error_retries)
        )

      # Determine the effective retry limit based on the lower and upper limits
      effective_retry_limit =
        case upper_limit == lower_limit do
          true -> rem(lower_limit, 2)
          false -> lower_limit
        end

      # Determine the number of retries for each error type
      server_error_retries = rem(effective_retry_limit, 2)
      socket_close_retries = rem(effective_retry_limit, 2)

      # Simulate socket close errors
      for _i <- 1..socket_close_retries do
        expect(PismoMock, :get_card_pci_information, fn _ ->
          {:error, "socket closed"}
        end)
      end

      # Simulate server errors
      for _i <- 1..server_error_retries do
        expect(PismoMock, :get_card_pci_information, fn _ ->
          {:error, {:server_error, %{"error" => "Internal Server Error"}}}
        end)
      end

      # Expect a successful response after simulated errors
      expect(PismoMock, :get_card_pci_information, fn _ ->
        {:ok, %{pan: "#####"}}
      end)

      # Execute the function and ensure it returns successfully
      {:ok, "#####"} =
        GetPismoCardDetails.execute(%{
          pismo_card_id: :rand.uniform(10_000),
          pismo_account_id: :rand.uniform(10_000)
        })
    end