Current section

Files

Jump to
klife lib klife.ex
Raw

lib/klife.ex

defmodule Klife do
@moduledoc """
Main functions to interact with clients.
Usually you will not need to call any function here directly
but instead use them through a module that uses `Klife.Client`.
"""
alias Klife.Record
alias Klife.Producer
alias Klife.TxnProducerPool
alias Klife.Producer.Controller, as: PController
@produce_opts [
producer: [
type: :atom,
required: false,
doc:
"Producer's name that will override the `default_producer` configuration. Ignored inside transactions."
],
partitioner: [
type: :atom,
required: false,
doc: "Module that will override `default_partitioner` configuration."
]
]
@async_opts [
callback: [
type: :any,
required: false,
doc:
"MFA or function/1 that will be called with the produce result. The result is injected as the first argument on MFA and is the only argument for anonymous functions"
]
]
@txn_opts [
pool_name: [
type: :atom,
required: false,
doc: "Txn pool's name that will override the `default_txn_pool` configuration."
]
]
@doc false
def get_produce_opts(), do: @produce_opts
@doc false
def get_txn_opts(), do: @txn_opts
@doc false
def get_async_opts(), do: @async_opts
@doc false
def produce(%Record{} = record, client, opts \\ []) do
[resp] = produce_batch([record], client, opts)
resp
end
@doc false
def produce_batch([%Record{} | _] = records, client, opts \\ []) do
records =
records
|> Enum.with_index(1)
|> Enum.map(fn {rec, idx} ->
rec
|> Map.replace!(:__estimated_size, Record.estimate_size(rec))
|> Map.replace!(:__batch_index, idx)
|> maybe_add_partition(client, opts)
end)
if TxnProducerPool.in_txn?(client),
do: TxnProducerPool.produce(records, client, opts),
else: Producer.produce(records, client, opts)
end
# The async implementation is non optimal because it may copy a lot of
# data to the new task process. Ideally we could solve this by making
# Dispatcher start the callback task instead of send message to the
# waiting pid but it would be hard to keep the same API this way
# because inside Dispatcher we do not have the same data and since
# we want the async callback to receive the exact same output
# as the sync counter part this is the easiest for now.
@doc false
def produce_async(%Record{} = record, client, opts \\ []) do
{:ok, _task_pid} =
Task.start(fn ->
resp = produce(record, client, opts)
case opts[:callback] do
{m, f, args} -> apply(m, f, [resp | args])
fun when is_function(fun, 1) -> fun.(resp)
_ -> :noop
end
end)
:ok
end
@doc false
def produce_batch_async([%Record{} | _] = records, client, opts \\ []) do
{:ok, _task_pid} =
Task.start(fn ->
resp = produce_batch(records, client, opts)
case opts[:callback] do
{m, f, args} -> apply(m, f, [resp | args])
fun when is_function(fun, 1) -> fun.(resp)
_ -> :noop
end
end)
:ok
end
@doc false
def produce_batch_txn([%Record{} | _] = records, client, opts \\ []) do
transaction(
fn -> records |> produce_batch(client, opts) |> Record.verify_batch() end,
client,
opts
)
end
@doc false
def transaction(fun, client, opts \\ []) do
TxnProducerPool.run_txn(client, get_txn_pool(client, opts), fun)
end
defp get_txn_pool(client, opts) do
Keyword.get(opts, :pool_name) || client.get_default_txn_pool()
end
defp maybe_add_partition(%Record{} = record, client, opts) do
case record do
%Record{partition: nil, topic: topic} ->
%{
default_partitioner: default_partitioner_mod,
max_partition: max_partition
} = PController.get_partitioner_data(client, topic)
partitioner_mod = Keyword.get(opts, :partitioner, default_partitioner_mod)
%{record | partition: partitioner_mod.get_partition(record, max_partition)}
record ->
record
end
end
end