Repository: elixir-grpc/grpc
Pull request #378 (2024-07-07 20:14:17)

Allow client settings opt on mint adapter
Hey Team, I've been actively working on this: https://github.com/elixir-grpc/grpc/issues/307 but so far no good results.

Tho, I've noticed that with a few tweaks in the configs, it's possible to improve the Mint adapter performance by 2x (which still slow if compared with gun, but it's a quick win)

This PR aims to accept a new option when starting the connection where it's possible to pass [Mint's client settings options](https://hexdocs.pm/mint/Mint.HTTP2.html#t:setting/0) and also setting a bigger window size as default (I got these values from [Gun](https://github.com/ninenines/gun/blob/master/src/gun_http2.erl#L187) )

I also have a branch where , with the supposer of benchee I can compare the improvement on speed 

https://github.com/beligante/grpc/pull/4


Patch:
diff --git a/lib/grpc/client/adapters/mint.ex b/lib/grpc/client/adapters/mint.ex
index e361e0d..d82ad2d 100644
--- a/lib/grpc/client/adapters/mint.ex
+++ b/lib/grpc/client/adapters/mint.ex
@@ -1,6 +1,6 @@
 defmodule GRPC.Client.Adapters.Mint do
   @moduledoc """
-  A client adapter using mint
+  A client adapter using Mint.
   """
 
   alias GRPC.Channel
@@ -10,9 +10,25 @@ defmodule GRPC.Client.Adapters.Mint do
 
   @behaviour GRPC.Client.Adapter
 
-  @default_connect_opts [protocols: [:http2]]
+  @default_connect_opts [
+    protocols: [:http2]
+  ]
+  @default_client_settings [
+    initial_window_size: 8_000_000,
+    max_frame_size: 8_000_000
+  ]
   @default_transport_opts [timeout: :infinity]
 
+  @doc """
+  Connects using Mint based on the provided configs. Options
+    * `:transport_opts`: Defaults to `[timeout: :infinity]`, given the nature of H2 connections (with support to
+      long-lived streams) this default is set to avoid timeouts while waiting for server streams to complete. The other
+      options may vary based on the transport used for this connection (tcp or ssl). Check [Mint.HTTP.connect/4](https://hexdocs.pm/mint/Mint.HTTP.html#connect/4)
+    * `:client_settings`: Defaults to `[initial_window_size: 8_000_000, max_frame_size: 8_000_000]`, a larger default
+      window size ensures that the number of packages exchanges is smaller, thus speeding up the requests by reducing the
+      amount of networks round trip, with the cost of having larger packages reaching the server per connection.
+      Check [Mint.HTTP2.setting() type](https://hexdocs.pm/mint/Mint.HTTP2.html#t:setting/0) for additional configs.
+  """
   @impl true
   def connect(%{host: host, port: port} = channel, opts \\ []) do
     # Added :config_options to facilitate testing.
@@ -119,12 +135,22 @@ defmodule GRPC.Client.Adapters.Mint do
       |> Keyword.get(:transport_opts, [])
       |> Keyword.merge(ssl)
 
-    [transport_opts: Keyword.merge(@default_transport_opts, transport_opts)]
+    client_settings = Keyword.get(opts, :client_settings, @default_client_settings)
+
+    [
+      transport_opts: Keyword.merge(@default_transport_opts, transport_opts),
+      client_settings: client_settings
+    ]
   end
 
   defp connect_opts(_channel, opts) do
     transport_opts = Keyword.get(opts, :transport_opts, [])
-    [transport_opts: Keyword.merge(@default_transport_opts, transport_opts)]
+    client_settings = Keyword.get(opts, :client_settings, @default_client_settings)
+
+    [
+      transport_opts: Keyword.merge(@default_transport_opts, transport_opts),
+      client_settings: client_settings
+    ]
   end
 
   defp merge_opts(opts, module_opts) do


Test patch:
diff --git a/test/grpc/client/adapters/mint_test.exs b/test/grpc/client/adapters/mint_test.exs
index b3f8d12..261235f 100644
--- a/test/grpc/client/adapters/mint_test.exs
+++ b/test/grpc/client/adapters/mint_test.exs
@@ -47,5 +47,36 @@ defmodule GRPC.Client.Adapters.MintTest do
 
       assert message == "Error while opening connection: {:error, :badarg}"
     end
+
+    test "defaults client settings when none is passed", %{port: port} do
+      channel = build(:channel, adapter: Mint, port: port, host: "localhost")
+
+      assert {:ok, result} = Mint.connect(channel, [])
+      # wait for settings to be pushed
+      Process.sleep(50)
+      state = :sys.get_state(result.adapter_payload.conn_pid)
+
+      assert %{initial_window_size: 8_000_000, max_frame_size: 8_000_000} =
+               Map.get(state.conn, :client_settings)
+    end
+
+    test "allow client settings to be passed", %{port: port} do
+      channel = build(:channel, adapter: Mint, port: port, host: "localhost")
+
+      assert {:ok, result} =
+               Mint.connect(channel,
+                 client_settings: [
+                   initial_window_size: 50_000,
+                   max_frame_size: 50_000
+                 ]
+               )
+
+      # wait for settings to be pushed
+      Process.sleep(50)
+      state = :sys.get_state(result.adapter_payload.conn_pid)
+
+      assert %{initial_window_size: 50_000, max_frame_size: 50_000} =
+               Map.get(state.conn, :client_settings)
+    end
   end
 end