-
Notifications
You must be signed in to change notification settings - Fork 183
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add elixir client request pool behaviour (#2138)
Allows for a persistent connection per client rather than the default request coalescing behaviour. Also expand replication stream messages with the timestamp of the actual HTTP request and the handle of the shape. Basically enhancements for the load generation client to enable stats collection and improve the resume/reconnect behaviour.
- Loading branch information
1 parent
3e53a45
commit 71b8ab2
Showing
10 changed files
with
209 additions
and
94 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@core/elixir-client": patch | ||
--- | ||
|
||
Add pool behaviour for the Elixir client to allow for per-client persistent connections. Add request timestamp and shape handle to replication stream messages. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,18 @@ | ||
defmodule Electric.Client.Fetch do | ||
alias Electric.Client.Fetch.{Request, Response} | ||
alias Electric.Client | ||
|
||
@callback fetch(Request.t(), Keyword.t()) :: | ||
{:ok, Response.t()} | ||
| {:error, Response.t() | term()} | ||
|
||
@behaviour Electric.Client.Fetch.Pool | ||
|
||
def request(client, request, opts \\ []) | ||
|
||
@impl Electric.Client.Fetch.Pool | ||
def request(%Client{} = client, %Request{} = request, _opts) do | ||
%{pool: {module, opts}} = client | ||
apply(module, :request, [client, request, opts]) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
defmodule Electric.Client.Fetch.Pool do | ||
@moduledoc """ | ||
Coaleses requests so that multiple client instances making the same | ||
(potentially long-polling) request will all use the same request process. | ||
""" | ||
|
||
alias Electric.Client | ||
alias Electric.Client.Fetch | ||
|
||
require Logger | ||
|
||
@callback request(Client.t(), Fetch.Request.t(), opts :: Keyword.t()) :: | ||
Fetch.Response.t() | {:error, Fetch.Response.t() | term()} | ||
|
||
@behaviour __MODULE__ | ||
|
||
@impl Electric.Client.Fetch.Pool | ||
def request(%Client{} = client, %Fetch.Request{} = request, opts) do | ||
request_id = request_id(client, request) | ||
|
||
# register this pid before making the request to avoid race conditions for | ||
# very fast responses | ||
{:ok, monitor_pid} = start_monitor(request_id) | ||
|
||
try do | ||
ref = Fetch.Monitor.register(monitor_pid, self()) | ||
|
||
{:ok, _request_pid} = start_request(request_id, request, client, monitor_pid) | ||
|
||
Fetch.Monitor.wait(ref) | ||
catch | ||
:exit, {reason, _} -> | ||
Logger.debug(fn -> | ||
"Request process ended with reason #{inspect(reason)} before we could register. Re-attempting." | ||
end) | ||
|
||
request(client, request, opts) | ||
end | ||
end | ||
|
||
defp start_request(request_id, request, client, monitor_pid) do | ||
DynamicSupervisor.start_child( | ||
Electric.Client.RequestSupervisor, | ||
{Fetch.Request, {request_id, request, client, monitor_pid}} | ||
) | ||
|> return_existing() | ||
end | ||
|
||
defp start_monitor(request_id) do | ||
DynamicSupervisor.start_child( | ||
Electric.Client.RequestSupervisor, | ||
{Electric.Client.Fetch.Monitor, request_id} | ||
) | ||
|> return_existing() | ||
end | ||
|
||
defp return_existing({:ok, pid}), do: {:ok, pid} | ||
defp return_existing({:error, {:already_started, pid}}), do: {:ok, pid} | ||
defp return_existing(error), do: error | ||
|
||
defp request_id(%Client{fetch: {fetch_impl, _}}, %Fetch.Request{shape_handle: nil} = request) do | ||
%{endpoint: endpoint, shape: shape_definition} = request | ||
{fetch_impl, URI.to_string(endpoint), shape_definition} | ||
end | ||
|
||
defp request_id(%Client{fetch: {fetch_impl, _}}, %Fetch.Request{} = request) do | ||
%{endpoint: endpoint, offset: offset, live: live, shape_handle: shape_handle} = request | ||
{fetch_impl, URI.to_string(endpoint), shape_handle, Client.Offset.to_tuple(offset), live} | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.