Skip to content

Commit

Permalink
Merge pull request #93 from seriyps/code-change
Browse files Browse the repository at this point in the history
Add `code_change` implementation and `appup` and hot upgrade test
  • Loading branch information
seriyps authored Apr 21, 2023
2 parents 6c0ee31 + c1c7f3a commit 07095d4
Show file tree
Hide file tree
Showing 13 changed files with 289 additions and 20 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/hot_upgrade.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Hot upgrade
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
ci:
name: Hot code upgrade from ${{ matrix.from_version }} on OTP-${{ matrix.otp }}
runs-on: ${{matrix.os}}

strategy:
fail-fast: true
matrix:
os: ["ubuntu-20.04"]
otp: ["23.3"]
rebar3: ["3.17.0"]
from_version:
- "1.5.2"
- "9c28fb479f9329e2a1644565a632bc222780f1b7"

steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

- uses: erlef/setup-beam@v1
with:
otp-version: ${{ matrix.otp }}
rebar3-version: ${{ matrix.rebar3 }}

- name: Hot-upgrade
run: make hotupgrade_setup BASE_REV=${{ matrix.from_version }} hotupgrade_check
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ clean: $(REBAR)
dialyzer: $(REBAR)
$(REBAR) dialyzer

hotupgrade_setup: $(REBAR)
./test/hotupgrade_test.sh setup $(BASE_REV)

hotupgrade_check:
./test/hotupgrade_test.sh check

# Get rebar3 if it doesn't exist. If rebar3 was found on PATH, the
# $(REBAR) dep will be satisfied since the file will exist.

Expand Down
14 changes: 14 additions & 0 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,20 @@ Please attach the output of ~rebar3 bench --baseline master~ after your changes
in order to prove that there were no performance regressions. Please attach the OTP version you run the
benchmarks on.

*** New release

Our goal is to allow the hot code upgrade of ~pooler~, so it is shipped with ~.appup~ file and hot upgrade
procedure is tested in CI.

To cut a new release, do the following steps:

1. In ~src/pooler.app.src~: update the ~vsn~
2. In ~src/pooler.appup.src~: replace the contents with upgrade instructions for a new release
3. In ~test/relx-base.config~: update the ~pooler~'s app version to a previous release (or leave it without version)
4. In ~test/relx-current.config~: update the ~pooler~'s app version to a new one
5. In ~.github/workflows/hot_upgrade.yml~: update ~from_version~ to a previous release, maybe bump OTP version as well
6. Push, wait for the green build, tag

** License
Pooler is licensed under the Apache License Version 2.0. See the
[[file:LICENSE][LICENSE]] file for details.
Expand Down
8 changes: 5 additions & 3 deletions src/pooler.app.src
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
{application, pooler, [
{description, "An OTP Process Pool Application"},
{vsn, git},
{registered, []},
{vsn, "1.6.0"},
{registered, [pooler_sup, pooler_starter_sup]},
{applications, [
kernel,
stdlib
]},
{mod, {pooler_app, []}},
{env, []}
{env, []},
{licenses, ["Apache License 2.0"]},
{links, [{"Github", "https://github.com/epgsql/pooler"}]}
]}.
24 changes: 24 additions & 0 deletions src/pooler.appup.src
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
% -*- mode: erlang -*-
{"1.6.0",
[{<<"1\\.5\\.2.*">>,
[{update, pooler, {advanced, []}},
{load_module, pooler_starter},
{load_module, pooler_app},
{update, pooler_sup, supervisor},
{update, pooler_pool_sup, supervisor},
{update, pooler_starter_sup, supervisor},
{update, pooler_pooled_worker_sup, supervisor},
{delete_module, pooler_config}]
},
{<<"1\\.5\\.3.*">>,
[{update, pooler, {advanced, []}},
{load_module, pooler_starter},
{load_module, pooler_app},
{update, pooler_sup, supervisor},
{update, pooler_pool_sup, supervisor},
{update, pooler_starter_sup, supervisor},
{update, pooler_pooled_worker_sup, supervisor},
{delete_module, pooler_config}]
}
],
[{<<".*">>, []}]}.
41 changes: 39 additions & 2 deletions src/pooler.erl
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
call_free_members/2,
call_free_members/3
]).
-export([create_group_table/0, config_as_map/1]).
-export([create_group_table/0, config_as_map/1, to_map/1]).

