File size: 6,053 Bytes
e996a55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#include "unity/unity.h"
#include "zlib.h"

#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <pthread.h>

/* Detect availability of C11 atomics similarly to the source, to decide
   whether concurrent initialization is supported. */
#if defined(__STDC__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__)
#define HAVE_C11_ATOMICS 1
#else
#define HAVE_C11_ATOMICS 0
#endif

/* Number of threads used in concurrency tests */
#ifndef ONCE_TEST_THREADS
#define ONCE_TEST_THREADS 8
#endif

/* Simple shared state for thread start synchronization */
typedef struct {
    volatile int start_flag;
} start_sync_t;

typedef struct {
    start_sync_t *sync;
    const unsigned char *buf;
    size_t len;
    const void *table_ptr;
    unsigned long crc_out;
} thread_arg_t;

static void busy_wait_until_start(start_sync_t *sync) {
    /* Simple spin-wait on a volatile flag to try to start calls together. */
    while (!sync->start_flag) { /* spin */ }
}

static void *thread_get_table(void *arg) {
    thread_arg_t *a = (thread_arg_t *)arg;
    busy_wait_until_start(a->sync);
    /* Call get_crc_table() to exercise once() initialization path. */
    a->table_ptr = (const void *)get_crc_table();
    /* Also do a CRC to ensure the table is actually usable. */
    if (a->buf && a->len > 0) {
        a->crc_out = crc32(0L, a->buf, (uInt)a->len);
    } else {
        a->crc_out = 0UL;
    }
    return NULL;
}

static void *thread_do_crc(void *arg) {
    thread_arg_t *a = (thread_arg_t *)arg;
    busy_wait_until_start(a->sync);
    /* Just compute CRC many times to apply load. */
    unsigned long crc = 0L;
    for (int i = 0; i < 1000; i++) {
        crc = crc32(0L, a->buf, (uInt)a->len);
    }
    a->crc_out = crc;
    /* Read the table pointer once to compare pointers across threads too. */
    a->table_ptr = (const void *)get_crc_table();
    return NULL;
}

void setUp(void) {
    /* No pre-initialization here so that the concurrent init test can
       actually race on first initialization when atomics are available. */
}

void tearDown(void) {
    /* Nothing to clean up */
}

/* Test 1: Concurrent initialization via get_crc_table() (only if atomics available).
   Validates that all threads observe the same table pointer and no crashes occur. */
void test_once_concurrent_initialization_via_get_crc_table(void) {
#if HAVE_C11_ATOMICS
    pthread_t tids[ONCE_TEST_THREADS];
    thread_arg_t args[ONCE_TEST_THREADS];
    start_sync_t sync;
    sync.start_flag = 0;

    static const unsigned char data[] = "zlib once() concurrent init";
    for (int i = 0; i < ONCE_TEST_THREADS; i++) {
        args[i].sync = &sync;
        args[i].buf = data;
        args[i].len = strlen((const char *)data);
        args[i].table_ptr = NULL;
        args[i].crc_out = 0;
        int rc = pthread_create(&tids[i], NULL, thread_get_table, &args[i]);
        TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "pthread_create failed");
    }

    /* Start all threads at roughly the same time. */
    sync.start_flag = 1;

    for (int i = 0; i < ONCE_TEST_THREADS; i++) {
        pthread_join(tids[i], NULL);
    }

    /* All should have the same non-null table pointer. */
    const void *ptr0 = args[0].table_ptr;
    TEST_ASSERT_NOT_NULL(ptr0);
    for (int i = 1; i < ONCE_TEST_THREADS; i++) {
        TEST_ASSERT_EQUAL_PTR(ptr0, args[i].table_ptr);
    }

    /* A subsequent call should return the exact same pointer. */
    const void *ptr_after = (const void *)get_crc_table();
    TEST_ASSERT_EQUAL_PTR(ptr0, ptr_after);
#else
    TEST_IGNORE_MESSAGE("Concurrent initialization is not supported without C11 atomics; skipping.");
#endif
}

/* Test 2: Known CRC value, single-thread sanity check. */
void test_once_crc_known_value_single_thread(void) {
    const unsigned char msg[] = "123456789";
    unsigned long crc = crc32(0L, msg, (uInt)strlen((const char *)msg));
    TEST_ASSERT_EQUAL_HEX32(0xCBF43926UL, crc);
}

/* Test 3: Repeated get_crc_table() returns stable pointer (single-thread). */
void test_once_pointer_stability_single_thread(void) {
    const void *p1 = (const void *)get_crc_table();
    TEST_ASSERT_NOT_NULL(p1);

    for (int i = 0; i < 1000; i++) {
        const void *pi = (const void *)get_crc_table();
        TEST_ASSERT_EQUAL_PTR(p1, pi);
    }
}

/* Test 4: Concurrent CRC computations after single-thread pre-initialization.
   This is allowed even without atomics as long as get_crc_table() was called first. */
void test_once_concurrent_crc_after_preinit(void) {
    /* Pre-initialize tables single-threaded to avoid concurrent init in non-atomics builds. */
    const void *ptable = (const void *)get_crc_table();
    TEST_ASSERT_NOT_NULL(ptable);

    pthread_t tids[ONCE_TEST_THREADS];
    thread_arg_t args[ONCE_TEST_THREADS];
    start_sync_t sync;
    sync.start_flag = 0;

    static const unsigned char msg[] = "The quick brown fox jumps over the lazy dog";
    /* Compute expected CRC single-threaded. */
    unsigned long expected = crc32(0L, msg, (uInt)strlen((const char *)msg));

    for (int i = 0; i < ONCE_TEST_THREADS; i++) {
        args[i].sync = &sync;
        args[i].buf = msg;
        args[i].len = strlen((const char *)msg);
        args[i].table_ptr = NULL;
        args[i].crc_out = 0;
        int rc = pthread_create(&tids[i], NULL, thread_do_crc, &args[i]);
        TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "pthread_create failed");
    }

    /* Start threads together. */
    sync.start_flag = 1;

    for (int i = 0; i < ONCE_TEST_THREADS; i++) {
        pthread_join(tids[i], NULL);
    }

    for (int i = 0; i < ONCE_TEST_THREADS; i++) {
        TEST_ASSERT_EQUAL_HEX32(expected, args[i].crc_out);
        TEST_ASSERT_EQUAL_PTR(ptable, args[i].table_ptr);
    }
}

int main(void) {
    UNITY_BEGIN();
    RUN_TEST(test_once_concurrent_initialization_via_get_crc_table);
    RUN_TEST(test_once_crc_known_value_single_thread);
    RUN_TEST(test_once_pointer_stability_single_thread);
    RUN_TEST(test_once_concurrent_crc_after_preinit);
    return UNITY_END();
}