The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
worker_test.c
Go to the documentation of this file.
1/*
2 * worker_test.c Tests for channels
3 *
4 * Version: $Id: e139cac2fd6aab63bc5b08806b73f2037d4cbcf5 $
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 *
20 * @copyright 2016 Alan DeKok (aland@freeradius.org)
21 */
22
23RCSID("$Id: e139cac2fd6aab63bc5b08806b73f2037d4cbcf5 $")
24
25#include <freeradius-devel/io/control.h>
26#include <freeradius-devel/io/listen.h>
27#include <freeradius-devel/io/worker.h>
28#include <freeradius-devel/util/debug.h>
29#include <freeradius-devel/util/syserror.h>
30
31#ifdef HAVE_GETOPT_H
32# include <getopt.h>
33#endif
34
35#include <signal.h>
36
37
38#define MAX_MESSAGES (2048)
39#define MAX_CONTROL_PLANE (1024)
40#define MAX_KEVENTS (10)
41#define MAX_WORKERS (1024)
42
43#define MPRINT1 if (debug_lvl) printf
44#define MPRINT2 if (debug_lvl > 1) printf
45
46typedef struct {
47 int id; //!< ID of the worker 0..N
48 pthread_t pthread_id; //!< pthread ID of the worker
49 fr_worker_t *worker; //!< pointer to the worker
50 fr_channel_t *ch; //!< channel for communicating with the worker
52
53static int debug_lvl = 0;
54static int kq_master;
57static int max_messages = 10;
58static int max_control_plane = 0;
59static int max_outstanding = 1;
60static bool touch_memory = false;
61static int num_workers = 1;
62static bool quiet = false;
64
65/**********************************************************************/
66typedef struct request_s request_t;
67
69{
70 return NULL;
71}
72
73void request_verify(UNUSED char const *file, UNUSED int line, UNUSED request_t const *request)
74{
75}
76
77/**********************************************************************/
78
79static NEVER_RETURNS void usage(void)
80{
81 fprintf(stderr, "usage: worker_test [OPTS]\n");
82 fprintf(stderr, " -c <control-plane> Size of the control plane queue.\n");
83 fprintf(stderr, " -m <messages> Send number of messages.\n");
84 fprintf(stderr, " -o <outstanding> Keep number of messages outstanding.\n");
85 fprintf(stderr, " -q quiet - suppresses worker stats.\n");
86 fprintf(stderr, " -t Touch memory for fake packets.\n");
87 fprintf(stderr, " -w N Create N workers. Default is 1.\n");
88 fprintf(stderr, " -x Debugging mode.\n");
89
90 fr_exit_now(EXIT_FAILURE);
91}
92
93static rlm_rcode_t test_process(UNUSED void const *inst, request_t *request, fr_io_action_t action)
94{
95 MPRINT1("\t\tPROCESS --- request %"PRIu64" action %d\n", request->number, action);
97}
98
99static int test_decode(UNUSED void const *instance, request_t *request, uint8_t *const data, size_t data_len)
100{
102
103 /*
104 * The data is the packet number.
105 */
106 memcpy(&number, data, sizeof(number));
107 request->number = number;
108
109 request->async->process = test_process;
110
111 MPRINT1("\t\tDECODE <<< request %"PRIu64" - %p data %p size %zd\n", request->number,
112 request->async->packet_ctx, data, data_len);
113 return 0;
114}
115
116static ssize_t test_encode(void const *instance, request_t *request, uint8_t *const data, size_t data_len)
117{
118 MPRINT1("\t\tENCODE >>> request %"PRIu64" - data %p %p size %zd\n", request->number,
119 instance, data, data_len);
120
121 return data_len;
122}
123
124static size_t test_nak(UNUSED void const *instance, void *packet_ctx, uint8_t *const packet, size_t packet_len, uint8_t *reply, UNUSED size_t reply_len)
125{
127
128 /*
129 * The data is the packet number.
130 */
131 memcpy(&number, packet, sizeof(number));
132 memcpy(reply, packet, sizeof(number));
133
134 MPRINT1("\t\tNAK !!! request %"PRIu64" - data %p %p size %zd\n", (uint64_t) number, packet_ctx, packet, packet_len);
135
136 return 10;
137}
138
140 .name = "worker-test",
141 .default_message_size = 4096,
142 .nak = test_nak,
143 .encode = test_encode,
144 .decode = test_decode
145};
146
147static void *worker_thread(void *arg)
148{
149 TALLOC_CTX *ctx;
150 fr_worker_t *worker;
153 char buffer[16];
154
155 sw = (fr_schedule_worker_t *) arg;
156
157 MPRINT1("\tWorker %d started.\n", sw->id);
158
159 MEM(ctx = talloc_init_const("worker"));
160
161 el = fr_event_list_alloc(ctx, NULL, NULL);
162 if (!el) {
163 fprintf(stderr, "worker_test: Failed to create the event list\n");
164 fr_exit_now(EXIT_FAILURE);
165 }
166
167 snprintf(buffer, sizeof(buffer), "%d", sw->id);
168 worker = sw->worker = fr_worker_create(ctx, el, buffer, &default_log, L_DBG_LVL_MAX);
169 if (!worker) {
170 fprintf(stderr, "worker_test: Failed to create the worker\n");
171 fr_exit_now(EXIT_FAILURE);
172 }
173
174 MPRINT1("\tWorker %d looping.\n", sw->id);
175 fr_worker(worker);
176
177 sw->worker = NULL;
178 MPRINT1("\tWorker %d exiting.\n", sw->id);
179
180 talloc_free(ctx);
181 return NULL;
182}
183
184
185static void master_process(void)
186{
187 bool running, signaled_close;
188 int rcode, i, num_events, which_worker;
189 int num_outstanding, num_messages;
190 int num_replies;
192 TALLOC_CTX *ctx;
193 fr_channel_t *ch;
195 pthread_attr_t attr;
197 fr_listen_t listen = { .app_io = &app_io };
198 struct kevent events[MAX_KEVENTS];
199
200 MEM(ctx = talloc_init_const("master"));
201
202 ms = fr_message_set_create(ctx, MAX_MESSAGES, sizeof(fr_channel_data_t), MAX_MESSAGES * 1024, false);
203 if (!ms) {
204 fprintf(stderr, "Failed creating message set\n");
205 fr_exit_now(EXIT_FAILURE);
206 }
207
208 MPRINT1("Master started.\n");
209
210 /*
211 * Create the worker threads.
212 */
213 (void) pthread_attr_init(&attr);
214 (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
215
216 for (i = 0; i < num_workers; i++) {
217 workers[i].id = i;
218 (void) pthread_create(&workers[i].pthread_id, &attr, worker_thread, &workers[i]);
219 }
220
221 MPRINT1("Master created %d workers.\n", num_workers);
222
223 /*
224 * Busy loop because that's fine for the test
225 */
226 num_outstanding = 0;
227 while (num_outstanding < num_workers) {
228 for (i = 0; i < num_workers; i++) {
229 if (!workers[i].worker) continue;
230 if (workers[i].ch != NULL) continue;
231
232 /*
233 * Create the channel and signal the
234 * worker that it is open
235 */
236 MPRINT1("Master creating channel to worker %d.\n", num_workers);
238 fr_assert(workers[i].ch != NULL);
239
240 (void) fr_channel_master_ctx_add(workers[i].ch, &workers[i]);
241
242 num_outstanding++;
243 }
244 }
245
246 MPRINT1("Master created all channels.\n");
247
248 /*
249 * Bootstrap the queue with messages.
250 */
251 num_replies = num_outstanding = num_messages = 0;
252 which_worker = 0;
253
254 running = true;
255 signaled_close = false;
256
257 while (running) {
258 fr_time_t now;
259 int num_to_send;
260 fr_channel_data_t *cd, *reply;
261
262 /*
263 * Ensure we have outstanding messages.
264 */
265 if (num_messages >= max_messages) {
266 MPRINT1("Master DONE sending\n");
267 goto check_close;
268 }
269
270 num_to_send = max_outstanding - num_outstanding;
271 if ((num_messages + num_to_send) > max_messages) {
272 num_to_send = max_messages - num_messages;
273 }
274 MPRINT1("Master sending %d messages\n", num_to_send);
275
276 for (i = 0; i < num_to_send; i++) {
277 cd = (fr_channel_data_t *) fr_message_alloc(ms, NULL, 100);
278 fr_assert(cd != NULL);
279
280 num_outstanding++;
281 num_messages++;
282
283 cd->m.when = fr_time();
284
285 cd->priority = 0;
286 cd->listen = &listen;
287
288 if (touch_memory) {
289 size_t j, k;
290
291 for (j = k = 0; j < cd->m.data_size; j++) {
292 k += cd->m.data[j];
293 }
294
295 cd->m.data[4] = k;
296 }
297
298 memcpy(cd->m.data, &num_messages, sizeof(num_messages));
299
300 MPRINT1("Master sent message %d to worker %d\n", num_messages, which_worker);
301 rcode = fr_channel_send_request(workers[which_worker].ch, cd, &reply);
302 if (rcode < 0) {
303 fprintf(stderr, "Failed sending request: %s\n", fr_syserror(errno));
304 }
305 which_worker++;
306 if (which_worker >= num_workers) which_worker = 0;
307
308 fr_assert(rcode == 0);
309 if (reply) {
310 num_replies++;
311 num_outstanding--;
312 MPRINT1("Master got reply %d, outstanding=%d, %d/%d sent.\n",
313 num_replies, num_outstanding, num_messages, max_messages);
314 fr_message_done(&reply->m);
315 }
316 }
317
318 /*
319 * Signal close only when done.
320 */
321check_close:
322 if (!signaled_close && (num_messages >= max_messages) && (num_outstanding == 0)) {
323 MPRINT1("Master signaling workers to exit.\n");
324
325 for (i = 0; i < num_workers; i++) {
326 if (!quiet) {
327 printf("Worker %d\n", i);
328 fr_worker_debug(workers[i].worker, stdout);
329 }
330
332 MPRINT1("Master asked exit for worker %d.\n", workers[i].id);
333 if (rcode < 0) {
334 fprintf(stderr, "Failed signaling close %d: %s\n", i, fr_syserror(errno));
335 fr_exit_now(EXIT_FAILURE);
336 }
337 }
338 signaled_close = true;
339 }
340
341 MPRINT1("Master waiting on events.\n");
342 fr_assert(num_messages <= max_messages);
343
344 num_events = kevent(kq_master, NULL, 0, events, MAX_KEVENTS, NULL);
345 MPRINT1("Master kevent returned %d\n", num_events);
346
347 if (num_events < 0) {
348 if (errno == EINTR) continue;
349
350 fprintf(stderr, "Failed waiting for kevent: %s\n", fr_syserror(errno));
351 fr_exit_now(EXIT_FAILURE);
352 }
353
354 if (num_events == 0) continue;
355
356 /*
357 * Service the events.
358 *
359 * @todo this should NOT take a channel pointer
360 */
361 for (i = 0; i < num_events; i++) {
363 }
364
365 now = fr_time();
366
367 MPRINT1("Master servicing control-plane\n");
368
369 while (true) {
370 uint32_t id;
371 size_t data_size;
372 char data[256];
373
374 data_size = fr_control_message_pop(aq_master, &id, data, sizeof(data));
375 if (!data_size) break;
376
378
379 ce = fr_channel_service_message(now, &ch, data, data_size);
380 MPRINT1("Master got channel event %d\n", ce);
381
382 switch (ce) {
384 MPRINT1("Master got data ready signal\n");
385
386 reply = fr_channel_recv_reply(ch);
387 if (!reply) {
388 MPRINT1("Master SIGNAL WITH NO DATA!\n");
389 continue;
390 }
391
392 do {
393 num_replies++;
394 num_outstanding--;
395 MPRINT1("Master got reply %d, outstanding=%d, %d/%d sent.\n",
396 num_replies, num_outstanding, num_messages, max_messages);
397 fr_message_done(&reply->m);
398 } while ((reply = fr_channel_recv_reply(ch)) != NULL);
399 break;
400
401 case FR_CHANNEL_CLOSE:
402 sw = fr_channel_master_ctx_get(ch);
403 fr_assert(sw != NULL);
404
405 MPRINT1("Master received close signal for worker %d\n", sw->id);
406 fr_assert(signaled_close == true);
407
408 (void) pthread_kill(sw->pthread_id, SIGTERM);
409 running = false;
410 break;
411
412 case FR_CHANNEL_NOOP:
413 break;
414
415 default:
416 fprintf(stderr, "Master got unexpected CE %d\n", ce);
417
418 /*
419 * Not written yet!
420 */
421 fr_assert(0 == 1);
422 break;
423 } /* switch over signal returned */
424 } /* drain the control plane */
425 } /* loop until told to exit */
426
427 MPRINT1("Master exiting.\n");
428
429 fr_time_t last_checked = fr_time();
430
431 /*
432 * Busy-wait for the workers to exit;
433 */
434 do {
435 fr_time_t now = fr_time();
436
437 num_outstanding = num_workers;
438
439 for (i = 0; i < num_workers; i++) {
440 if (!workers[i].worker) num_outstanding--;
441 }
442
443 if ((now - last_checked) > (NSEC / 10)) {
444 MPRINT1("still num_outstanding %d\n", num_outstanding);
445 }
446
447 } while (num_outstanding > 0);
448
449 /*
450 * Force all messages to be garbage collected
451 */
452 MPRINT2("GC\n");
454
455 if (debug_lvl > 1) fr_message_set_debug(stdout, ms);
456
457 /*
458 * After the garbage collection, all messages marked "done" MUST also be marked "free".
459 */
461 MPRINT2("Master messages used = %d\n", rcode);
462 fr_assert(rcode == 0);
463
464 talloc_free(ctx);
465
466}
467
468static void sig_ignore(int sig)
469{
470 (void) signal(sig, sig_ignore);
471}
472
473int main(int argc, char *argv[])
474{
475 int c;
476 TALLOC_CTX *autofree = talloc_autofree_context();
477
478 if (fr_time_start() < 0) {
479 fprintf(stderr, "Failed to start time: %s\n", fr_syserror(errno));
480 fr_exit_now(EXIT_FAILURE);
481 }
482
484
485 while ((c = getopt(argc, argv, "c:hm:o:qtw:x")) != -1) switch (c) {
486 case 'x':
487 debug_lvl++;
488 break;
489
490 case 'c':
491 max_control_plane = atoi(optarg);
492 break;
493
494 case 'm':
495 max_messages = atoi(optarg);
496 break;
497
498 case 'o':
499 max_outstanding = atoi(optarg);
500 break;
501
502 case 'q':
503 quiet = true;
504 break;
505
506 case 't':
507 touch_memory = true;
508 break;
509
510 case 'w':
511 num_workers = atoi(optarg);
512 if ((num_workers <= 0) || (num_workers >= MAX_WORKERS)) usage();
513 break;
514
515 case 'h':
516 default:
517 usage();
518 }
519
521
522 if (!max_control_plane) {
526 }
527
528#if 0
529 argc -= (optind - 1);
530 argv += (optind - 1);
531#endif
532
533 kq_master = kqueue();
534 fr_assert(kq_master >= 0);
535
537 fr_assert(aq_master != NULL);
538
540 fr_assert(control_master != NULL);
541
542 signal(SIGTERM, sig_ignore);
543
544 if (debug_lvl) {
545 setvbuf(stdout, NULL, _IONBF, 0);
546 }
547
549
550 close(kq_master);
551
552 return EXIT_SUCCESS;
553}
static int const char char buffer[256]
Definition acutest.h:576
int const char * file
Definition acutest.h:702
va_list args
Definition acutest.h:770
int const char int line
Definition acutest.h:702
size_t default_message_size
Usually maximum message size.
Definition app_io.h:39
Public structure describing an I/O path for a protocol.
Definition app_io.h:33
fr_atomic_queue_t * fr_atomic_queue_alloc(TALLOC_CTX *ctx, size_t size)
Create fixed-size atomic queue.
Structure to hold the atomic queue.
static TALLOC_CTX * autofree
Definition fuzzer.c:44
#define RCSID(id)
Definition build.h:506
#define NEVER_RETURNS
Should be placed before the function return type.
Definition build.h:334
#define UNUSED
Definition build.h:336
bool fr_channel_recv_reply(fr_channel_t *ch)
Receive a reply message from the channel.
Definition channel.c:406
int fr_channel_signal_responder_close(fr_channel_t *ch)
Signal a responder that the channel is closing.
Definition channel.c:822
int fr_channel_send_request(fr_channel_t *ch, fr_channel_data_t *cd)
Send a request message into the channel.
Definition channel.c:304
fr_channel_event_t fr_channel_service_message(fr_time_t when, fr_channel_t **p_channel, void const *data, size_t data_size)
Service a control-plane message.
Definition channel.c:683
int fr_channel_service_kevent(fr_channel_t *ch, fr_control_t *c, UNUSED struct kevent const *kev)
Service a control-plane event.
Definition channel.c:786
A full channel, which consists of two ends.
Definition channel.c:142
fr_message_t m
the message header
Definition channel.h:107
fr_channel_event_t
Definition channel.h:69
@ FR_CHANNEL_NOOP
Definition channel.h:76
@ FR_CHANNEL_CLOSE
Definition channel.h:74
@ FR_CHANNEL_DATA_READY_REQUESTOR
Definition channel.h:72
fr_listen_t * listen
for tracking packet transport, etc.
Definition channel.h:148
#define FR_CONTROL_ID_CHANNEL
Definition channel.h:67
uint32_t priority
Priority of this packet.
Definition channel.h:142
Channel information which is added to a message.
Definition channel.h:106
#define MEM(x)
Definition debug.h:46
#define fr_exit_now(_x)
Exit without calling atexit() handlers, producing a log message in debug builds.
Definition debug.h:236
talloc_free(hp)
fr_control_t * fr_control_create(TALLOC_CTX *ctx, fr_event_list_t *el, fr_atomic_queue_t *aq, size_t num_callbacks)
Create a control-plane signaling path.
Definition control.c:152
ssize_t fr_control_message_pop(fr_atomic_queue_t *aq, uint32_t *p_id, void *data, size_t data_size)
Pop control-plane message.
Definition control.c:403
The control structure.
Definition control.c:76
fr_app_io_t const * app_io
I/O path functions.
Definition listen.h:32
#define fr_time()
Definition event.c:60
fr_event_list_t * fr_event_list_alloc(TALLOC_CTX *ctx, fr_event_status_cb_t status, void *status_uctx)
Initialise a new event list.
Definition event.c:2506
Stores all information relating to an event list.
Definition event.c:377
int fr_log_init_legacy(fr_log_t *log, bool daemonize)
Initialise file descriptors based on logging destination.
Definition log.c:901
fr_log_t default_log
Definition log.c:288
@ L_DBG_LVL_MAX
Lowest priority debug messages (-xxxxx | -Xxxx).
Definition log.h:71
unsigned int uint32_t
long int ssize_t
unsigned char uint8_t
fr_message_set_t * fr_message_set_create(TALLOC_CTX *ctx, int num_messages, size_t message_size, size_t ring_buffer_size, bool unlimited_size)
Create a message set.
Definition message.c:127
int fr_message_done(fr_message_t *m)
Mark a message as done.
Definition message.c:195
fr_message_t * fr_message_alloc(fr_message_set_t *ms, fr_message_t *m, size_t actual_packet_size)
Allocate packet data for a message.
Definition message.c:1000
int fr_message_set_messages_used(fr_message_set_t *ms)
Count the number of used messages.
Definition message.c:1224
void fr_message_set_gc(fr_message_set_t *ms)
Garbage collect the message set.
Definition message.c:1250
void fr_message_set_debug(FILE *fp, fr_message_set_t *ms)
Print debug information about the message set.
Definition message.c:1274
A Message set, composed of message headers and ring buffer data.
Definition message.c:94
fr_time_t when
when this message was sent
Definition message.h:47
uint8_t * data
pointer to the data in the ring buffer
Definition message.h:49
size_t data_size
size of the data in the ring buffer
Definition message.h:50
#define fr_assert(_expr)
Definition rad_assert.h:37
static fr_event_list_t * events
Definition radsniff.c:58
#define RETURN_UNLANG_OK
Definition rcode.h:64
rlm_rcode_t
Return codes indicating the result of the module call.
Definition rcode.h:44
fr_packet_t * reply
Outgoing response.
Definition request.h:252
char const *fr_packet_t * packet
< Module the request is currently being processed by.
Definition request.h:251
rlm_rcode_t rcode
Last rcode returned by a module.
Definition request.h:260
uint64_t number
Monotonically increasing request number. Reset on server restart.
Definition request.h:206
Optional arguments for initialising requests.
Definition request.h:287
Signals that can be sent to a request.
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition snprintf.c:689
eap_aka_sim_process_conf_t * inst
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition syserror.c:243
static TALLOC_CTX * talloc_init_const(char const *name)
Allocate a top level chunk with a constant name.
Definition talloc.h:120
#define talloc_autofree_context
The original function is deprecated, so replace it with our version.
Definition talloc.h:48
int fr_time_start(void)
Initialize the local time.
Definition time.c:150
#define NSEC
Definition time.h:379
"server local" time.
Definition time.h:69
static fr_event_list_t * el
static fr_slen_t data
Definition value.h:1340
fr_channel_t * fr_worker_channel_create(fr_worker_t *worker, TALLOC_CTX *ctx, fr_control_t *master)
Create a channel to the worker.
Definition worker.c:1620
void fr_worker(fr_worker_t *worker)
The main loop and entry point of the stand-alone worker thread.
Definition worker.c:1502
void fr_worker_debug(fr_worker_t *worker, FILE *fp)
Print debug information about the worker structure.
Definition worker.c:1593
A worker which takes packets from a master, and processes them.
Definition worker.c:90
static int max_control_plane
Definition worker_test.c:58
int id
ID of the worker 0..N.
fr_channel_t * ch
channel for communicating with the worker
int main(int argc, char *argv[])
#define MAX_KEVENTS
Definition worker_test.c:40
static void * worker_thread(void *arg)
#define MAX_MESSAGES
Definition worker_test.c:38
pthread_t pthread_id
pthread ID of the worker
static fr_schedule_worker_t workers[MAX_WORKERS]
Definition worker_test.c:63
static bool touch_memory
Definition worker_test.c:60
static int test_decode(UNUSED void const *instance, request_t *request, uint8_t *const data, size_t data_len)
Definition worker_test.c:99
static fr_atomic_queue_t * aq_master
Definition worker_test.c:55
static size_t test_nak(UNUSED void const *instance, void *packet_ctx, uint8_t *const packet, size_t packet_len, uint8_t *reply, UNUSED size_t reply_len)
#define MAX_CONTROL_PLANE
Definition worker_test.c:39
request_t * request_alloc(UNUSED TALLOC_CTX *ctx, UNUSED request_init_args_t const *args)
Definition worker_test.c:68
#define MAX_WORKERS
Definition worker_test.c:41
void request_verify(UNUSED char const *file, UNUSED int line, UNUSED request_t const *request)
Definition worker_test.c:73
#define MPRINT2
Definition worker_test.c:44
static void master_process(void)
static int max_outstanding
Definition worker_test.c:59
#define MPRINT1
Definition worker_test.c:43
static fr_control_t * control_master
Definition worker_test.c:56
static int kq_master
Definition worker_test.c:54
static void sig_ignore(int sig)
static bool quiet
Definition worker_test.c:62
static ssize_t test_encode(void const *instance, request_t *request, uint8_t *const data, size_t data_len)
static fr_app_io_t app_io
static int num_workers
Definition worker_test.c:61
static rlm_rcode_t test_process(UNUSED void const *inst, request_t *request, fr_io_action_t action)
Definition worker_test.c:93
static int max_messages
Definition worker_test.c:57
static NEVER_RETURNS void usage(void)
Definition worker_test.c:79
fr_worker_t * worker
the worker data structure
Definition schedule.c:54
static int debug_lvl
Definition worker_test.c:53
Scheduler specific information for worker threads.
Definition schedule.c:46