Merge pull request #8997 from blockscout/ap-isolate-requests-throttable-error-count

Isolate throttable error count by request method
pull/8975/head
Victor Baranov 11 months ago committed by GitHub
commit ecada141da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 29
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex
  3. 29
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/request_coordinator_test.exs

@ -4,6 +4,7 @@
### Features ### Features
- [#8997](https://github.com/blockscout/blockscout/pull/8997) - Isolate throttable error count by request method
- [#8972](https://github.com/blockscout/blockscout/pull/8972) - BENS integration - [#8972](https://github.com/blockscout/blockscout/pull/8972) - BENS integration
- [#8957](https://github.com/blockscout/blockscout/pull/8957) - Add Tx Interpreter Service integration - [#8957](https://github.com/blockscout/blockscout/pull/8957) - Add Tx Interpreter Service integration

@ -58,7 +58,7 @@ defmodule EthereumJSONRPC.RequestCoordinator do
alias EthereumJSONRPC.{RollingWindow, Tracer, Transport} alias EthereumJSONRPC.{RollingWindow, Tracer, Transport}
@error_key :throttleable_error_count @error_key_base "throttleable_error_count"
@throttle_key :throttle_requests_count @throttle_key :throttle_requests_count
@doc """ @doc """
@ -73,7 +73,9 @@ defmodule EthereumJSONRPC.RequestCoordinator do
@spec perform(Transport.batch_request(), Transport.t(), Transport.options(), non_neg_integer()) :: @spec perform(Transport.batch_request(), Transport.t(), Transport.options(), non_neg_integer()) ::
{:ok, Transport.batch_response()} | {:error, term()} {:ok, Transport.batch_response()} | {:error, term()}
def perform(request, transport, transport_options, throttle_timeout) do def perform(request, transport, transport_options, throttle_timeout) do
sleep_time = sleep_time() request_method = request_method(request)
sleep_time = sleep_time(request_method)
if sleep_time <= throttle_timeout do if sleep_time <= throttle_timeout do
:timer.sleep(sleep_time) :timer.sleep(sleep_time)
@ -85,7 +87,7 @@ defmodule EthereumJSONRPC.RequestCoordinator do
trace_request(request, fn -> trace_request(request, fn ->
request request
|> transport.json_rpc(transport_options) |> transport.json_rpc(transport_options)
|> handle_transport_response() |> handle_transport_response(request_method)
end) end)
:error -> :error ->
@ -110,19 +112,24 @@ defmodule EthereumJSONRPC.RequestCoordinator do
defp trace_request(_, fun), do: fun.() defp trace_request(_, fun), do: fun.()
defp handle_transport_response({:error, {error_type, _}} = error) when error_type in [:bad_gateway, :bad_response] do defp request_method([request | _]), do: request_method(request)
RollingWindow.inc(table(), @error_key) defp request_method(%{method: method}), do: method
defp request_method(_), do: nil
defp handle_transport_response({:error, {error_type, _}} = error, method)
when error_type in [:bad_gateway, :bad_response] do
RollingWindow.inc(table(), method_error_key(method))
inc_throttle_table() inc_throttle_table()
error error
end end
defp handle_transport_response({:error, :timeout} = error) do defp handle_transport_response({:error, :timeout} = error, method) do
RollingWindow.inc(table(), @error_key) RollingWindow.inc(table(), method_error_key(method))
inc_throttle_table() inc_throttle_table()
error error
end end
defp handle_transport_response(response) do defp handle_transport_response(response, _method) do
inc_throttle_table() inc_throttle_table()
response response
end end
@ -154,14 +161,16 @@ defmodule EthereumJSONRPC.RequestCoordinator do
end end
end end
defp sleep_time do defp sleep_time(request_method) do
wait_coefficient = RollingWindow.count(table(), @error_key) wait_coefficient = RollingWindow.count(table(), method_error_key(request_method))
jitter = :rand.uniform(config!(:max_jitter)) jitter = :rand.uniform(config!(:max_jitter))
wait_per_timeout = config!(:wait_per_timeout) wait_per_timeout = config!(:wait_per_timeout)
wait_coefficient * (wait_per_timeout + jitter) wait_coefficient * (wait_per_timeout + jitter)
end end
defp method_error_key(method), do: :"#{@error_key_base}_#{method}"
defp table do defp table do
:rolling_window_opts :rolling_window_opts
|> config!() |> config!()

@ -26,28 +26,35 @@ defmodule EthereumJSONRPC.RequestCoordinatorTest do
describe "perform/4" do describe "perform/4" do
test "forwards result whenever a request doesn't timeout", %{timeout_table: timeout_table} do test "forwards result whenever a request doesn't timeout", %{timeout_table: timeout_table} do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _, _ -> {:ok, %{}} end) expect(EthereumJSONRPC.Mox, :json_rpc, fn _, _ -> {:ok, %{}} end)
assert RollingWindow.count(timeout_table, :throttleable_error_count) == 0 assert RollingWindow.count(timeout_table, :throttleable_error_count_eth_call) == 0
assert {:ok, %{}} == RequestCoordinator.perform(%{}, EthereumJSONRPC.Mox, [], :timer.minutes(60))
assert RollingWindow.count(timeout_table, :throttleable_error_count) == 0 assert {:ok, %{}} ==
RequestCoordinator.perform(%{method: "eth_call"}, EthereumJSONRPC.Mox, [], :timer.minutes(60))
assert RollingWindow.count(timeout_table, :throttleable_error_count_eth_call) == 0
end end
test "increments counter on certain errors", %{timeout_table: timeout_table} do test "increments counter on certain errors", %{timeout_table: timeout_table} do
expect(EthereumJSONRPC.Mox, :json_rpc, fn :timeout, _ -> {:error, :timeout} end) expect(EthereumJSONRPC.Mox, :json_rpc, fn %{method: "timeout"}, _ -> {:error, :timeout} end)
expect(EthereumJSONRPC.Mox, :json_rpc, fn :bad_gateway, _ -> {:error, {:bad_gateway, "message"}} end) expect(EthereumJSONRPC.Mox, :json_rpc, fn %{method: "bad_gateway"}, _ -> {:error, {:bad_gateway, "message"}} end)
assert {:error, :timeout} ==
RequestCoordinator.perform(%{method: "timeout"}, EthereumJSONRPC.Mox, [], :timer.minutes(60))
assert {:error, :timeout} == RequestCoordinator.perform(:timeout, EthereumJSONRPC.Mox, [], :timer.minutes(60)) assert RollingWindow.count(timeout_table, :throttleable_error_count_timeout) == 1
assert RollingWindow.count(timeout_table, :throttleable_error_count) == 1 assert RollingWindow.count(timeout_table, :throttleable_error_count_bad_gateway) == 0
assert {:error, {:bad_gateway, "message"}} == assert {:error, {:bad_gateway, "message"}} ==
RequestCoordinator.perform(:bad_gateway, EthereumJSONRPC.Mox, [], :timer.minutes(60)) RequestCoordinator.perform(%{method: "bad_gateway"}, EthereumJSONRPC.Mox, [], :timer.minutes(60))
assert RollingWindow.count(timeout_table, :throttleable_error_count) == 2 assert RollingWindow.count(timeout_table, :throttleable_error_count_timeout) == 1
assert RollingWindow.count(timeout_table, :throttleable_error_count_bad_gateway) == 1
end end
test "returns timeout error if sleep time will exceed max timeout", %{timeout_table: timeout_table} do test "returns timeout error if sleep time will exceed max timeout", %{timeout_table: timeout_table} do
expect(EthereumJSONRPC.Mox, :json_rpc, 0, fn _, _ -> :ok end) expect(EthereumJSONRPC.Mox, :json_rpc, 0, fn _, _ -> :ok end)
RollingWindow.inc(timeout_table, :throttleable_error_count) RollingWindow.inc(timeout_table, :throttleable_error_count_eth_call)
assert {:error, :timeout} == RequestCoordinator.perform(%{}, EthereumJSONRPC.Mox, [], 1) assert {:error, :timeout} == RequestCoordinator.perform(%{method: "eth_call"}, EthereumJSONRPC.Mox, [], 1)
end end
test "increments throttle_table even when not an error", %{throttle_table: throttle_table} do test "increments throttle_table even when not an error", %{throttle_table: throttle_table} do

Loading…
Cancel
Save