rdkafka-hs-1.1.0.0: test-integration/cbits/callback_lifetime.c
/* Integration executable only: observed constructor/destructor stages. The
* log barrier is reached by an actual native DESTROY log callback. Every
* ordinary operation, including shutdown, still reaches librdkafka. */
#define _GNU_SOURCE
#include <dlfcn.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <librdkafka/rdkafka.h>
typedef void (*log_callback)(const rd_kafka_t *, int, const char *, const char *);
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t resume = PTHREAD_COND_INITIALIZER;
static int enabled, mode, released, counters[10];
static rd_kafka_t *client;
static rd_kafka_queue_t *message_queue;
static rd_kafka_conf_t *duplicate;
static log_callback user_log;
extern void rdkafka_hs_state_client_created(rd_kafka_type_t, rd_kafka_t *);
extern void rdkafka_hs_producer_client_created(rd_kafka_type_t, rd_kafka_t *);
#define REAL(name) __typeof__(&name) real = (__typeof__(&name))dlsym(RTLD_NEXT, #name); \
if (!real) { fprintf(stderr, "callback lifetime: %s\n", dlerror()); abort(); }
/* Called with the lock held. Native/Haskell cancellation cannot release this
* barrier; the controller must observe it and release it explicitly. */
static void boundary(void) {
counters[0]++;
while (!released) pthread_cond_wait(&resume, &lock);
}
void rdkafka_hs_test_callbacks_begin(int value) {
pthread_mutex_lock(&lock);
enabled = 1; mode = value; released = 0;
memset(counters, 0, sizeof counters);
client = NULL; message_queue = NULL; duplicate = NULL; user_log = NULL;
pthread_mutex_unlock(&lock);
}
void rdkafka_hs_test_callbacks_release(void) {
pthread_mutex_lock(&lock);
released = 1;
pthread_cond_broadcast(&resume);
pthread_mutex_unlock(&lock);
}
void rdkafka_hs_test_callbacks_end(void) {
pthread_mutex_lock(&lock);
enabled = 0; released = 1;
/* Do not keep dead native addresses reachable for heap diagnostics. */
client = NULL; message_queue = NULL; duplicate = NULL; user_log = NULL;
pthread_cond_broadcast(&resume);
pthread_mutex_unlock(&lock);
}
int rdkafka_hs_test_callbacks_stat(int index) {
int value;
pthread_mutex_lock(&lock);
value = index >= 0 && index < 10 ? counters[index] : -1;
pthread_mutex_unlock(&lock);
return value;
}
/* Explicit diagnostic negative control, never called by ordinary tests. An
* instrumented native library must diagnose the read in conf_get. */
int rdkafka_hs_test_callbacks_invalid_access(void) {
rd_kafka_conf_t *conf = rd_kafka_conf_new();
rd_kafka_conf_destroy(conf);
size_t size = 0;
return (int)rd_kafka_conf_get(conf, "client.id", NULL, &size);
}
static void forward_log(const rd_kafka_t *rk, int level, const char *facility, const char *message) {
pthread_mutex_lock(&lock);
log_callback callback = user_log;
int shutting_down = enabled && rk == client && counters[2] > counters[3];
if (enabled) counters[9]++;
pthread_mutex_unlock(&lock);
if (callback) callback(rk, level, facility, message);
pthread_mutex_lock(&lock);
if (shutting_down && strcmp(facility, "DESTROY") == 0) {
counters[4]++;
if (mode == 6 && counters[0] == 0) boundary();
}
pthread_mutex_unlock(&lock);
}
void rd_kafka_conf_set_log_cb(rd_kafka_conf_t *conf, log_callback callback) {
REAL(rd_kafka_conf_set_log_cb);
pthread_mutex_lock(&lock);
int tracking = enabled;
if (tracking) user_log = callback;
pthread_mutex_unlock(&lock);
real(conf, tracking ? forward_log : callback);
}
void rd_kafka_conf_set_stats_cb(rd_kafka_conf_t *conf,
int (*callback)(rd_kafka_t *, char *, size_t, void *)) {
REAL(rd_kafka_conf_set_stats_cb);
real(conf, callback);
pthread_mutex_lock(&lock);
if (enabled) {
counters[6]++;
if (mode == 1) boundary();
}
pthread_mutex_unlock(&lock);
}
rd_kafka_conf_t *rd_kafka_conf_dup(const rd_kafka_conf_t *conf) {
REAL(rd_kafka_conf_dup);
rd_kafka_conf_t *result = real(conf);
pthread_mutex_lock(&lock);
if (enabled) { duplicate = result; counters[7]++; }
pthread_mutex_unlock(&lock);
return result;
}
void rd_kafka_conf_destroy(rd_kafka_conf_t *conf) {
REAL(rd_kafka_conf_destroy);
pthread_mutex_lock(&lock);
if (enabled && conf == duplicate) { duplicate = NULL; counters[5]++; }
pthread_mutex_unlock(&lock);
real(conf);
}
rd_kafka_t *rd_kafka_new(rd_kafka_type_t type, rd_kafka_conf_t *conf, char *error, size_t capacity) {
REAL(rd_kafka_new);
rd_kafka_t *result = real(type, conf, error, capacity);
rdkafka_hs_state_client_created(type, result);
rdkafka_hs_producer_client_created(type, result);
pthread_mutex_lock(&lock);
if (enabled && result) {
if (client) abort(); /* Each fixture owns one client. */
client = result;
duplicate = NULL; /* Consumed by successful native creation. */
counters[1]++;
if (mode == 2) boundary();
}
pthread_mutex_unlock(&lock);
return result;
}
void rdkafka_hs_callbacks_queue_created(rd_kafka_t *rk, rd_kafka_queue_t *queue) {
pthread_mutex_lock(&lock);
if (enabled && rk == client && !message_queue) message_queue = queue;
pthread_mutex_unlock(&lock);
}
void rdkafka_hs_callbacks_queue_destroying(rd_kafka_queue_t *queue) {
pthread_mutex_lock(&lock);
if (enabled && queue == message_queue) {
counters[8]++;
if (mode == 4) boundary();
message_queue = NULL;
}
pthread_mutex_unlock(&lock);
}
void rd_kafka_destroy_flags(rd_kafka_t *rk, int flags) {
REAL(rd_kafka_destroy_flags);
pthread_mutex_lock(&lock);
int tracking = enabled && rk == client;
if (tracking) {
counters[2]++;
const char *probe = getenv("RDKAFKA_TEST_CALLBACK_SAFETY_PROBE");
if (probe) {
fprintf(stderr, "callback safety probe: native destructor entered (%s)\n", probe);
fflush(stderr);
}
if (mode == 3) boundary();
}
pthread_mutex_unlock(&lock);
real(rk, flags);
pthread_mutex_lock(&lock);
if (tracking) {
counters[3]++;
if (mode == 5) boundary();
client = NULL;
}
pthread_mutex_unlock(&lock);
}