packages feed

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

/* Test executable only: actual polled messages, owned return boundaries and
 * decoder failures. Native calls remain real except declared fault results. */
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <librdkafka/rdkafka.h>

static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t resume = PTHREAD_COND_INITIALIZER;
static int enabled, mode, released, count, stats[8];
static rd_kafka_message_t *messages[8];
static int live[8];
static int32_t original_partition[8];
static int64_t original_offset[8];
static rd_kafka_topic_t *original_topic[8];
static rd_kafka_resp_err_t original_error[8];

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

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)))

void rdkafka_hs_test_messages_begin(int value) {
  pthread_mutex_lock(&lock);
  enabled = 1; mode = value; released = count = 0;
  memset(stats, 0, sizeof stats); memset(messages, 0, sizeof messages); memset(live, 0, sizeof live);
  pthread_mutex_unlock(&lock);
}
void rdkafka_hs_test_messages_release(void) {
  pthread_mutex_lock(&lock);
  released = 1; pthread_cond_broadcast(&resume);
  pthread_mutex_unlock(&lock);
}
void rdkafka_hs_test_messages_end(void) {
  REAL(rd_kafka_message_destroy);
  rd_kafka_message_t *retained[8]; int remaining = 0;
  pthread_mutex_lock(&lock);
  for (int i = 0; i < count; ++i) if (live[i]) {
    messages[i]->partition = original_partition[i];
    messages[i]->offset = original_offset[i];
    messages[i]->rkt = original_topic[i];
    messages[i]->err = original_error[i];
    retained[remaining++] = messages[i];
  }
  enabled = 0; released = 1; count = 0;
  memset(messages, 0, sizeof messages); memset(live, 0, sizeof live);
  pthread_cond_broadcast(&resume);
  pthread_mutex_unlock(&lock);
  /* Release the deliberate control, or messages retained by a failing test. */
  for (int i = 0; i < remaining; ++i) real(retained[i]);
}
int rdkafka_hs_test_messages_stat(int index) {
  pthread_mutex_lock(&lock);
  int result = index >= 0 && index < 8 ? stats[index] : -1;
  pthread_mutex_unlock(&lock);
  return result;
}
static void boundary(void) {
  stats[4]++;
  while (!released) pthread_cond_wait(&resume, &lock);
}
static void acquired(rd_kafka_message_t *message) {
  if (count == 8 || !message) abort();
  original_partition[count] = message->partition;
  original_offset[count] = message->offset;
  original_topic[count] = message->rkt;
  original_error[count] = message->err;
  messages[count] = message; live[count++] = 1; stats[1]++;
}
static long long now_ms(void) {
  struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now);
  return (long long)now.tv_sec * 1000 + now.tv_nsec / 1000000;
}

ssize_t rd_kafka_consume_batch_queue(rd_kafka_queue_t *queue, int timeout,
                                    rd_kafka_message_t **output, size_t capacity) {
  REAL(rd_kafka_consume_batch_queue);
  pthread_mutex_lock(&lock);
  int tracking = enabled && APPLICATION_CALL;
  int current = mode;
  if (tracking) stats[0]++;
  pthread_mutex_unlock(&lock);
  if (!tracking) return real(queue, timeout, output, capacity);
  if (current == 4) { errno = ETIMEDOUT; return -1; }

  /* Ownership faults require three actual records at one native boundary.
   * Collect from the real queue up to that bound; ordinary observation mode
   * 20 preserves the requested timeout/capacity exactly. */
  ssize_t received = 0;
  if (current <= 6) {
    size_t target = capacity < 3 ? capacity : 3;
    long long deadline = now_ms() + 3000;
    while ((size_t)received < target && now_ms() < deadline) {
      int remaining = (int)(deadline - now_ms());
      int wait = timeout < 0 || timeout > remaining ? remaining : timeout;
      ssize_t next = real(queue, wait, output + received, target - (size_t)received);
      if (next < 0) {
        int saved = errno;
        __typeof__(&rd_kafka_message_destroy) destroy = (__typeof__(&rd_kafka_message_destroy))dlsym(RTLD_NEXT, "rd_kafka_message_destroy");
        if (!destroy) abort();
        for (ssize_t i = 0; i < received; ++i) destroy(output[i]);
        errno = saved; return next;
      }
      received += next;
    }
  } else received = real(queue, timeout, output, capacity);
  pthread_mutex_lock(&lock);
  for (ssize_t i = 0; i < received; ++i) acquired(output[i]);
  if (current == 2 && received > 0) boundary();
  pthread_mutex_unlock(&lock);
  return received;
}

