-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improved monitoring of leased connections #15
Changes from all commits
528af16
abc5d29
7568d79
f60d508
0253482
94733e4
72d82b8
0a54299
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,36 +4,47 @@ defmodule Feeb.DB.Repo do | |
# This can be removed once the usage of `DB.prepared_raw/3` is consolidated | ||
@dialyzer {:nowarn_function, format_custom: 3} | ||
|
||
@struct_keys [:manager_pid, :context, :shard_id, :mode, :path, :conn, :transaction_id] | ||
@enforce_keys List.delete(@struct_keys, [:transaction_id]) | ||
defstruct @struct_keys | ||
|
||
use GenServer | ||
require Logger | ||
alias Feeb.DB.{Config, Migrator, Query, Schema, SQLite} | ||
alias __MODULE__.RepoConfig | ||
|
||
@env Mix.env() | ||
@is_test_mode Application.compile_env(:feebdb, :is_test_mode, false) | ||
|
||
def get_path(context, shard_id), | ||
do: "#{Config.data_dir()}/#{context}/#{shard_id}.db" | ||
|
||
# Callbacks | ||
|
||
# Client API | ||
|
||
def start_link({_, _, _, _} = args) do | ||
GenServer.start_link(__MODULE__, args) | ||
end | ||
|
||
def get_path(context, shard_id), | ||
do: "#{Config.data_dir()}/#{context}/#{shard_id}.db" | ||
def start_link({_, _, _, _, _} = args), | ||
do: GenServer.start_link(__MODULE__, args) | ||
|
||
def close(pid), | ||
do: GenServer.call(pid, {:close}) | ||
|
||
@doc """ | ||
Used by the Repo.Manager to notify once the Repo has been released. Useful to resetting internal | ||
counters, transaction_id etc. | ||
""" | ||
def notify_release(pid), | ||
do: GenServer.call(pid, {:mgt_connection_released}) | ||
|
||
# Server API | ||
|
||
def init({context, shard_id, path, mode}) do | ||
def init({context, shard_id, path, mode, manager_pid}) do | ||
Logger.info("Starting #{mode} repo for shard #{shard_id}@#{context}") | ||
true = mode in [:readwrite, :readonly] | ||
|
||
case SQLite.open(path) do | ||
{:ok, conn} -> | ||
state = %{ | ||
state = %__MODULE__{ | ||
manager_pid: manager_pid, | ||
context: context, | ||
shard_id: shard_id, | ||
mode: mode, | ||
|
@@ -43,7 +54,7 @@ defmodule Feeb.DB.Repo do | |
} | ||
|
||
# `repo_config` is metadata that the Schema has access to when building virtual fields | ||
repo_config = Map.drop(state, [:transaction_id, :conn]) | ||
repo_config = RepoConfig.from_state(state) | ||
Process.put(:repo_config, repo_config) | ||
|
||
{:ok, state, {:continue, :bootstrap}} | ||
|
@@ -133,6 +144,19 @@ defmodule Feeb.DB.Repo do | |
end | ||
end | ||
|
||
def handle_call({:mgt_connection_released}, {caller_pid, _}, state) do | ||
# Make sure only the Repo.Manager can send mgt signals to the Repo. | ||
assert_release_signal_from_manager!(state.manager_pid, caller_pid) | ||
|
||
if not is_nil(state.transaction_id) do | ||
Logger.info("Connection released forcibly; rolling back transaction #{state.transaction_id}") | ||
:ok = SQLite.exec(state.conn, "ROLLBACK") | ||
end | ||
|
||
# Reset the GenServer state so we are ready to serve a new request | ||
{:reply, :ok, %{state | transaction_id: nil}} | ||
end | ||
Comment on lines
+147
to
+158
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle potential race conditions when rolling back transactions on release In Consider synchronizing the access to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That's not true. If we are handling the If it processes a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @renatomassaro, thank you for the clarification. You're correct that the GenServer processes messages sequentially, so there's no race condition in this scenario. I appreciate the explanation, and I'll keep this in mind for future reviews. ✏️ Learnings added
|
||
|
||
def handle_call({:close}, _from, %{transaction_id: nil} = state) do | ||
Logger.info("Closing conn from repo #{inspect(self())}") | ||
:ok = SQLite.close(state.conn) | ||
|
@@ -373,4 +397,15 @@ defmodule Feeb.DB.Repo do | |
other_value | ||
end) | ||
end | ||
|
||
defp assert_release_signal_from_manager!(manager_pid, manager_pid), do: :ok | ||
|
||
if @env == :test do | ||
defp assert_release_signal_from_manager!(nil, _caller_pid), do: :ok | ||
end | ||
|
||
defp assert_release_signal_from_manager!(manager_pid, other_pid) do | ||
"Repo can only be released by its Manager (#{inspect(manager_pid)}, got #{inspect(other_pid)})" | ||
|> raise() | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Clarify parameter names in
assert_release_signal_from_manager!/2
The function
assert_release_signal_from_manager!/2
uses the same name for both parameters in pattern matching, which can be confusing and potentially error-prone.Refactor the function to use distinct parameter names:
📝 Committable suggestion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be confusing to someone who is new to Erlang/Elixir; otherwise this is considered idiomatic pattern matching.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@renatomassaro, understood. Thank you for the clarification. I'll keep in mind that using the same parameter name in pattern matching is considered idiomatic in Erlang/Elixir.
✏️ Learnings added