packages feed

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

/* Integration executable only. Invoke the installed native callback with an
 * owned native partition list and controlled, actually allocated error objects.
 * Mode zero only observes native commit arguments without changing results.
 * Real cooperative group handoffs remain separate broker scenarios. */
#define _GNU_SOURCE
#include <dlfcn.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <librdkafka/rdkafka.h>

typedef void (*rebalance_callback)(rd_kafka_t *, rd_kafka_resp_err_t,
                                    rd_kafka_topic_partition_list_t *, void *);
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static int enabled, mode, stats[7];
static rd_kafka_t *client;
static int commit_calls, commit_async;
static rebalance_callback installed;
static rd_kafka_error_t *owned_error;

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

void rdkafka_hs_test_state_begin(int value) {
  pthread_mutex_lock(&lock);
  if (owned_error) abort();
  enabled = 1; mode = value; client = NULL; installed = NULL;
  commit_calls = 0; commit_async = -1;
  for (int i = 0; i < 7; ++i) stats[i] = 0;
  pthread_mutex_unlock(&lock);
}
void rdkafka_hs_test_state_end(void) {
  REAL(rd_kafka_error_destroy);
  pthread_mutex_lock(&lock);
  rd_kafka_error_t *retained = owned_error;
  enabled = 0; client = NULL; installed = NULL; owned_error = NULL;
  pthread_mutex_unlock(&lock);
  if (retained) real(retained);
}
int rdkafka_hs_test_state_stat(int index) {
  pthread_mutex_lock(&lock);
  int value = index >= 0 && index < 7 ? stats[index] : -1;
  pthread_mutex_unlock(&lock);
  return value;
}
void rdkafka_hs_state_client_created(rd_kafka_type_t type, rd_kafka_t *created) {
  pthread_mutex_lock(&lock);
  if (enabled && type == RD_KAFKA_CONSUMER && created) client = created;
  pthread_mutex_unlock(&lock);
}
void rdkafka_hs_state_error_destroyed(rd_kafka_error_t *error) {
  pthread_mutex_lock(&lock);
  if (enabled && error && error == owned_error) { stats[1]++; owned_error = NULL; }
  pthread_mutex_unlock(&lock);
}
void rd_kafka_conf_set_rebalance_cb(rd_kafka_conf_t *conf, rebalance_callback callback) {
  REAL(rd_kafka_conf_set_rebalance_cb);
  real(conf, callback);
  pthread_mutex_lock(&lock);
  if (enabled) installed = callback;
  pthread_mutex_unlock(&lock);
}
const char *rd_kafka_rebalance_protocol(rd_kafka_t *rk) {
  REAL(rd_kafka_rebalance_protocol);
  pthread_mutex_lock(&lock);
  int controlled = enabled && rk == client && mode >= 1 && mode <= 6;
  int eager = mode >= 5;
  if (controlled) stats[4]++;
  pthread_mutex_unlock(&lock);
  return controlled ? (eager ? "EAGER" : "COOPERATIVE") : real(rk);
}
static rd_kafka_error_t *failure_locked(int index) {
  if (owned_error) abort();
  stats[index]++;
  owned_error = rd_kafka_error_new(mode == 3 || mode == 4 ? (rd_kafka_resp_err_t)32767 : RD_KAFKA_RESP_ERR__STATE,
                                   "%s", "controlled rebalance failure");
  stats[0]++;
  return owned_error;
}
rd_kafka_error_t *rd_kafka_incremental_assign(rd_kafka_t *rk, const rd_kafka_topic_partition_list_t *partitions) {
  REAL(rd_kafka_incremental_assign);
  pthread_mutex_lock(&lock);
  if (enabled && rk == client && mode >= 1 && mode <= 6) {
    rd_kafka_error_t *error = failure_locked(2);
    pthread_mutex_unlock(&lock); return error;
  }
  pthread_mutex_unlock(&lock);
  return real(rk, partitions);
}
rd_kafka_error_t *rd_kafka_incremental_unassign(rd_kafka_t *rk, const rd_kafka_topic_partition_list_t *partitions) {
  REAL(rd_kafka_incremental_unassign);
  pthread_mutex_lock(&lock);
  if (enabled && rk == client && mode >= 1 && mode <= 6) {
    rd_kafka_error_t *error = failure_locked(3);
    pthread_mutex_unlock(&lock); return error;
  }
  pthread_mutex_unlock(&lock);
  return real(rk, partitions);
}
rd_kafka_resp_err_t rd_kafka_assign(rd_kafka_t *rk, const rd_kafka_topic_partition_list_t *partitions) {
  REAL(rd_kafka_assign);
  pthread_mutex_lock(&lock);
  int observed = enabled && rk == client && mode >= 5 && mode <= 8;
  int controlled = observed && (mode == 5 || mode == 6 || (mode == 8 && !partitions));
  if (observed) stats[5]++;
  pthread_mutex_unlock(&lock);
  return controlled ? RD_KAFKA_RESP_ERR__STATE : real(rk, partitions);
}
void rdkafka_hs_test_state_trigger(const char *topic) {
  pthread_mutex_lock(&lock);
  rd_kafka_t *target = client; rebalance_callback callback = installed;
  int revoke = mode == 2 || mode == 4 || mode == 6;
  int unexpected = mode == 7 || mode == 8;
  if (!enabled || !target || !callback) abort();
  stats[6]++;
  pthread_mutex_unlock(&lock);
  rd_kafka_topic_partition_list_t *partitions = rd_kafka_topic_partition_list_new(1);
  rd_kafka_topic_partition_list_add(partitions, topic, 0);
  callback(target, unexpected ? RD_KAFKA_RESP_ERR_GROUP_AUTHORIZATION_FAILED : revoke ? RD_KAFKA_RESP_ERR__REVOKE_PARTITIONS : RD_KAFKA_RESP_ERR__ASSIGN_PARTITIONS, partitions, NULL);
  rd_kafka_topic_partition_list_destroy(partitions);
}
void rdkafka_hs_test_state_trigger_cooperative(const char *topic, int revoke) {
  pthread_mutex_lock(&lock);
  rd_kafka_t *target = client; rebalance_callback callback = installed;
  if (!enabled || mode != 9 || !target || !callback) abort();
  pthread_mutex_unlock(&lock);
  /* No protocol or action is replaced: the client must have negotiated
   * COOPERATIVE and the installed bridge calls real incremental APIs. */
  rd_kafka_topic_partition_list_t *partitions = rd_kafka_topic_partition_list_new(1);
  rd_kafka_topic_partition_t *partition = rd_kafka_topic_partition_list_add(partitions, topic, 0);
  if (!revoke) partition->offset = RD_KAFKA_OFFSET_END;
  callback(target, revoke ? RD_KAFKA_RESP_ERR__REVOKE_PARTITIONS : RD_KAFKA_RESP_ERR__ASSIGN_PARTITIONS, partitions, NULL);
  rd_kafka_topic_partition_list_destroy(partitions);
}
void rdkafka_hs_test_state_leak_control(void) {
  pthread_mutex_lock(&lock);
  failure_locked(2);
  pthread_mutex_unlock(&lock);
}