static rd_kafka_message_t *single(rd_kafka_message_t *message, int app, int queue) {
  pthread_mutex_lock(&lock);
  int selected = (queue ? mode >= 10 && mode <= 12 : mode >= 13 && mode <= 19) || (mode >= 21 && mode <= 25);
  if (enabled && app && selected && message && message->err == RD_KAFKA_RESP_ERR_NO_ERROR) {
    acquired(message);
    /* Native error frames can lack a partition or offset independently.
     * Retain the real allocation and restore its fields before destruction. */
    if (mode == 21 || mode == 23) message->partition = RD_KAFKA_PARTITION_UA;
    if (mode == 21 || mode == 22) message->offset = RD_KAFKA_OFFSET_INVALID;
    if (mode == 24 || mode == 25) {
      /* Only documented public message fields change; restore them before
       * native destruction, so the original allocation/ownership stays real. */
      message->rkt = NULL; message->partition = RD_KAFKA_PARTITION_UA;
      message->offset = RD_KAFKA_OFFSET_INVALID;
      message->err = mode == 24 ? RD_KAFKA_RESP_ERR__BAD_MSG : RD_KAFKA_RESP_ERR__FATAL;
    }
    if (mode == 10 || mode == 13) boundary();
  }
  pthread_mutex_unlock(&lock);
  return message;
}
rd_kafka_message_t *rd_kafka_consumer_poll(rd_kafka_t *client, int timeout) {
  REAL(rd_kafka_consumer_poll);
  int app = APPLICATION_CALL;
  return single(real(client, timeout), app, 0);
}
rd_kafka_message_t *rd_kafka_consume_queue(rd_kafka_queue_t *queue, int timeout) {
  REAL(rd_kafka_consume_queue);
  int app = APPLICATION_CALL;
  return single(real(queue, timeout), app, 1);
}
rd_kafka_resp_err_t rd_kafka_message_headers(const rd_kafka_message_t *message,
                                           rd_kafka_headers_t **headers) {
  REAL(rd_kafka_message_headers);
  extern int rdkafka_hs_test_producer_header_failure(void);
  if (rdkafka_hs_test_producer_header_failure()) { *headers = NULL; return RD_KAFKA_RESP_ERR__BAD_MSG; }
  pthread_mutex_lock(&lock);
  int index;
  for (index = 0; index < count && messages[index] != message; ++index) {}
  if (enabled && index < count && live[index]) {
    stats[3]++;
    if (mode >= 21 && mode <= 23) {
      *headers = NULL;
      pthread_mutex_unlock(&lock);
      return RD_KAFKA_RESP_ERR__BAD_MSG;
    }
    if (index == 1 && mode == 3) boundary();
    if (index == 0 && (mode == 11 || mode == 14)) boundary();
    if ((index == 1 && (mode == 1 || mode == 6)) || (index == 0 && (mode == 12 || mode == 15))) {
      *headers = NULL;
      int current = mode;
      pthread_mutex_unlock(&lock);
      return current == 1 ? (rd_kafka_resp_err_t)32767 : RD_KAFKA_RESP_ERR__BAD_MSG;
    }
  }
  pthread_mutex_unlock(&lock);
  return real(message, headers);
}
int64_t rd_kafka_message_timestamp(const rd_kafka_message_t *message, rd_kafka_timestamp_type_t *type) {
  REAL(rd_kafka_message_timestamp);
  pthread_mutex_lock(&lock);
  int selected = 0;
  for (int i = 0; i < count; ++i) if (messages[i] == message && live[i]) selected = 1;
  int current = enabled && selected ? mode : 0;
  pthread_mutex_unlock(&lock);
  if (current == 17) { if (type) *type = RD_KAFKA_TIMESTAMP_NOT_AVAILABLE; return -1; }
  if (current == 18) { if (type) *type = RD_KAFKA_TIMESTAMP_LOG_APPEND_TIME; return 123456789; }
  if (current == 19) { if (type) *type = RD_KAFKA_TIMESTAMP_CREATE_TIME; return 987654321; }
  return real(message, type);
}
void rd_kafka_message_destroy(rd_kafka_message_t *message) {
  REAL(rd_kafka_message_destroy);
  int skip = 0;
  pthread_mutex_lock(&lock);
  if (enabled && APPLICATION_CALL) for (int i = 0; i < count; ++i) if (messages[i] == message) {
    stats[6]++;
    if (!live[i]) { stats[5]++; skip = 1; }
    else if (mode == 5 && i == count - 1) { stats[7]++; skip = 1; }
    else {
      message->partition = original_partition[i];
      message->offset = original_offset[i];
      message->rkt = original_topic[i];
      message->err = original_error[i];
      live[i] = 0; stats[2]++;
    }
    break;
  }
  pthread_mutex_unlock(&lock);
  if (!skip) real(message);
}