Current section
Files
Jump to
Current section
Files
c_src/erlkaf_producer.cc
#include "erlkaf_producer.h"
#include "nif_utils.h"
#include "macros.h"
#include "topicmanager.h"
#include "erlkaf_nif.h"
#include "erlkaf_config.h"
#include "erlkaf_logger.h"
#include "erlkaf_oauthbearer.h"
#include "rdkafka.h"
#include "queuecallbacksdispatcher.h"
#include "erlkaf_message.h"
#include <string.h>
#include <memory>
#include <string>
#include <future>
namespace {
struct enif_producer
{
rd_kafka_t* kf;
TopicManager* topics;
ErlNifPid owner_pid;
std::future<bool>* closed_future;
};
bool cleanup_producer(enif_producer* producer, bool stop_feedback)
{
rd_kafka_flush(producer->kf, 30000);
if(stop_feedback)
{
ErlNifEnv* env = enif_alloc_env();
enif_send(NULL, &producer->owner_pid, env, ATOMS.atomClientStopped);
enif_free_env(env);
}
return true;
}
void delivery_report_callback (rd_kafka_t* rk, const rd_kafka_message_t* msg, void* data)
{
UNUSED(rk);
enif_producer* producer = static_cast<enif_producer*>(data);
ErlNifEnv* env = enif_alloc_env();
ERL_NIF_TERM status = msg->err == 0 ? ATOMS.atomOk : make_error(env, rd_kafka_err2str(msg->err));
enif_send(NULL, &producer->owner_pid, env, enif_make_tuple3(env, ATOMS.atomDeliveryReport, status, make_message_nif(env, msg)));
enif_free_env(env);
}
int stats_callback(rd_kafka_t *rk, char *json, size_t json_len, void *opaque)
{
UNUSED(rk);
enif_producer* producer = static_cast<enif_producer*>(opaque);
if(producer->owner_pid.pid == 0)
return 0;
ErlNifEnv* env = enif_alloc_env();
ERL_NIF_TERM stats = make_binary(env, json, json_len);
enif_send(NULL, &producer->owner_pid, env, enif_make_tuple2(env, ATOMS.atomStats, stats));
enif_free_env(env);
return 0;
}
void oauthbearer_token_refresh_callback(rd_kafka_t *rk, const char *oauthbearer_config, void *opaque)
{
UNUSED(rk);
enif_producer* producer = static_cast<enif_producer*>(opaque);
ErlNifEnv* env = enif_alloc_env();
if (oauthbearer_config == NULL)
{
enif_send(NULL, &producer->owner_pid, env, enif_make_tuple2(env, ATOMS.atomOauthbearerTokenRefresh, ATOMS.atomUndefined));
}
else
{
ERL_NIF_TERM config = make_binary(env, oauthbearer_config, strlen(oauthbearer_config));
enif_send(NULL, &producer->owner_pid, env, enif_make_tuple2(env, ATOMS.atomOauthbearerTokenRefresh, config));
}
enif_free_env(env);
}
bool populate_headers(ErlNifEnv* env, ERL_NIF_TERM headers_term, rd_kafka_headers_t* out)
{
ERL_NIF_TERM head;
const ERL_NIF_TERM *items;
int arity;
ErlNifBinary key;
ErlNifBinary value;
while(enif_get_list_cell(env, headers_term, &head, &headers_term))
{
if(!enif_get_tuple(env, head, &arity, &items) || arity != 2)
return false;
if (!get_binary(env, items[0], &key) || !get_binary(env, items[1], &value))
return false;
if(rd_kafka_header_add(out, reinterpret_cast<const char*>(key.data), key.size, value.data, value.size) != RD_KAFKA_RESP_ERR_NO_ERROR)
return false;
}
return true;
}
} // namespace
void enif_producer_free(ErlNifEnv* env, void* obj)
{
UNUSED(env);
enif_producer* producer = static_cast<enif_producer*>(obj);
if(producer->closed_future)
{
producer->closed_future->get();
delete producer->closed_future;
}
else if(producer->kf)
{
erlkaf_data* data = static_cast<erlkaf_data*>(enif_priv_data(env));
data->notifier_->remove(producer->kf);
cleanup_producer(producer, false);
}
if(producer->topics)
delete producer->topics;
if(producer->kf)
rd_kafka_destroy(producer->kf);
}
ERL_NIF_TERM enif_producer_topic_new(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
UNUSED(argc);
std::string topic_name;
enif_producer* producer;
erlkaf_data* data = static_cast<erlkaf_data*>(enif_priv_data(env));
if(!enif_get_resource(env, argv[0], data->res_producer, reinterpret_cast<void**>(&producer)))
return make_badarg(env);
if(!get_string(env, argv[1], &topic_name))
return make_badarg(env);
scoped_ptr(config, rd_kafka_topic_conf_t, rd_kafka_topic_conf_new(), rd_kafka_topic_conf_destroy);
ERL_NIF_TERM parse_result = parse_topic_config(env, argv[2], config.get());
if(parse_result != ATOMS.atomOk)
return parse_result;
bool already_exist;
if(!producer->topics->AddTopic(topic_name, config.get(), &already_exist))
return make_error(env, already_exist ? "topic already exist" : "failed to create topic");
config.release();
return ATOMS.atomOk;
}
ERL_NIF_TERM enif_producer_new(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
UNUSED(argc);
bool has_dr_callback;
if(!get_boolean(argv[0], &has_dr_callback))
return make_badarg(env);
scoped_ptr(config, rd_kafka_conf_t, rd_kafka_conf_new(), rd_kafka_conf_destroy);
ERL_NIF_TERM parse_result = parse_kafka_config(env, argv[1], config.get(), TargetObject::Producer);
if(parse_result != ATOMS.atomOk)
return parse_result;
rd_kafka_conf_set_log_cb(config.get(), logger_callback);
rd_kafka_conf_set_stats_cb(config.get(), stats_callback);
rd_kafka_conf_set_oauthbearer_token_refresh_cb(config.get(), oauthbearer_token_refresh_callback);
rd_kafka_conf_enable_sasl_queue(config.get(), 1);
if(has_dr_callback)
rd_kafka_conf_set_dr_msg_cb(config.get(), delivery_report_callback);
erlkaf_data* data = static_cast<erlkaf_data*>(enif_priv_data(env));
scoped_ptr(producer, enif_producer, static_cast<enif_producer*>(enif_alloc_resource(data->res_producer, sizeof(enif_producer))), enif_release_resource);
if(!producer.get())
return make_error(env, "failed to alloc producer");
memset(producer.get(), 0, sizeof(enif_producer));
rd_kafka_conf_set_opaque(config.get(), producer.get());
char errstr[512];
scoped_ptr(rk, rd_kafka_t, rd_kafka_new(RD_KAFKA_PRODUCER, config.get(), errstr, sizeof(errstr)), rd_kafka_destroy);
if (!rk)
return make_error(env, errstr);
config.release();
producer->topics = new TopicManager(rk.get());
producer->kf = rk.release();
producer->closed_future = nullptr;
data->notifier_->watch(producer->kf, false);
ERL_NIF_TERM term = enif_make_resource(env, producer.get());
enif_release_resource(producer.get());
producer.release();
return enif_make_tuple2(env, ATOMS.atomOk, term);
}
ERL_NIF_TERM enif_producer_set_owner(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
UNUSED(argc);
erlkaf_data* data = static_cast<erlkaf_data*>(enif_priv_data(env));
enif_producer* producer;
if(!enif_get_resource(env, argv[0], data->res_producer, reinterpret_cast<void**>(&producer)))
return make_badarg(env);
if(!enif_get_local_pid(env, argv[1], &producer->owner_pid))
return make_badarg(env);
rd_kafka_sasl_background_callbacks_enable(producer->kf);
return ATOMS.atomOk;
}
ERL_NIF_TERM enif_producer_cleanup(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
UNUSED(argc);
erlkaf_data* data = static_cast<erlkaf_data*>(enif_priv_data(env));
enif_producer* producer;
if(!enif_get_resource(env, argv[0], data->res_producer, reinterpret_cast<void**>(&producer)))
return make_badarg(env);
data->notifier_->remove(producer->kf);
producer->closed_future = new std::future<bool>(std::async(std::launch::async, cleanup_producer, producer, true));
return ATOMS.atomOk;
}
ERL_NIF_TERM enif_get_metadata(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
UNUSED(argc);
erlkaf_data* data = static_cast<erlkaf_data*>(enif_priv_data(env));
enif_producer* producer;
if(!enif_get_resource(env, argv[0], data->res_producer, reinterpret_cast<void**>(&producer)))
return make_badarg(env);
const struct rd_kafka_metadata *metadata;
rd_kafka_resp_err_t err;
/* Fetch metadata */
err = rd_kafka_metadata(producer->kf, 1, NULL, &metadata, 5000);
if (err != RD_KAFKA_RESP_ERR_NO_ERROR) {
return make_error(env, "failed to get metadata");
}
ERL_NIF_TERM metadata_map = enif_make_new_map(env);
ERL_NIF_TERM broker_arr[metadata->broker_cnt];
for (int i = 0 ; i < metadata->broker_cnt ; i++) {
ERL_NIF_TERM broker_map = enif_make_new_map(env);
ERL_NIF_TERM id_term = enif_make_int(env, metadata->brokers[i].id);
ERL_NIF_TERM host_term = make_binary(env, metadata->brokers[i].host, strlen(metadata->brokers[i].host));
ERL_NIF_TERM port_term = enif_make_int64(env, metadata->brokers[i].port);
enif_make_map_put(env, broker_map, ATOMS.atomId, id_term, &broker_map);
enif_make_map_put(env, broker_map, ATOMS.atomHost, host_term, &broker_map);
enif_make_map_put(env, broker_map, ATOMS.atomPort, port_term, &broker_map);
broker_arr[i] = broker_map;
}
ERL_NIF_TERM broker_list = enif_make_list_from_array(env, broker_arr, metadata->broker_cnt);
enif_make_map_put(env, metadata_map, ATOMS.atomBrokers, broker_list, &metadata_map);
ERL_NIF_TERM topic_arr[metadata->topic_cnt];
for (int i = 0 ; i < metadata->topic_cnt ; i++) {
const struct rd_kafka_metadata_topic *t = &metadata->topics[i];
if (t->err) {
if (t->err == RD_KAFKA_RESP_ERR_LEADER_NOT_AVAILABLE)
return make_error(env, "failed to get topic info");
}
ERL_NIF_TERM topic_map = enif_make_new_map(env);
ERL_NIF_TERM topic_name_term = make_binary(env, t->topic, strlen(t->topic));
enif_make_map_put(env, topic_map, ATOMS.atomName, topic_name_term, &topic_map);
ERL_NIF_TERM partition_arr[t->partition_cnt];
for (int j = 0 ; j < t->partition_cnt ; j++) {
const struct rd_kafka_metadata_partition *p;
p = &t->partitions[j];
if (p->err)
return make_error(env, "failed to get partition info");
ERL_NIF_TERM partition_map = enif_make_new_map(env);
ERL_NIF_TERM id_term = enif_make_int(env, p->id);
ERL_NIF_TERM leader_term = enif_make_int(env, p->leader);
ERL_NIF_TERM replica_arr[p->replica_cnt];
for (int k = 0 ; k < p->replica_cnt ; k++) {
ERL_NIF_TERM replica_term = enif_make_int(env, p->replicas[k]);
replica_arr[k] = replica_term;
}
ERL_NIF_TERM replica_list = enif_make_list_from_array(env, replica_arr, p->replica_cnt);
ERL_NIF_TERM isr_arr[p->isr_cnt];
for (int k = 0 ; k < p->isr_cnt ; k++) {
ERL_NIF_TERM isr_term = enif_make_int(env, p->isrs[k]);
isr_arr[k] = isr_term;
}
ERL_NIF_TERM isr_list = enif_make_list_from_array(env, isr_arr, p->isr_cnt);
enif_make_map_put(env, partition_map, ATOMS.atomId, id_term, &partition_map);
enif_make_map_put(env, partition_map, ATOMS.atomLeader, leader_term, &partition_map);
enif_make_map_put(env, partition_map, ATOMS.atomReplicas, replica_list, &partition_map);
enif_make_map_put(env, partition_map, ATOMS.atomIsrs, isr_list, &partition_map);
partition_arr[j] = partition_map;
}
ERL_NIF_TERM partition_list = enif_make_list_from_array(env, partition_arr, t->partition_cnt);
enif_make_map_put(env, topic_map, ATOMS.atomPartitions, partition_list, &topic_map);
topic_arr[i] = topic_map;
}
ERL_NIF_TERM topic_list = enif_make_list_from_array(env, topic_arr, metadata->topic_cnt);
enif_make_map_put(env, metadata_map, ATOMS.atomTopics, topic_list, &metadata_map);
rd_kafka_metadata_destroy(metadata);
return enif_make_tuple2(env, ATOMS.atomOk, metadata_map);
}
ERL_NIF_TERM enif_produce(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
UNUSED(argc);
erlkaf_data* data = static_cast<erlkaf_data*>(enif_priv_data(env));
enif_producer* producer;
std::string topic_name;
int32_t partition;
ErlNifBinary key;
ErlNifBinary value;
long timestamp;
scoped_ptr(headers, rd_kafka_headers_t, NULL, rd_kafka_headers_destroy);
if(!enif_get_resource(env, argv[0], data->res_producer, reinterpret_cast<void**>(&producer)))
return make_badarg(env);
if(!get_string(env, argv[1], &topic_name))
return make_badarg(env);
if(!enif_get_int(env, argv[2], &partition))
return make_badarg(env);
if (!get_binary(env, argv[3], &key))
{
if(!enif_is_identical(ATOMS.atomUndefined, argv[3]))
return make_badarg(env);
memset(&key, 0, sizeof(ErlNifBinary));
}
if (!get_binary(env, argv[4], &value))
{
if(!enif_is_identical(ATOMS.atomUndefined, argv[4]))
return make_badarg(env);
memset(&value, 0, sizeof(ErlNifBinary));
}
if(!enif_is_identical(argv[5], ATOMS.atomUndefined))
{
uint32_t length;
if(!enif_get_list_length(env, argv[5], &length))
return make_badarg(env);
if(length > 0)
{
headers.reset(rd_kafka_headers_new(length));
if(!populate_headers(env, argv[5], headers.get()))
return make_badarg(env);
}
}
if(!enif_get_int64(env, argv[6], ×tamp))
return make_badarg(env);
if(!headers.get() && timestamp == 0)
{
rd_kafka_topic_t* topic = producer->topics->GetOrCreateTopic(topic_name);
if(topic == NULL)
return make_error(env, "failed to create topic object");
if (rd_kafka_produce(topic, partition, RD_KAFKA_MSG_F_COPY, value.data, value.size, key.data, key.size, NULL) != 0)
return make_error(env, enif_make_int(env, rd_kafka_last_error()));
}
else
{
rd_kafka_resp_err_t result = rd_kafka_producev(producer->kf,
RD_KAFKA_V_TOPIC(topic_name.c_str()),
RD_KAFKA_V_PARTITION(partition),
RD_KAFKA_V_MSGFLAGS(RD_KAFKA_MSG_F_COPY),
RD_KAFKA_V_VALUE(value.data, value.size),
RD_KAFKA_V_KEY(key.data, key.size),
RD_KAFKA_V_HEADERS(headers.get()),
RD_KAFKA_V_TIMESTAMP(timestamp),
RD_KAFKA_V_END);
if(result != RD_KAFKA_RESP_ERR_NO_ERROR)
return make_error(env, enif_make_int(env, result));
else
headers.release();
}
return ATOMS.atomOk;
}
ERL_NIF_TERM enif_producer_oauthbearer_set_token(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
UNUSED(argc);
std::string token;
long lifetime;
std::string principal;
std::string extensions_str;
erlkaf_data* data = static_cast<erlkaf_data*>(enif_priv_data(env));
enif_producer* producer;
if(!enif_get_resource(env, argv[0], data->res_producer, reinterpret_cast<void**>(&producer)))
return make_badarg(env);
if(!get_string(env, argv[1], &token))
return make_badarg(env);
if(!enif_get_long(env, argv[2], &lifetime))
return make_badarg(env);
if(!get_string(env, argv[3], &principal))
return make_badarg(env);
if(!get_string(env, argv[4], &extensions_str))
return make_badarg(env);
return oauthbearer_set_token(producer->kf, token, lifetime, principal, extensions_str);
}
ERL_NIF_TERM enif_producer_oauthbearer_set_token_failure(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
UNUSED(argc);
std::string error_str;
erlkaf_data* data = static_cast<erlkaf_data*>(enif_priv_data(env));
enif_producer* producer;
if(!enif_get_resource(env, argv[0], data->res_producer, reinterpret_cast<void**>(&producer)))
return make_badarg(env);
if(!get_string(env, argv[1], &error_str))
return make_badarg(env);
return oauthbearer_set_token_failure(producer->kf, error_str);
}