int rdkafka_hs_test_state_commit_count(void) {
  pthread_mutex_lock(&lock); int value = commit_calls; pthread_mutex_unlock(&lock); return value;
}
int rdkafka_hs_test_state_commit_async(void) {
  pthread_mutex_lock(&lock); int value = commit_async; pthread_mutex_unlock(&lock); return value;
}
rd_kafka_resp_err_t rd_kafka_commit(rd_kafka_t *rk, const rd_kafka_topic_partition_list_t *offsets, int async) {
  REAL(rd_kafka_commit);
  pthread_mutex_lock(&lock);
  if (enabled && mode == 0 && rk == client) { commit_calls++; commit_async = async; }
  pthread_mutex_unlock(&lock);
  return real(rk, offsets, async);
}

/* A sync queue handle is a temporary wrapper. Observe its length within the
 * call that borrows it; retain only scalar observations after that call. */
static int queue_tracking, queue_calls, notification_length;
static int queue_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;
}
void rdkafka_hs_test_queue_begin(void) {
  pthread_mutex_lock(&lock);
  if (queue_tracking) abort();
  queue_tracking = 1; queue_calls = 0; notification_length = -1;
  pthread_mutex_unlock(&lock);
}
void rdkafka_hs_test_queue_end(void) {
  pthread_mutex_lock(&lock);
  queue_tracking = 0; notification_length = -1;
  pthread_mutex_unlock(&lock);
}
void rd_kafka_queue_io_event_enable(rd_kafka_queue_t *queue, int fd, const void *payload, size_t size) {
  REAL(rd_kafka_queue_io_event_enable);
  real(queue, fd, payload, size);
  if (!queue_application_call(__builtin_return_address(0))) return;
  pthread_mutex_lock(&lock);
  if (queue_tracking) { notification_length = (int)rd_kafka_queue_length(queue); queue_calls++; }
  pthread_mutex_unlock(&lock);
}
int rdkafka_hs_test_queue_stat(int index) {
  pthread_mutex_lock(&lock);
  int result = index == 0 ? queue_calls :
    index == 1 && queue_tracking ? notification_length : -1;
  pthread_mutex_unlock(&lock);
  return result;
}