From 1e9992078d83d428453729228375841b8a4f0641 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Wed, 15 Jan 2025 14:35:38 +0100 Subject: [PATCH 1/7] chore: update to use `gun` 2.1.0 --- rebar.config | 2 +- src/client/grpc_client.erl | 49 +++++++++++--------------------------- test/grpc_SUITE.erl | 2 +- 3 files changed, 16 insertions(+), 37 deletions(-) diff --git a/rebar.config b/rebar.config index 5da4661..747b2f1 100644 --- a/rebar.config +++ b/rebar.config @@ -23,7 +23,7 @@ [ {gpb, "~> 4.11"} , {gproc, "0.8.0"} , {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.9.0"}}} - , {gun, {git, "https://github.com/emqx/gun", {tag, "1.3.7"}}} + , {gun, "2.1.0"} ]}. {xref_checks, diff --git a/src/client/grpc_client.erl b/src/client/grpc_client.erl index 7d44881..de78b70 100644 --- a/src/client/grpc_client.erl +++ b/src/client/grpc_client.erl @@ -59,7 +59,7 @@ %% XXX: Bad impl. encoding :: grpc_frame:encoding(), %% Streams - streams :: #{reference() := stream()}, + streams :: #{gun:stream_ref() => stream()}, %% Client options client_opts :: client_opts(), %% Flush timer reference @@ -118,7 +118,7 @@ #{protocols => [http2], connect_timeout => 5000, http2_opts => #{keepalive => 60000}, - transport_opts => [{nodelay, true}] + tcp_opts => [{nodelay, true}] }). -define(STREAM_RESERVED_TIMEOUT, 15000). @@ -387,7 +387,7 @@ handle_info({timeout, TRef, flush_streams_sendbuff}, handle_info({gun_up, GunPid, http2}, State = #state{gun_pid = GunPid}) -> {noreply, State}; -handle_info({gun_down, GunPid, http2, Reason, KilledStreamRefs, _}, +handle_info({gun_down, GunPid, http2, Reason, KilledStreamRefs}, State = #state{gun_pid = GunPid, streams = Streams}) -> Nowts = erlang:system_time(millisecond), %% Reply killed streams error @@ -633,45 +633,24 @@ do_connect(State = #state{server = {_, Host, Port}, client_opts = ClientOpts}) - GunOpts = maps:get(gun_opts, ClientOpts, #{}), case gun:open(Host, Port, GunOpts) of {ok, Pid} -> - case gun_await_up_helper(Pid) of + %% NOTE + %% By default, `gun` retries failed connection attempts 5 times, with + %% 5 seconds delay in-between. Give it a bit more spare time, just in + %% case. + MRef = monitor(process, Pid), + case gun:await_up(Pid, 60_000, MRef) of {ok, _Protocol} -> - MRef = monitor(process, Pid), State#state{mref = MRef, gun_pid = Pid}; - {error, Reason} -> - gun:close(Pid), - {error, Reason} + {error, {down, Reason}} -> + {error, Reason}; + {error, timeout} -> + _ = gun:close(Pid), + {error, timeout} end; {error, Reason} -> {error, Reason} end. -gun_await_up_helper(Pid) -> - gun_await_up_helper(Pid, 50, undefined). -gun_await_up_helper(_Pid, 0, LastRet) -> - LastRet ; -gun_await_up_helper(Pid, Retry, LastRet) -> - case gun:await_up(Pid, 100) of - {ok, _} = Ret -> - Ret; - {error, timeout} -> - case gun_last_reason(Pid) of - undefined -> - gun_await_up_helper(Pid, Retry-1, LastRet); - Reason -> - {error, Reason} - end; - {error, _} = Ret -> - Ret - end. - -gun_last_reason(Pid) -> - %% XXX: Hard-coded to get detailed reason, because gun - %% does not expose it with a function - lists:last( - tuple_to_list( - element(2, sys:get_state(Pid))) - ). - %%-------------------------------------------------------------------- %% Helpers diff --git a/test/grpc_SUITE.erl b/test/grpc_SUITE.erl index ed03caa..c131abb 100644 --- a/test/grpc_SUITE.erl +++ b/test/grpc_SUITE.erl @@ -61,7 +61,7 @@ init_per_group(GrpName, Cfg) -> https -> #{gun_opts => #{transport => ssl, - transport_opts => [{cacertfile, CA}]}}; + tls_opts => [{cacertfile, CA}]}}; _ -> #{} end, SvrAddr = case GrpName of From 537229b77846eda9c4081decfc2179a66e580741 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Wed, 15 Jan 2025 14:36:00 +0100 Subject: [PATCH 2/7] chore: export referenced type --- src/grpc_frame.erl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/grpc_frame.erl b/src/grpc_frame.erl index 2a4206b..4f87029 100644 --- a/src/grpc_frame.erl +++ b/src/grpc_frame.erl @@ -22,6 +22,8 @@ , split/2 ]). +-export_type([encoding/0]). + -type encoding() :: identity | gzip. -define(GRPC_ERROR(Status, Message), {grpc_error, {Status, Message}}). From fc74e08c116917ca3559e29ea1f9fcc592fd4f44 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Wed, 15 Jan 2025 14:36:20 +0100 Subject: [PATCH 3/7] chore: bump `grpc_plugin` to OTP-27-ready v0.10.3 --- rebar.config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rebar.config b/rebar.config index 747b2f1..f4779c5 100644 --- a/rebar.config +++ b/rebar.config @@ -3,7 +3,7 @@ {erl_opts, [debug_info]}. {plugins, - [ {grpc_plugin, {git, "https://github.com/HJianBo/grpc_plugin", {tag, "v0.10.2"}}} + [ {grpc_plugin, {git, "https://github.com/HJianBo/grpc_plugin", {tag, "v0.10.3"}}} ]}. {grpc, @@ -20,7 +20,7 @@ ]}. {deps, - [ {gpb, "~> 4.11"} + [ {gpb, "~> 4.21"} , {gproc, "0.8.0"} , {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.9.0"}}} , {gun, "2.1.0"} From 88c9927baba140d0f92009fbf0baccb7ea3354e5 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Wed, 15 Jan 2025 14:45:53 +0100 Subject: [PATCH 4/7] test: use now non-default `verify_none` under Erlang/OTP 27 --- test/grpc_SUITE.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/grpc_SUITE.erl b/test/grpc_SUITE.erl index c131abb..f593420 100644 --- a/test/grpc_SUITE.erl +++ b/test/grpc_SUITE.erl @@ -61,7 +61,7 @@ init_per_group(GrpName, Cfg) -> https -> #{gun_opts => #{transport => ssl, - tls_opts => [{cacertfile, CA}]}}; + tls_opts => [{cacertfile, CA}, {verify, verify_none}]}}; _ -> #{} end, SvrAddr = case GrpName of From 45f67d3792f4446d2a26613cda03ecc1818688f8 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Wed, 15 Jan 2025 14:56:27 +0100 Subject: [PATCH 5/7] ci: update workflows to use recent Erlang/OTP 27 images --- .github/workflows/run_test_case.yaml | 57 +++++++++++++++------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/.github/workflows/run_test_case.yaml b/.github/workflows/run_test_case.yaml index 4782894..ded9484 100644 --- a/.github/workflows/run_test_case.yaml +++ b/.github/workflows/run_test_case.yaml @@ -8,11 +8,11 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - uses: erlef/setup-beam@v1 with: - otp-version: '24' - rebar3-version: '3.16.1' + otp-version: '27' + rebar3-version: '3.23.0' - run: rebar3 xref - run: rebar3 dialyzer - run: rebar3 eunit @@ -23,46 +23,49 @@ jobs: strategy: matrix: + builder: + - ghcr.io/emqx/emqx-builder/5.4-3:1.17.3-27.2-1 os: + - ubuntu24.04 + - ubuntu22.04 - ubuntu20.04 - - ubuntu18.04 - - ubuntu16.04 + - debian12 + - debian11 - debian10 - - debian9 - - centos8 - - centos7 - - raspbian10 + - amzn2023 + - el9 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Code analyze - env: - ERL_OTP: erl24.3.4.2-1 - SYSTEM: ${{ matrix.os }} run: | version=$(echo ${{ github.ref }} | sed -r "s .*/.*/(.*) \1 g") - sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset - sudo docker run --rm -i --name $SYSTEM -v $(pwd):/repos emqx/build-env:$ERL_OTP-$SYSTEM sh -c "cd /repos && make xref && make dialyzer" + docker run --rm --privileged multiarch/qemu-user-static:register --reset + docker run --rm -i --name ${{ matrix.os }} -v $(pwd):/repos ${{ matrix.builder }}-${{ matrix.os }} \ + sh -c "cd /repos && make xref && make dialyzer" - name: Run tests - env: - ERL_OTP: erl24.3.4.2-1 - SYSTEM: ${{ matrix.os }} run: | version=$(echo ${{ github.ref }} | sed -r "s .*/.*/(.*) \1 g") - sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset - sudo docker run --rm -i --name $SYSTEM -v $(pwd):/repos emqx/build-env:$ERL_OTP-$SYSTEM sh -c "cd /repos && make eunit && make ct" + docker run --rm --privileged multiarch/qemu-user-static:register --reset + docker run --rm -i --name ${{ matrix.os }} -v $(pwd):/repos ${{ matrix.builder }}-${{ matrix.os }} \ + sh -c "cd /repos && make eunit && make ct" docker: runs-on: ubuntu-latest + strategy: + matrix: + builder: + - ghcr.io/emqx/emqx-builder/5.4-3:1.17.3-27.2-1 + steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Code analyze - env: - ERL_OTP: erl24.3.4.2-1 - run: docker run --rm -i --name alpine -v $(pwd):/repos emqx/build-env:$ERL_OTP-alpine sh -c "cd /repos && make xref && make dialyzer" + run: | + docker run --rm -i --name alpine -v $(pwd):/repos ${{ matrix.builder }}-alpine3.15.1 \ + sh -c "cd /repos && make xref && make dialyzer" - name: Run tests - env: - ERL_OTP: erl24.3.4.2-1 - run: docker run --rm -i --name alpine -v $(pwd):/repos emqx/build-env:$ERL_OTP-alpine sh -c "cd /repos && make eunit && make ct" + run: | + docker run --rm -i --name alpine -v $(pwd):/repos ${{ matrix.builder }}-alpine3.15.1 \ + sh -c "cd /repos && make eunit && make ct" From 8dabacaae9f598ac0684eba03737e87f8d55ea4c Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Wed, 15 Jan 2025 15:26:43 +0100 Subject: [PATCH 6/7] chore: fix typing issues --- rebar.config | 2 ++ src/client/grpc_client.erl | 3 +++ src/client/grpc_client_sup.erl | 2 +- src/grpc.app.src | 2 +- src/grpc.erl | 17 +++++++++++++---- src/grpc_lib.erl | 2 +- src/grpc_stream.erl | 7 ++++++- 7 files changed, 27 insertions(+), 8 deletions(-) diff --git a/rebar.config b/rebar.config index f4779c5..fcfeb23 100644 --- a/rebar.config +++ b/rebar.config @@ -34,6 +34,8 @@ {xref_ignores, [grpc_health_pb,grpc_reflection_pb, grpc_greeter_pb,grpc_route_guide_pb]}. +{dialyzer, [{plt_apps, all_deps}]}. + {profiles, [{test, [{cover_enabled, true}, diff --git a/src/client/grpc_client.erl b/src/client/grpc_client.erl index de78b70..22dc440 100644 --- a/src/client/grpc_client.erl +++ b/src/client/grpc_client.erl @@ -42,6 +42,9 @@ , code_change/3 ]). +-export_type([ options/0 + , grpcstream/0]). + -record(state, { %% Pool name pool, diff --git a/src/client/grpc_client_sup.erl b/src/client/grpc_client_sup.erl index 1f3077f..d89ed26 100644 --- a/src/client/grpc_client_sup.erl +++ b/src/client/grpc_client_sup.erl @@ -32,7 +32,7 @@ -type name() :: term(). --type options() :: grpc_client:client_opts() +-type options() :: grpc_client:options() | #{pool_size => non_neg_integer()}. %%-------------------------------------------------------------------- diff --git a/src/grpc.app.src b/src/grpc.app.src index 0f50d25..5faa942 100644 --- a/src/grpc.app.src +++ b/src/grpc.app.src @@ -4,7 +4,7 @@ {vsn, "git"}, {registered, []}, {mod, {grpc_app, []}}, - {applications, [kernel,stdlib,gproc,cowboy,gun]}, + {applications, [kernel,stdlib,ssl,public_key,gproc,cowboy,gun]}, {env,[]}, {modules, []}, {licenses, ["Apache 2.0"]}, diff --git a/src/grpc.erl b/src/grpc.erl index 5aac280..8f9bab7 100644 --- a/src/grpc.erl +++ b/src/grpc.erl @@ -29,16 +29,25 @@ -type listen_on() :: {inet:ip_address(), inet:port_number()} | inet:port_number(). -type services() :: #{protos := [module()], - services := #{ServiceName :: atom() := HandlerModule :: module()} + services := #{ServiceName :: atom() => HandlerModule :: module()} }. --type option() :: {ssl_options, ssl:server_option()} +-type option() :: {ssl_options, ssl:tls_server_option()} | {ranch_opts, ranch:opts()} | {cowboy_opts, cowboy_http2:opts()}. --type metadata() :: map(). +%% TODO: Figure out types. +-type metadata_key() :: term(). +-type metadata_value() :: term(). +-type metadata() :: #{metadata_key() => metadata_value()}. --export_type([metadata/0]). +%% NOTE: Expected by generated code. +-type options() :: grpc_client:options(). + +-export_type([ metadata/0 + , metadata_key/0 + , metadata_value/0 + , options/0]). %%-------------------------------------------------------------------- %% APIs diff --git a/src/grpc_lib.erl b/src/grpc_lib.erl index 12f4bee..aca18cc 100644 --- a/src/grpc_lib.erl +++ b/src/grpc_lib.erl @@ -98,7 +98,7 @@ decode(Base64) when byte_size(Base64) rem 4 == 2 -> decode(Base64) -> base64:decode(Base64). --spec maybe_encode_headers(grpc:meta_data()) -> grpc:meta_data(). +-spec maybe_encode_headers(grpc:metadata()) -> grpc:metadata(). %% @doc Encode the header values to Base64 for those headers that have the name %% ending with "-bin". maybe_encode_headers(Headers) -> diff --git a/src/grpc_stream.erl b/src/grpc_stream.erl index 71d7428..e5dfd0d 100644 --- a/src/grpc_stream.erl +++ b/src/grpc_stream.erl @@ -35,11 +35,13 @@ , handle_out/3 ]). +-export_type([stream/0, error_response/0]). + -type stream() :: #{ req := cowboy_req:req() , rest := binary() , metadata := map() , encoding := grpc_frame:encoding() - , compression := grpc_frame:compression() + , compression := grpc_frame:encoding() %% TODO: Figure out types , decoder := function() , encoder := function() , handler := {atom(), atom()} @@ -49,6 +51,9 @@ , client_info := map() }. +%% TODO: Figure out types. +-type error_response() :: term(). + %%-------------------------------------------------------------------- %% APIs %%-------------------------------------------------------------------- From 90c8f323f06b6292efb19bf71ffe4d72ad5056b0 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Wed, 15 Jan 2025 15:27:09 +0100 Subject: [PATCH 7/7] chore: bump `gproc` to 1.0.0 --- rebar.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebar.config b/rebar.config index fcfeb23..f57387f 100644 --- a/rebar.config +++ b/rebar.config @@ -21,7 +21,7 @@ {deps, [ {gpb, "~> 4.21"} - , {gproc, "0.8.0"} + , {gproc, "1.0.0"} , {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.9.0"}}} , {gun, "2.1.0"} ]}.