Repository: elixir-grpc/grpc
Pull request #375 (2024-07-01 22:04:36)

[#184] gracefully handles server process shutdown
### Fixes https://github.com/elixir-grpc/grpc/issues/184

Add pattern match to handle server process shutdown. It's very common to see these error happen when we manually setup gRPC server in our test cases. ExUnit gracefully shutdown the applications (for example), thus causing the server processes to exit with `:shutdown` reason.

Without this handler, a lot of error logs are produced, causing to misleading logs.




Patch:
diff --git a/lib/grpc/server/adapters/cowboy/handler.ex b/lib/grpc/server/adapters/cowboy/handler.ex
index 836e9f5..1fad43a 100644
--- a/lib/grpc/server/adapters/cowboy/handler.ex
+++ b/lib/grpc/server/adapters/cowboy/handler.ex
@@ -449,8 +449,8 @@ defmodule GRPC.Server.Adapters.Cowboy.Handler do
     end
   end
 
-  def info({:EXIT, pid, :normal}, req, state = %{pid: pid}) do
-    exit_handler(pid, :normal)
+  def info({:EXIT, server_rpc_pid, reason}, req, state = %{pid: server_rpc_pid})
+      when reason in [:normal, :shutdown] do
     {:stop, req, state}
   end
 


Test patch:
diff --git a/test/grpc/integration/server_test.exs b/test/grpc/integration/server_test.exs
index 7a6ae8c..e4e344d 100644
--- a/test/grpc/integration/server_test.exs
+++ b/test/grpc/integration/server_test.exs
@@ -7,6 +7,12 @@ defmodule GRPC.Integration.ServerTest do
     def get_feature(point, _stream) do
       %Routeguide.Feature{location: point, name: "#{point.latitude},#{point.longitude}"}
     end
+
+    def route_chat(_ex_stream, stream) do
+      GRPC.Server.send_headers(stream, %{})
+      Process.exit(self(), :shutdown)
+      Process.sleep(500)
+    end
   end
 
   defmodule TranscodeErrorServer do
@@ -320,6 +326,21 @@ defmodule GRPC.Integration.ServerTest do
     end)
   end
 
+  test "gracefully handles server shutdown disconnects" do
+    logs =
+      ExUnit.CaptureLog.capture_log(fn ->
+        run_server(FeatureServer, fn port ->
+          {:ok, channel} = GRPC.Stub.connect("localhost:#{port}")
+          client_stream = Routeguide.RouteGuide.Stub.route_chat(channel)
+          assert %GRPC.Client.Stream{} = client_stream
+          {:ok, ex_stream} = GRPC.Stub.recv(client_stream, timeout: :infinity)
+          assert [{:error, %GRPC.RPCError{status: 13}}] = Enum.into(ex_stream, [])
+        end)
+      end)
+
+    assert logs == ""
+  end
+
   describe "http/json transcode" do
     test "grpc method can be called using json when http_transcode == true" do
       run_server([TranscodeServer], fn port ->
diff --git a/test/support/google/api/annotations.pb.ex b/test/support/google/api/annotations.pb.ex
deleted file mode 100644
index 374877d..0000000
--- a/test/support/google/api/annotations.pb.ex
+++ /dev/null
@@ -1,8 +0,0 @@
-defmodule Google.Api.PbExtension do
-  @moduledoc false
-  use Protobuf, protoc_gen_elixir_version: "0.11.0", syntax: :proto3
-
-  extend Google.Protobuf.MethodOptions, :http, 72_295_728,
-    optional: true,
-    type: Google.Api.HttpRule
-end
diff --git a/test/support/google/api/http.pb.ex b/test/support/google/api/http.pb.ex
deleted file mode 100644
index ebd043a..0000000
--- a/test/support/google/api/http.pb.ex
+++ /dev/null
@@ -1,40 +0,0 @@
-defmodule Google.Api.Http do
-  @moduledoc false
-  use Protobuf, protoc_gen_elixir_version: "0.11.0", syntax: :proto3
-
-  field :rules, 1, repeated: true, type: Google.Api.HttpRule
-
-  field :fully_decode_reserved_expansion, 2,
-    type: :bool,
-    json_name: "fullyDecodeReservedExpansion"
-end
-
-defmodule Google.Api.HttpRule do
-  @moduledoc false
-  use Protobuf, protoc_gen_elixir_version: "0.11.0", syntax: :proto3
-
-  oneof :pattern, 0
-
-  field :selector, 1, type: :string
-  field :get, 2, type: :string, oneof: 0
-  field :put, 3, type: :string, oneof: 0
-  field :post, 4, type: :string, oneof: 0
-  field :delete, 5, type: :string, oneof: 0
-  field :patch, 6, type: :string, oneof: 0
-  field :custom, 8, type: Google.Api.CustomHttpPattern, oneof: 0
-  field :body, 7, type: :string
-  field :response_body, 12, type: :string, json_name: "responseBody"
-
-  field :additional_bindings, 11,
-    repeated: true,
-    type: Google.Api.HttpRule,
-    json_name: "additionalBindings"
-end
-
-defmodule Google.Api.CustomHttpPattern do
-  @moduledoc false
-  use Protobuf, protoc_gen_elixir_version: "0.11.0", syntax: :proto3
-
-  field :kind, 1, type: :string
-  field :path, 2, type: :string
-end