%% ------------------------------------------------------------------
%% gen_server Function Exports
Expand All @@ -59,6 +59,9 @@
code_change/3
]).

-vsn(2).
%% Bump this value and add a new clause to `code_change', if the format of `#pool{}' record changed

%% ------------------------------------------------------------------
%% Types
%% ------------------------------------------------------------------
Expand Down Expand Up @@ -606,7 +609,40 @@ terminate(_Reason, _State) ->
ok.

-spec code_change(_, _, _) -> {'ok', _}.
code_change(_OldVsn, State, _Extra) ->
code_change(
_OldVsn,
{pool, Name, Group, MaxCount, InitCount, StartMFA, FreePids, InUseCount, FreeCount, AddMemberRetry, CullInterval,
MaxAge, MemberSup, StarterSup, AllMembers, ConsumerToPid, StartingMembers, MemberStartTimeout,
AutoGrowThreshold, StopMFA, MetricsMod, MetricsAPI, QueuedRequestors, QueueMax},
_Extra
) ->
{ok, #pool{
cull_timer = undefined,
all_members = maps:from_list(dict:to_list(AllMembers)),
consumer_to_pid = maps:from_list(dict:to_list(ConsumerToPid)),
name = Name,
group = Group,
max_count = MaxCount,
init_count = InitCount,
start_mfa = StartMFA,
free_pids = FreePids,
in_use_count = InUseCount,
free_count = FreeCount,
add_member_retry = AddMemberRetry,
cull_interval = CullInterval,
max_age = MaxAge,
member_sup = MemberSup,
starter_sup = StarterSup,
starting_members = StartingMembers,
member_start_timeout = MemberStartTimeout,
auto_grow_threshold = AutoGrowThreshold,
stop_mfa = StopMFA,
metrics_mod = MetricsMod,
metrics_api = MetricsAPI,
queued_requestors = QueuedRequestors,
queue_max = QueueMax
}};
code_change(_, State, _Extra) ->
{ok, State}.

%% ------------------------------------------------------------------
Expand Down Expand Up @@ -1311,6 +1347,7 @@ do_call_free_member(Fun, Pid) ->
{error, Reason}
end.

%% @private
to_map(#pool{} = Pool) ->
[Name | Values] = tuple_to_list(Pool),
maps:from_list(
Expand Down
54 changes: 47 additions & 7 deletions src/pooler_pool_sup.erl
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,57 @@ start_link(PoolConf) ->
SupName = pool_sup_name(PoolConf),
supervisor:start_link({local, SupName}, ?MODULE, PoolConf).

init(PoolConf) ->
PoolerSpec = {pooler, {pooler, start_link, [PoolConf]}, transient, 5000, worker, [pooler]},
init(PoolConf) when is_map(PoolConf) ->
PoolerSpec = #{
id => pooler,
start => {pooler, start_link, [PoolConf]},
restart => transient,
shutdown => 5000,
type => worker,
modules => [pooler]
},
MemberSupName = member_sup_name(PoolConf),
MemberSupSpec =
{MemberSupName, {pooler_pooled_worker_sup, start_link, [PoolConf]}, transient, 5000, supervisor, [
pooler_pooled_worker_sup
]},
#{
id => MemberSupName,
start => {pooler_pooled_worker_sup, start_link, [PoolConf]},
restart => transient,
shutdown => 5000,
type => supervisor,
modules => [pooler_pooled_worker_sup]
},

