mirror of
https://github.com/Hamlib/Hamlib
synced 2026-08-13 17:32:05 -04:00
Concurrent calls to rig_debug() could use the same temporary buffer
and corrupt the saved debug history.
Give each call its own buffer and update the shared history under the
existing mutex.
(cherry picked from commit cf3fcc4b8a)
206 lines
4.9 KiB
C
206 lines
4.9 KiB
C
/*
|
|
* Test debug history behavior.
|
|
*
|
|
* Copyright (C) 2026 The Hamlib Group
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
|
*/
|
|
|
|
#include <pthread.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <hamlib/rig.h>
|
|
|
|
#define THREAD_COUNT 8
|
|
#define PADDING_SIZE 1536
|
|
#define MESSAGE_SIZE 1664
|
|
#define HISTORY_LINE_COUNT 25
|
|
#define RETAINED_LINE_COUNT 20
|
|
|
|
struct start_gate
|
|
{
|
|
pthread_mutex_t mutex;
|
|
pthread_cond_t condition;
|
|
int open;
|
|
};
|
|
|
|
struct worker_context
|
|
{
|
|
struct start_gate *gate;
|
|
unsigned int thread;
|
|
};
|
|
|
|
static int ignore_debug_output(enum rig_debug_level_e debug_level,
|
|
rig_ptr_t arg, const char *fmt, va_list ap)
|
|
{
|
|
(void)debug_level;
|
|
(void)arg;
|
|
(void)fmt;
|
|
(void)ap;
|
|
return RIG_OK;
|
|
}
|
|
|
|
static int build_message(char *message, size_t size, unsigned int thread)
|
|
{
|
|
char padding[PADDING_SIZE + 1];
|
|
int length;
|
|
|
|
memset(padding, (int)('A' + thread), PADDING_SIZE);
|
|
padding[PADDING_SIZE] = '\0';
|
|
length = snprintf(message, size,
|
|
"concurrent history thread=%u padding=%s marker=%u\n",
|
|
thread, padding, thread);
|
|
|
|
return length >= 0 && (size_t)length < size;
|
|
}
|
|
|
|
static void *worker(void *arg)
|
|
{
|
|
struct worker_context *context = arg;
|
|
char message[MESSAGE_SIZE];
|
|
|
|
if (!build_message(message, sizeof(message), context->thread))
|
|
{
|
|
abort();
|
|
}
|
|
|
|
pthread_mutex_lock(&context->gate->mutex);
|
|
while (!context->gate->open)
|
|
{
|
|
pthread_cond_wait(&context->gate->condition, &context->gate->mutex);
|
|
}
|
|
pthread_mutex_unlock(&context->gate->mutex);
|
|
|
|
rig_debug(RIG_DEBUG_TRACE, "%s", message);
|
|
return NULL;
|
|
}
|
|
|
|
static int test_concurrent_history(void)
|
|
{
|
|
struct start_gate gate = {
|
|
PTHREAD_MUTEX_INITIALIZER,
|
|
PTHREAD_COND_INITIALIZER,
|
|
0
|
|
};
|
|
struct worker_context contexts[THREAD_COUNT];
|
|
pthread_t threads[THREAD_COUNT];
|
|
size_t expected_length = 0;
|
|
unsigned int thread;
|
|
|
|
rig_debug_clear();
|
|
rig_set_debug(RIG_DEBUG_NONE);
|
|
|
|
for (thread = 0; thread < THREAD_COUNT; ++thread)
|
|
{
|
|
contexts[thread].gate = &gate;
|
|
contexts[thread].thread = thread;
|
|
if (pthread_create(&threads[thread], NULL, worker,
|
|
&contexts[thread]) != 0)
|
|
{
|
|
fprintf(stderr, "failed to create worker thread %u\n", thread);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
pthread_mutex_lock(&gate.mutex);
|
|
gate.open = 1;
|
|
pthread_cond_broadcast(&gate.condition);
|
|
pthread_mutex_unlock(&gate.mutex);
|
|
|
|
for (thread = 0; thread < THREAD_COUNT; ++thread)
|
|
{
|
|
if (pthread_join(threads[thread], NULL) != 0)
|
|
{
|
|
fprintf(stderr, "failed to join worker thread %u\n", thread);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
pthread_cond_destroy(&gate.condition);
|
|
pthread_mutex_destroy(&gate.mutex);
|
|
|
|
for (thread = 0; thread < THREAD_COUNT; ++thread)
|
|
{
|
|
char expected[MESSAGE_SIZE];
|
|
|
|
if (!build_message(expected, sizeof(expected), thread))
|
|
{
|
|
return 0;
|
|
}
|
|
expected_length += strlen(expected);
|
|
if (strstr(debugmsgsave, expected) == NULL)
|
|
{
|
|
fprintf(stderr,
|
|
"history does not contain an intact message "
|
|
"from thread %u\n",
|
|
thread);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (strlen(debugmsgsave) != expected_length)
|
|
{
|
|
fprintf(stderr, "history retained %zu bytes; expected %zu\n",
|
|
strlen(debugmsgsave), expected_length);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int test_rolling_history(void)
|
|
{
|
|
const char *history;
|
|
unsigned int line;
|
|
|
|
rig_debug_clear();
|
|
rig_set_debug_callback(ignore_debug_output, NULL);
|
|
rig_set_debug(RIG_DEBUG_TRACE);
|
|
|
|
for (line = 0; line < HISTORY_LINE_COUNT; ++line)
|
|
{
|
|
rig_debug(RIG_DEBUG_TRACE, "history line=%u\n", line);
|
|
}
|
|
|
|
rig_set_debug_callback(NULL, NULL);
|
|
rig_set_debug(RIG_DEBUG_NONE);
|
|
|
|
history = debugmsgsave;
|
|
for (line = HISTORY_LINE_COUNT - RETAINED_LINE_COUNT;
|
|
line < HISTORY_LINE_COUNT; ++line)
|
|
{
|
|
char expected[32];
|
|
int written = snprintf(expected, sizeof(expected),
|
|
"history line=%u\n", line);
|
|
|
|
if (written < 0 || (size_t)written >= sizeof(expected)
|
|
|| strncmp(history, expected, (size_t)written) != 0)
|
|
{
|
|
fprintf(stderr, "history did not retain line %u in order\n", line);
|
|
return 0;
|
|
}
|
|
history += written;
|
|
}
|
|
|
|
if (*history != '\0')
|
|
{
|
|
fprintf(stderr, "history retained more than %u lines\n",
|
|
RETAINED_LINE_COUNT);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
if (!test_concurrent_history() || !test_rolling_history())
|
|
{
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|