packages feed

rdkafka-hs-1.1.0.0: test-integration/cbits/admin_ownership.c

/* Test executable only: observe real public-library allocations and inject
 * failures at native ownership transitions. Production artifacts never link
 * these symbols. RTLD_NEXT dispatches each ordinary call to librdkafka. */
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <time.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <librdkafka/rdkafka.h>

#define REAL(name) \
  __typeof__(&name) real = (__typeof__(&name))dlsym(RTLD_NEXT, #name); \
  if (!real) { fprintf(stderr, "admin ownership: %s: %s\n", #name, dlerror()); abort(); }

enum { NEW_TOPIC, DELETE_TOPIC, QUEUE, OPTIONS, EVENT, KINDS };
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t resumed = PTHREAD_COND_INITIALIZER;
static struct { void *pointer; int kind; } live[4096];
static int allocations[KINDS], releases[KINDS], peaks[KINDS];
static char *last_key, *last_value;
static int enabled, fail_config, block_config, config_entered, submissions, config_calls, response_mode, timeout_fault, timeout_reads;

/* ELF may preempt calls made inside librdkafka itself. Count application-owned
 * references, not the library's internal copies and reference increments. */
static int application_call(void *caller) {
  Dl_info image;
  if (!dladdr(caller, &image) || !image.dli_fname) abort();
  const char *name = strrchr(image.dli_fname, '/');
  name = name ? name + 1 : image.dli_fname;
  return strncmp(name, "librdkafka.", 11) != 0;
}
#define APPLICATION_CALL application_call(__builtin_extract_return_addr(__builtin_return_address(0)))

static void acquired(void *pointer, int kind) {
  if (!pointer) return;
  pthread_mutex_lock(&lock);
  if (enabled) {
    size_t slot;
    for (slot = 0; slot < 4096 && live[slot].pointer; ++slot) {}
    if (slot == 4096) abort();
    live[slot].pointer = pointer;
    live[slot].kind = kind;
    ++allocations[kind];
    if (allocations[kind] - releases[kind] > peaks[kind]) peaks[kind] = allocations[kind] - releases[kind];
  }
  pthread_mutex_unlock(&lock);
}

static void released(void *pointer, int kind) {
  pthread_mutex_lock(&lock);
  for (size_t slot = 0; slot < 4096; ++slot) {
    if (live[slot].pointer == pointer && live[slot].kind == kind) {
      live[slot].pointer = NULL;
      ++releases[kind];
      break;
    }
  }
  pthread_mutex_unlock(&lock);
}

int rdkafka_hs_test_admin_begin(void) {
  int outstanding = 0;
  pthread_mutex_lock(&lock);
  for (int kind = 0; kind < KINDS; ++kind)
    outstanding += allocations[kind] - releases[kind];
  if (!outstanding) {
    memset(allocations, 0, sizeof allocations);
    memset(releases, 0, sizeof releases);
    memset(peaks, 0, sizeof peaks);
    free(last_key); free(last_value); last_key = last_value = NULL;
    enabled = 1;
    fail_config = block_config = config_entered = submissions = config_calls = response_mode = timeout_fault = timeout_reads = 0;
  }
  pthread_mutex_unlock(&lock);
  return outstanding;
}

void rdkafka_hs_test_admin_end(void) {
  pthread_mutex_lock(&lock);
  enabled = fail_config = block_config = response_mode = timeout_fault = 0;
  free(last_key); free(last_value); last_key = last_value = NULL;
  pthread_cond_broadcast(&resumed);
  pthread_mutex_unlock(&lock);
}

int rdkafka_hs_test_admin_count(int kind, int field) {
  int result;
  pthread_mutex_lock(&lock);
  result = field == 0 ? allocations[kind] : field == 1 ? releases[kind] : field == 3 ? peaks[kind] : allocations[kind] - releases[kind];
  pthread_mutex_unlock(&lock);
  return result;
}

void rdkafka_hs_test_admin_fail_config(int flag) {
  pthread_mutex_lock(&lock); fail_config = flag; pthread_mutex_unlock(&lock);
}
void rdkafka_hs_test_admin_block_config(int flag) {
  pthread_mutex_lock(&lock);
  block_config = flag;
  if (!flag) pthread_cond_broadcast(&resumed);
  pthread_mutex_unlock(&lock);
}
int rdkafka_hs_test_admin_config_entered(void) {
  int result;
  pthread_mutex_lock(&lock); result = config_entered; pthread_mutex_unlock(&lock);
  return result;
}

rd_kafka_NewTopic_t *rd_kafka_NewTopic_new(const char *name, int partitions, int replication, char *error, size_t size) {
  REAL(rd_kafka_NewTopic_new);
  rd_kafka_NewTopic_t *result = real(name, partitions, replication, error, size);
  if (APPLICATION_CALL) acquired(result, NEW_TOPIC);
  return result;
}
void rd_kafka_NewTopic_destroy(rd_kafka_NewTopic_t *topic) {
  REAL(rd_kafka_NewTopic_destroy);
  if (APPLICATION_CALL) released(topic, NEW_TOPIC); real(topic);
}
rd_kafka_resp_err_t rd_kafka_NewTopic_set_config(rd_kafka_NewTopic_t *topic, const char *name, const char *value) {
  REAL(rd_kafka_NewTopic_set_config);
  int reject;
  if (!APPLICATION_CALL) return real(topic, name, value);
  pthread_mutex_lock(&lock);
  if (!enabled) { pthread_mutex_unlock(&lock); return real(topic, name, value); }
  free(last_key); free(last_value);
  last_key = strdup(name ? name : "(null)");
  last_value = strdup(value ? value : "(null)");
  if (!last_key || !last_value) abort();
  config_entered = 1;
  while (block_config) pthread_cond_wait(&resumed, &lock);
  ++config_calls;
  reject = fail_config > 0 && config_calls >= fail_config;
  pthread_mutex_unlock(&lock);
  return reject ? RD_KAFKA_RESP_ERR__INVALID_ARG : real(topic, name, value);
}
rd_kafka_DeleteTopic_t *rd_kafka_DeleteTopic_new(const char *name) {
  REAL(rd_kafka_DeleteTopic_new);
  rd_kafka_DeleteTopic_t *result = real(name);
  if (APPLICATION_CALL) acquired(result, DELETE_TOPIC); return result;
}
void rd_kafka_DeleteTopic_destroy(rd_kafka_DeleteTopic_t *topic) {
  REAL(rd_kafka_DeleteTopic_destroy);
  if (APPLICATION_CALL) released(topic, DELETE_TOPIC); real(topic);
}
/* Share queue interception with callback lifetime fixtures, so each ordinary
 * native call still has exactly one interposer and one real dispatch. */
void rdkafka_hs_callbacks_queue_created(rd_kafka_t *, rd_kafka_queue_t *);
void rdkafka_hs_callbacks_queue_destroying(rd_kafka_queue_t *);

rd_kafka_queue_t *rd_kafka_queue_new(rd_kafka_t *client) {
  REAL(rd_kafka_queue_new);
  rd_kafka_queue_t *result = real(client);
  if (APPLICATION_CALL) {
    acquired(result, QUEUE);
    rdkafka_hs_callbacks_queue_created(client, result);
  } return result;
}
void rd_kafka_queue_destroy(rd_kafka_queue_t *queue) {
  REAL(rd_kafka_queue_destroy);
  if (APPLICATION_CALL) {
    released(queue, QUEUE);
    rdkafka_hs_callbacks_queue_destroying(queue);
  } real(queue);
}
rd_kafka_AdminOptions_t *rd_kafka_AdminOptions_new(rd_kafka_t *client, rd_kafka_admin_op_t operation) {
  REAL(rd_kafka_AdminOptions_new);
  rd_kafka_AdminOptions_t *result = real(client, operation);
  if (APPLICATION_CALL) acquired(result, OPTIONS); return result;
}
void rd_kafka_AdminOptions_destroy(rd_kafka_AdminOptions_t *options) {
  REAL(rd_kafka_AdminOptions_destroy);
  if (APPLICATION_CALL) released(options, OPTIONS); real(options);
}
rd_kafka_event_t *rd_kafka_queue_poll(rd_kafka_queue_t *queue, int timeout) {
  REAL(rd_kafka_queue_poll);
  int application = APPLICATION_CALL;
  int mode = 0;
  pthread_mutex_lock(&lock); if (application) mode = response_mode; pthread_mutex_unlock(&lock);
  if (mode == 5) {
    struct timespec delay = {0, 150000000};
    while (nanosleep(&delay, &delay) && errno == EINTR) {}
  }
  rd_kafka_event_t *result = real(queue, timeout);
  if (application) acquired(result, EVENT);
  if (mode == 4 && result) {
    rd_kafka_event_destroy(result);
    return NULL;
  }
  return result;
}
void rd_kafka_event_destroy(rd_kafka_event_t *event) {
  REAL(rd_kafka_event_destroy);
  if (APPLICATION_CALL) released(event, EVENT); real(event);
}

/* Deliberately retain one real native request to prove the counter sees a
 * leak. The control's caller must release it; it never submits to a broker. */
rd_kafka_NewTopic_t *rdkafka_hs_test_admin_positive_control(void) {
  char error[512];
  return rd_kafka_NewTopic_new("ownership-positive-control", 1, 1, error, sizeof error);
}
void rdkafka_hs_test_admin_release_control(rd_kafka_NewTopic_t *topic) {
  rd_kafka_NewTopic_destroy(topic);
}

int rdkafka_hs_test_admin_submissions(void) {
  int result;
  pthread_mutex_lock(&lock); result = submissions; pthread_mutex_unlock(&lock);
  return result;
}
void rd_kafka_CreateTopics(rd_kafka_t *client, rd_kafka_NewTopic_t **topics, size_t count, const rd_kafka_AdminOptions_t *options, rd_kafka_queue_t *queue) {
  REAL(rd_kafka_CreateTopics);
  pthread_mutex_lock(&lock); if (enabled && APPLICATION_CALL) ++submissions; pthread_mutex_unlock(&lock);
  real(client, topics, count, options, queue);
}

int rdkafka_hs_test_admin_reject_control(rd_kafka_NewTopic_t *topic) {
  return rd_kafka_NewTopic_set_config(topic, "cleanup.policy", "compact");
}

int rdkafka_hs_test_admin_configuration_calls(void) {
  int result;
  pthread_mutex_lock(&lock); result = config_calls; pthread_mutex_unlock(&lock);
  return result;
}
void rdkafka_hs_test_admin_response_mode(int mode) {
  pthread_mutex_lock(&lock); response_mode = mode; pthread_mutex_unlock(&lock);
}
static int current_response_mode(void) {
  int mode;
  pthread_mutex_lock(&lock); mode = response_mode; pthread_mutex_unlock(&lock);
  return mode;
}
const rd_kafka_CreateTopics_result_t *rd_kafka_event_CreateTopics_result(rd_kafka_event_t *event) {
  REAL(rd_kafka_event_CreateTopics_result);
  if (APPLICATION_CALL && current_response_mode() == 1) return NULL;
  return real(event);
}
const rd_kafka_topic_result_t **rd_kafka_CreateTopics_result_topics(const rd_kafka_CreateTopics_result_t *result, size_t *count) {
  REAL(rd_kafka_CreateTopics_result_topics);
  if (APPLICATION_CALL && current_response_mode() == 2) { *count = 0; return NULL; }
  return real(result, count);
}
const char *rd_kafka_topic_result_name(const rd_kafka_topic_result_t *result) {
  REAL(rd_kafka_topic_result_name);
  if (APPLICATION_CALL && current_response_mode() == 3) return "unrelated-result";
  return real(result);
}

const char *rdkafka_hs_test_admin_last_key(void) { return last_key ? last_key : ""; }
const char *rdkafka_hs_test_admin_last_value(void) { return last_value ? last_value : ""; }

const rd_kafka_DeleteTopics_result_t *rd_kafka_event_DeleteTopics_result(rd_kafka_event_t *event) {
  REAL(rd_kafka_event_DeleteTopics_result);
  if (APPLICATION_CALL && current_response_mode() == 1) return NULL;
  return real(event);
}
const rd_kafka_topic_result_t **rd_kafka_DeleteTopics_result_topics(const rd_kafka_DeleteTopics_result_t *result, size_t *count) {
  REAL(rd_kafka_DeleteTopics_result_topics);
  if (APPLICATION_CALL && current_response_mode() == 2) { *count = 0; return NULL; }
  return real(result, count);
}

void rdkafka_hs_test_admin_timeout_fault(int mode) {
  pthread_mutex_lock(&lock); timeout_fault = mode; pthread_mutex_unlock(&lock);
}
int rdkafka_hs_test_admin_timeout_reads(void) {
  int reads;
  pthread_mutex_lock(&lock); reads = timeout_reads; pthread_mutex_unlock(&lock);
  return reads;
}
rd_kafka_conf_res_t rd_kafka_conf_get(const rd_kafka_conf_t *conf, const char *name, char *destination, size_t *size) {
  REAL(rd_kafka_conf_get);
  int mode = 0;
  if (APPLICATION_CALL && strcmp(name, "socket.timeout.ms") == 0) {
    pthread_mutex_lock(&lock);
    if (enabled) { ++timeout_reads; mode = timeout_fault; }
    pthread_mutex_unlock(&lock);
  }
  if (mode == 1) return RD_KAFKA_CONF_UNKNOWN;
  if (mode == 6) {
    if (destination && *size) destination[0] = 0;
    *size = 100;
    return RD_KAFKA_CONF_OK;
  }
  if (mode) {
    const char *value = mode == 2 ? "not-an-integer" : mode == 3 ? "0" : mode == 4 ? "-1" : "2147483648";
    size_t capacity = *size;
    *size = strlen(value) + 1;
    if (destination && capacity) snprintf(destination, capacity, "%s", value);
    return RD_KAFKA_CONF_OK;
  }
  return real(conf, name, destination, size);
}