%% five restarts in 60 seconds, then shutdown
Restart = {one_for_all, 5, 60},
{ok, {Restart, [MemberSupSpec, PoolerSpec]}}.
Restart = #{strategy => one_for_all, intensity => 5, period => 60},
{ok, {Restart, [MemberSupSpec, PoolerSpec]}};
init(PoolRecord) when is_tuple(PoolRecord), element(1, PoolRecord) =:= pool ->
%% This clause is for the hot code upgrade from pre-1.6.0;
%% can be removed when "upgrade-from-version" below 1.6.0 are removed from `pooler.appup.src'
{ok, PoolRecord1} = pooler:code_change(0, PoolRecord, []),
AsMap = pooler:to_map(PoolRecord1),
init(
maps:with(
[
name,
init_count,
max_count,
start_mfa,
group,
cull_interval,
max_age,
member_start_timeout,
queue_max,
metrics_api,
metrics_mod,
stop_mfa,
auto_grow_threshold,
add_member_retry,
metrics_mod,
metrics_api
],
AsMap
)
).

-spec member_sup_name(pooler:pool_config()) -> atom().
member_sup_name(#{name := Name}) ->
Expand Down
11 changes: 9 additions & 2 deletions src/pooler_pooled_worker_sup.erl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ start_link(#{start_mfa := MFA} = PoolConf) ->
supervisor:start_link({local, SupName}, ?MODULE, MFA).

init({Mod, Fun, Args}) ->
Worker = {Mod, {Mod, Fun, Args}, temporary, brutal_kill, worker, [Mod]},
Worker = #{
id => Mod,
start => {Mod, Fun, Args},
restart => temporary,
shutdown => brutal_kill,
type => worker,
modules => [Mod]
},
Specs = [Worker],
Restart = {simple_one_for_one, 1, 1},
Restart = #{strategy => simple_one_for_one, intensity => 1, period => 1},
{ok, {Restart, Specs}}.
11 changes: 9 additions & 2 deletions src/pooler_starter_sup.erl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,14 @@ start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).

init([]) ->
Worker = {pooler_starter, {pooler_starter, start_link, []}, temporary, brutal_kill, worker, [pooler_starter]},
Worker = #{
id => pooler_starter,
start => {pooler_starter, start_link, []},
restart => temporary,
shutdown => brutal_kill,
type => worker,
modules => [pooler_starter]
},
Specs = [Worker],
Restart = {simple_one_for_one, 1, 1},
Restart = #{strategy => simple_one_for_one, intensity => 1, period => 1},
{ok, {Restart, Specs}}.
36 changes: 32 additions & 4 deletions src/pooler_sup.erl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
start_link/0
]).

-include_lib("kernel/include/logger.hrl").

start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).

Expand All @@ -30,8 +32,20 @@ init([]) ->
})
|| Config <- Configs
],
pooler:create_group_table(),
{ok, {{one_for_one, 5, 60}, [starter_sup_spec() | PoolSupSpecs]}}.
try
pooler:create_group_table()
catch
error:badarg:Stack ->
?LOG_ERROR(
#{
label => "Failed to start pool groups ETS table",
reason => badarg,
stack => Stack
},
#{domain => [pooler]}
)
end,
{ok, {#{strategy => one_for_one, intensity => 5, period => 60}, [starter_sup_spec() | PoolSupSpecs]}}.

%% @doc Create a new pool from proplist pool config `PoolConfig'. The
%% public API for this functionality is {@link pooler:new_pool/1}.
Expand Down Expand Up @@ -61,11 +75,25 @@ rm_pool(Name) ->
end.

starter_sup_spec() ->
{pooler_starter_sup, {pooler_starter_sup, start_link, []}, transient, 5000, supervisor, [pooler_starter_sup]}.
#{
id => pooler_starter_sup,
start => {pooler_starter_sup, start_link, []},
restart => transient,
shutdown => 5000,
type => supervisor,
modules => [pooler_starter_sup]
}.

pool_sup_spec(#{name := Name} = PoolConfig) ->
SupName = pool_sup_name(Name),
{SupName, {pooler_pool_sup, start_link, [PoolConfig]}, transient, 5000, supervisor, [pooler_pool_sup]}.
#{
id => SupName,
start => {pooler_pool_sup, start_link, [PoolConfig]},
restart => transient,
shutdown => 5000,
type => supervisor,
modules => [pooler_pool_sup]
}.

pool_sup_name(Name) ->
list_to_atom("pooler_" ++ atom_to_list(Name) ++ "_pool_sup").
Expand Down
Loading

0 comments on commit 07095d4

Please sign in to comment.