• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

saitoha / libsixel / 20283335538

16 Dec 2025 09:29PM UTC coverage: 51.434% (+0.9%) from 50.524%
20283335538

push

github

saitoha
fix: define _DRAGONFLY_SOURCE on dragonflybsd before incluing sys/sysctl.h

14484 of 44869 branches covered (32.28%)

21058 of 40942 relevant lines covered (51.43%)

3940316.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.21
/src/threading.c
1
/*
2
 * SPDX-License-Identifier: MIT
3
 *
4
 * Copyright (c) 2025 libsixel developers. See `AUTHORS`.
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a
7
 * copy of this software and associated documentation files
8
 * (the "Software"), to deal in the Software without restriction,
9
 * including without limitation the rights to use, copy, modify, merge,
10
 * publish, distribute, sublicense, and/or sell copies of the Software,
11
 * and to permit persons to whom the
12
 * Software is furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included
15
 * in all copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
 * SOFTWARE.
24
 */
25

26
#include "config.h"
27

28
#if defined(__linux__) && !defined(_GNU_SOURCE)
29
# define _GNU_SOURCE
30
#endif
31

32
/*
33
 * OpenBSD hides BSD typedefs such as u_long when _POSIX_C_SOURCE is defined.
34
 * Enable the BSD namespace locally so sys/sysctl.h exposes the kernel
35
 * structures needed for hardware concurrency detection.
36
 */
37
#if defined(__OpenBSD__)
38
# define _BSD_SOURCE
39
#endif
40

41
/*
42
 * NetBSD also conceals legacy typedefs like devmajor_t and u_int under
43
 * strict POSIX feature sets. Force the wider namespace so sys/sysctl.h can
44
 * provide the structures required by our concurrency probing helpers.
45
 */
46
#if defined(__NetBSD__)
47
# define _NETBSD_SOURCE
48
#endif
49

50
/*
51
 * for including sys/sysctl.h on DragonflyBSD
52
 */
53
#if defined(__DragonFly__)
54
# define _DRAGONFLY_SOURCE
55
#endif
56

57
#if defined(__APPLE__) && !defined(_DARWIN_C_SOURCE)
58
/*
59
 * Expose BSD-flavoured typedefs such as u_int from the macOS SDK when the
60
 * build defines _POSIX_C_SOURCE. The platform headers hide these legacy names
61
 * otherwise, and sys/sysctl.h requires them for data structures like
62
 * struct kinfo_proc.
63
 */
64
# define _DARWIN_C_SOURCE
65
#endif
66

67
#include <stdio.h>
68
#include <stdlib.h>
69
#include <string.h>
70

71
#if HAVE_ERRNO_H
72
# include <errno.h>
73
#endif
74
#if HAVE_LIMITS_H
75
# include <limits.h>
76
#endif
77
#if HAVE_UNISTD_H
78
# include <unistd.h>
79
#endif
80
#if HAVE_SYS_TYPES_H
81
# include <sys/types.h>
82
#endif
83
#if HAVE_SYS_SYSCTL_H
84
# include <sys/sysctl.h>
85
#endif
86

87
#include "compat_stub.h"
88
#include "threading.h"
89

90
/*
91
 * Backend selection is performed here so the header remains lightweight.
92
 * WITH_WINPTHREAD forces the pthread path even on Windows to honor
93
 * user-provided configuration switches.
94
 */
95
#if defined(WITH_WINPTHREAD) && WITH_WINPTHREAD
96
# define SIXEL_USE_PTHREADS 1
97
# define SIXEL_USE_WIN32_THREADS 0
98
#elif defined(_WIN32) && !defined(__CYGWIN__) && !defined(__MSYS__)
99
# define SIXEL_USE_PTHREADS 0
100
# define SIXEL_USE_WIN32_THREADS 1
101
#elif SIXEL_ENABLE_THREADS
102
# define SIXEL_USE_PTHREADS 1
103
# define SIXEL_USE_WIN32_THREADS 0
104
#else
105
# define SIXEL_USE_PTHREADS 0
106
# define SIXEL_USE_WIN32_THREADS 0
107
#endif
108

109
#if SIXEL_USE_WIN32_THREADS
110
# include <windows.h>
111
# include <process.h>
112
#endif
113

114
/*
115
 * Provide a thin portability layer for synchronization primitives so the
116
 * encoder can run on POSIX and Windows platforms without altering the public
117
 * API surface.
118
 */
119

120
#if SIXEL_USE_PTHREADS
121
# include <sched.h>
122

123
/*
124
 * Abort the process when a pthread call fails in a context where recovery is
125
 * impossible. Encoding without locking guarantees would corrupt state, so we
126
 * surface an explicit diagnostic before terminating.
127
 */
128
static void
129
sixel_pthread_abort(const char *what, int error)
130
{
131
    fprintf(stderr, "libsixel: %s failed: %d\n", what, error);
132
    abort();
133
}
134

135
/*
136
 * Entry point passed to pthread_create. It forwards execution to the user
137
 * supplied callback and stores the integer status for later inspection.
138
 */
139
static void *
140
sixel_thread_trampoline(void *arg)
447✔
141
{
142
    sixel_thread_t *thread;
447✔
143

144
    thread = (sixel_thread_t *)arg;
447✔
145
    thread->result = thread->fn(thread->arg);
447✔
146
    return NULL;
447✔
147
}
77✔
148

149
SIXELAPI int
150
sixel_mutex_init(sixel_mutex_t *mutex)
914✔
151
{
152
    int rc;
914✔
153

154
    if (mutex == NULL) {
914!
155
        return SIXEL_BAD_ARGUMENT;
156
    }
157
    /*
158
     * Default attributes already provide a non-recursive mutex, which is
159
     * sufficient for the encoder's synchronization requirements.
160
     */
161
    rc = pthread_mutex_init(&mutex->native, NULL);
914✔
162
    if (rc != 0) {
914!
163
        errno = rc;
164
        return SIXEL_RUNTIME_ERROR;
165
    }
166
    return SIXEL_OK;
274✔
167
}
274✔
168

169
SIXELAPI void
170
sixel_mutex_destroy(sixel_mutex_t *mutex)
905✔
171
{
172
    int rc;
905✔
173

174
    if (mutex == NULL) {
905!
175
        return;
176
    }
177
    rc = pthread_mutex_destroy(&mutex->native);
905✔
178
    if (rc != 0) {
905!
179
        sixel_pthread_abort("pthread_mutex_destroy", rc);
180
    }
181
}
265!
182

183
SIXELAPI void
184
sixel_mutex_lock(sixel_mutex_t *mutex)
27,805✔
185
{
186
    int rc;
27,805✔
187

188
    if (mutex == NULL) {
27,805!
189
        return;
190
    }
191
    rc = pthread_mutex_lock(&mutex->native);
27,805✔
192
    if (rc != 0) {
27,805!
193
        sixel_pthread_abort("pthread_mutex_lock", rc);
194
    }
195
}
6,673!
196

197
SIXELAPI void
198
sixel_mutex_unlock(sixel_mutex_t *mutex)
27,805✔
199
{
200
    int rc;
27,805✔
201

202
    if (mutex == NULL) {
27,805!
203
        return;
204
    }
205
    rc = pthread_mutex_unlock(&mutex->native);
27,805✔
206
    if (rc != 0) {
27,805!
207
        sixel_pthread_abort("pthread_mutex_unlock", rc);
208
    }
209
}
6,673!
210

211
SIXELAPI int
212
sixel_cond_init(sixel_cond_t *cond)
391✔
213
{
214
    int rc;
391✔
215

216
    if (cond == NULL) {
391!
217
        return SIXEL_BAD_ARGUMENT;
218
    }
219
    /*
220
     * Conditions wake waiters in FIFO order per pthreads documentation. No
221
     * custom attributes are needed for the thread pool queue.
222
     */
223
    rc = pthread_cond_init(&cond->native, NULL);
391✔
224
    if (rc != 0) {
391!
225
        errno = rc;
226
        return SIXEL_RUNTIME_ERROR;
227
    }
228
    return SIXEL_OK;
70✔
229
}
70✔
230

231
SIXELAPI void
232
sixel_cond_destroy(sixel_cond_t *cond)
391✔
233
{
234
    int rc;
391✔
235

236
    if (cond == NULL) {
391!
237
        return;
238
    }
239
    rc = pthread_cond_destroy(&cond->native);
391✔
240
    if (rc != 0) {
391!
241
        sixel_pthread_abort("pthread_cond_destroy", rc);
242
    }
243
}
70!
244

245
SIXELAPI void
246
sixel_cond_wait(sixel_cond_t *cond, sixel_mutex_t *mutex)
6,717✔
247
{
248
    int rc;
6,717✔
249

250
    if (cond == NULL || mutex == NULL) {
6,717!
251
        return;
252
    }
253
    rc = pthread_cond_wait(&cond->native, &mutex->native);
6,717✔
254
    if (rc != 0) {
6,717!
255
        sixel_pthread_abort("pthread_cond_wait", rc);
256
    }
257
}
1,197!
258

259
SIXELAPI void
260
sixel_cond_signal(sixel_cond_t *cond)
8,870✔
261
{
262
    int rc;
8,870✔
263

264
    if (cond == NULL) {
8,870!
265
        return;
266
    }
267
    rc = pthread_cond_signal(&cond->native);
8,870✔
268
    if (rc != 0) {
8,870!
269
        sixel_pthread_abort("pthread_cond_signal", rc);
270
    }
271
}
2,078!
272

273
SIXELAPI void
274
sixel_cond_broadcast(sixel_cond_t *cond)
5,586✔
275
{
276
    int rc;
5,586✔
277

278
    if (cond == NULL) {
5,586!
279
        return;
280
    }
281
    rc = pthread_cond_broadcast(&cond->native);
5,586✔
282
    if (rc != 0) {
5,586!
283
        sixel_pthread_abort("pthread_cond_broadcast", rc);
284
    }
285
}
1,298!
286

287
SIXELAPI int
288
sixel_thread_create(sixel_thread_t *thread, sixel_thread_fn fn, void *arg)
447✔
289
{
290
    int rc;
447✔
291

292
    if (thread == NULL || fn == NULL) {
447!
293
        return SIXEL_BAD_ARGUMENT;
294
    }
295
    /*
296
     * Store context before launching so the trampoline can record the
297
     * callback result inside the same structure without extra allocations.
298
     */
299
    thread->fn = fn;
447✔
300
    thread->arg = arg;
447✔
301
    thread->result = SIXEL_OK;
447✔
302
    thread->started = 0;
447✔
303
    rc = pthread_create(&thread->handle, NULL, sixel_thread_trampoline,
524✔
304
                        thread);
77✔
305
    if (rc != 0) {
447!
306
        errno = rc;
307
        return SIXEL_RUNTIME_ERROR;
308
    }
309
    thread->started = 1;
447✔
310
    return SIXEL_OK;
447✔
311
}
77✔
312

313
SIXELAPI void
314
sixel_thread_join(sixel_thread_t *thread)
447✔
315
{
316
    int rc;
447✔
317

318
    if (thread == NULL || !thread->started) {
447!
319
        return;
320
    }
321
    rc = pthread_join(thread->handle, NULL);
447✔
322
    if (rc != 0) {
447!
323
        sixel_pthread_abort("pthread_join", rc);
324
    }
325
    thread->started = 0;
447✔
326
}
77!
327

328
/*
329
 * Best-effort CPU affinity setter for the calling thread. Platforms without
330
 * pthread_setaffinity_np simply ignore the request so callers can use the
331
 * helper unconditionally.
332
 */
333
SIXELAPI int
334
sixel_thread_pin_self(int cpu_index)
335
{
336
#if defined(__linux__) && defined(CPU_SET)
337
    cpu_set_t set;
338
    int rc;
339

340
    if (cpu_index < 0) {
×
341
        return SIXEL_BAD_ARGUMENT;
342
    }
343
    CPU_ZERO(&set);
344
    CPU_SET((unsigned int)cpu_index, &set);
×
345
    rc = pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
346
    if (rc != 0) {
×
347
        return SIXEL_RUNTIME_ERROR;
348
    }
349

350
    return SIXEL_OK;
351
#else
352
    (void)cpu_index;
353

354
    return SIXEL_OK;
355
#endif
356
}
357

358
SIXELAPI int
359
sixel_get_hw_threads(void)
103✔
360
{
361
#if defined(_SC_NPROCESSORS_ONLN)
362
    long count;
103✔
363

364
    count = sysconf(_SC_NPROCESSORS_ONLN);
103✔
365
    if (count > 0 && count <= (long)INT_MAX) {
103!
366
        return (int)count;
103✔
367
    }
368
#endif
369
#if defined(__APPLE__)
370
    {
371
        int mib[2];
372
        size_t size;
373
        int value;
374

375
        mib[0] = CTL_HW;
376
        mib[1] = HW_AVAILCPU;
377
        size = sizeof(value);
378
        if (sysctl(mib, 2, &value, &size, NULL, 0) == 0 && value > 0) {
×
379
            return value;
380
        }
381
        mib[1] = HW_NCPU;
382
        size = sizeof(value);
383
        if (sysctl(mib, 2, &value, &size, NULL, 0) == 0 && value > 0) {
×
384
            return value;
385
        }
386
    }
×
387
#endif
388
    return 1;
389
}
25✔
390

391
#elif SIXEL_USE_WIN32_THREADS
392

393
/*
394
 * Abort execution on unrecoverable Win32 API failures to mirror pthread path
395
 * semantics. Printing the failing call and error code helps debugging in
396
 * environments where stderr is available.
397
 */
398
static void
399
sixel_win32_abort(const char *what, DWORD error)
400
{
401
    fprintf(stderr, "libsixel: %s failed: %lu\n", what,
402
            (unsigned long)error);
403
    abort();
404
}
405

406
/*
407
 * Trampoline for _beginthreadex. It records the callback result back into the
408
 * owning sixel_thread_t structure so the caller can retrieve it after join.
409
 */
410
static unsigned __stdcall
411
sixel_win32_thread_start(void *arg)
412
{
413
    sixel_thread_t *thread;
414

415
    thread = (sixel_thread_t *)arg;
416
    thread->result = thread->fn(thread->arg);
417
    return 0;
418
}
419

420
SIXELAPI int
421
sixel_mutex_init(sixel_mutex_t *mutex)
422
{
423
    if (mutex == NULL) {
424
        return SIXEL_BAD_ARGUMENT;
425
    }
426
    InitializeCriticalSection(&mutex->native);
427
    return SIXEL_OK;
428
}
429

430
SIXELAPI void
431
sixel_mutex_destroy(sixel_mutex_t *mutex)
432
{
433
    if (mutex == NULL) {
434
        return;
435
    }
436
    DeleteCriticalSection(&mutex->native);
437
}
438

439
SIXELAPI void
440
sixel_mutex_lock(sixel_mutex_t *mutex)
441
{
442
    if (mutex == NULL) {
443
        return;
444
    }
445
    EnterCriticalSection(&mutex->native);
446
}
447

448
SIXELAPI void
449
sixel_mutex_unlock(sixel_mutex_t *mutex)
450
{
451
    if (mutex == NULL) {
452
        return;
453
    }
454
    LeaveCriticalSection(&mutex->native);
455
}
456

457
SIXELAPI int
458
sixel_cond_init(sixel_cond_t *cond)
459
{
460
    if (cond == NULL) {
461
        return SIXEL_BAD_ARGUMENT;
462
    }
463
    InitializeConditionVariable(&cond->native);
464
    return SIXEL_OK;
465
}
466

467
SIXELAPI void
468
sixel_cond_destroy(sixel_cond_t *cond)
469
{
470
    /* CONDITION_VARIABLE does not need explicit teardown. */
471
    (void)cond;
472
}
473

474
SIXELAPI void
475
sixel_cond_wait(sixel_cond_t *cond, sixel_mutex_t *mutex)
476
{
477
    BOOL rc;
478
    DWORD error;
479

480
    if (cond == NULL || mutex == NULL) {
481
        return;
482
    }
483
    rc = SleepConditionVariableCS(&cond->native, &mutex->native, INFINITE);
484
    if (rc == 0) {
485
        error = GetLastError();
486
        sixel_win32_abort("SleepConditionVariableCS", error);
487
    }
488
}
489

490
SIXELAPI void
491
sixel_cond_signal(sixel_cond_t *cond)
492
{
493
    if (cond == NULL) {
494
        return;
495
    }
496
    WakeConditionVariable(&cond->native);
497
}
498

499
SIXELAPI void
500
sixel_cond_broadcast(sixel_cond_t *cond)
501
{
502
    if (cond == NULL) {
503
        return;
504
    }
505
    WakeAllConditionVariable(&cond->native);
506
}
507

508
SIXELAPI int
509
sixel_thread_create(sixel_thread_t *thread, sixel_thread_fn fn, void *arg)
510
{
511
    uintptr_t handle;
512

513
    if (thread == NULL || fn == NULL) {
514
        return SIXEL_BAD_ARGUMENT;
515
    }
516
    thread->fn = fn;
517
    thread->arg = arg;
518
    thread->result = SIXEL_OK;
519
    thread->started = 0;
520
    handle = _beginthreadex(NULL, 0, sixel_win32_thread_start, thread, 0,
521
                            NULL);
522
    if (handle == 0) {
523
        return SIXEL_RUNTIME_ERROR;
524
    }
525
    thread->handle = (HANDLE)handle;
526
    thread->started = 1;
527
    return SIXEL_OK;
528
}
529

530
SIXELAPI void
531
sixel_thread_join(sixel_thread_t *thread)
532
{
533
    DWORD rc;
534
    DWORD error;
535

536
    if (thread == NULL || !thread->started) {
537
        return;
538
    }
539
    rc = WaitForSingleObject(thread->handle, INFINITE);
540
    if (rc != WAIT_OBJECT_0) {
541
        error = (rc == WAIT_FAILED) ? GetLastError() : rc;
542
        sixel_win32_abort("WaitForSingleObject", error);
543
    }
544
    CloseHandle(thread->handle);
545
    thread->handle = NULL;
546
    thread->started = 0;
547
}
548

549
SIXELAPI int
550
sixel_thread_pin_self(int cpu_index)
551
{
552
    DWORD_PTR mask;
553
    DWORD_PTR previous;
554

555
    if (cpu_index < 0) {
556
        return SIXEL_BAD_ARGUMENT;
557
    }
558
    mask = ((DWORD_PTR)1) << (DWORD_PTR)cpu_index;
559
    previous = SetThreadAffinityMask(GetCurrentThread(), mask);
560
    if (previous == 0) {
561
        return SIXEL_RUNTIME_ERROR;
562
    }
563

564
    return SIXEL_OK;
565
}
566

567
SIXELAPI int
568
sixel_get_hw_threads(void)
569
{
570
    DWORD count;
571
    SYSTEM_INFO info;
572

573
    count = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
574
    if (count == 0) {
575
        GetSystemInfo(&info);
576
        count = info.dwNumberOfProcessors;
577
    }
578
    if (count == 0) {
579
        count = 1;
580
    }
581
    return (int)count;
582
}
583

584
#else
585
/*
586
 * Thread support is disabled. Provide stub implementations so callers that
587
 * inadvertently use the API receive a deterministic failure rather than a
588
 * linker error. Mutex and condition helpers become no-ops while creation
589
 * attempts return an explicit runtime error.
590
 */
591
SIXELAPI int
592
sixel_mutex_init(sixel_mutex_t *mutex)
162✔
593
{
594
    (void)mutex;
162✔
595
    return SIXEL_RUNTIME_ERROR;
162✔
596
}
597

598
SIXELAPI void
599
sixel_mutex_destroy(sixel_mutex_t *mutex)
600
{
601
    (void)mutex;
602
}
603

604
SIXELAPI void
605
sixel_mutex_lock(sixel_mutex_t *mutex)
606
{
607
    (void)mutex;
608
}
609

610
SIXELAPI void
611
sixel_mutex_unlock(sixel_mutex_t *mutex)
612
{
613
    (void)mutex;
614
}
615

616
SIXELAPI int
617
sixel_cond_init(sixel_cond_t *cond)
618
{
619
    (void)cond;
620
    return SIXEL_RUNTIME_ERROR;
621
}
622

623
SIXELAPI void
624
sixel_cond_destroy(sixel_cond_t *cond)
625
{
626
    (void)cond;
627
}
628

629
SIXELAPI void
630
sixel_cond_wait(sixel_cond_t *cond, sixel_mutex_t *mutex)
631
{
632
    (void)cond;
633
    (void)mutex;
634
}
635

636
SIXELAPI void
637
sixel_cond_signal(sixel_cond_t *cond)
638
{
639
    (void)cond;
640
}
641

642
SIXELAPI void
643
sixel_cond_broadcast(sixel_cond_t *cond)
644
{
645
    (void)cond;
646
}
647

648
SIXELAPI int
649
sixel_thread_create(sixel_thread_t *thread, sixel_thread_fn fn, void *arg)
650
{
651
    (void)thread;
652
    (void)fn;
653
    (void)arg;
654
    return SIXEL_RUNTIME_ERROR;
655
}
656

657
SIXELAPI void
658
sixel_thread_join(sixel_thread_t *thread)
659
{
660
    (void)thread;
661
}
662

663
SIXELAPI int
664
sixel_thread_pin_self(int cpu_index)
665
{
666
    (void)cpu_index;
667

668
    return SIXEL_OK;
669
}
670

671
SIXELAPI int
672
sixel_get_hw_threads(void)
673
{
674
    return 1;
675
}
676

677
#endif /* SIXEL_USE_PTHREADS */
678

679
/*
680
 * Thread configuration keeps the precedence rules centralized:
681
 *   1. Library callers may override via `sixel_set_threads`.
682
 *   2. Otherwise, `SIXEL_THREADS` from the environment is honored.
683
 *   3. Fallback defaults to single threaded execution.
684
 */
685
typedef struct sixel_thread_config_state {
686
    int requested_threads;
687
    int override_active;
688
    int env_threads;
689
    int env_valid;
690
    int env_checked;
691
} sixel_thread_config_state_t;
692

693
static sixel_thread_config_state_t g_thread_config = {
694
    1,
695
    0,
696
    1,
697
    0,
698
    0
699
};
700

701
static int
702
sixel_threads_token_is_auto(char const *text)
796✔
703
{
704
    if (text == NULL) {
796!
705
        return 0;
706
    }
707

708
    if ((text[0] == 'a' || text[0] == 'A') &&
796!
709
        (text[1] == 'u' || text[1] == 'U') &&
×
710
        (text[2] == 't' || text[2] == 'T') &&
×
711
        (text[3] == 'o' || text[3] == 'O') &&
×
712
        text[4] == '\0') {
×
713
        return 1;
×
714
    }
715

716
    return 0;
212✔
717
}
212✔
718

719
SIXELAPI int
720
sixel_threads_normalize(int requested)
754✔
721
{
722
    int normalized;
754✔
723

724
#if SIXEL_ENABLE_THREADS
725
    int hw_threads;
607✔
726

727
    if (requested <= 0) {
604!
728
        hw_threads = sixel_get_hw_threads();
103✔
729
        if (hw_threads < 1) {
100!
730
            hw_threads = 1;
731
        }
732
        normalized = hw_threads;
25✔
733
    } else {
25✔
734
        normalized = requested;
212✔
735
    }
736

737
    if (normalized < 1) {
237!
738
        normalized = 1;
739
    }
740
#else
741
    (void)requested;
147✔
742
    normalized = 1;
147✔
743
#endif
744

745
    return normalized;
991✔
746
}
237✔
747

748
static int
749
sixel_threads_parse_env_value(char const *text, int *value)
796✔
750
{
751
    long parsed;
796✔
752
    char *endptr;
796✔
753
    int normalized;
796✔
754

755
    if (text == NULL || value == NULL) {
796!
756
        return 0;
757
    }
758

759
    if (sixel_threads_token_is_auto(text)) {
796!
760
        normalized = sixel_threads_normalize(0);
×
761
        *value = normalized;
×
762
        return 1;
×
763
    }
764

765
    errno = 0;
796✔
766
    parsed = strtol(text, &endptr, 10);
796✔
767
    if (endptr == text || *endptr != '\0' || errno == ERANGE) {
796!
768
        return 0;
769
    }
770

771
    if (parsed < 1) {
796!
772
        normalized = sixel_threads_normalize(1);
773
    } else if (parsed > INT_MAX) {
796!
774
        normalized = sixel_threads_normalize(INT_MAX);
775
    } else {
776
        normalized = sixel_threads_normalize((int)parsed);
796✔
777
    }
778

779
    *value = normalized;
796✔
780
    return 1;
796✔
781
}
212✔
782

783
static void
784
sixel_threads_load_env(void)
4,776✔
785
{
786
    char const *text;
4,776✔
787
    int parsed;
4,776✔
788

789
    if (g_thread_config.env_checked) {
4,776✔
790
        return;
3,969✔
791
    }
792

793
    g_thread_config.env_checked = 1;
852✔
794
    g_thread_config.env_valid = 0;
852✔
795

796
    text = sixel_compat_getenv("SIXEL_THREADS");
852✔
797
    if (text == NULL || text[0] == '\0') {
852!
798
        return;
11✔
799
    }
800

801
    if (sixel_threads_parse_env_value(text, &parsed)) {
796!
802
        g_thread_config.env_threads = parsed;
796✔
803
        g_thread_config.env_valid = 1;
796✔
804
    }
212✔
805
}
1,109!
806

807
SIXELAPI int
808
sixel_threads_resolve(void)
4,776✔
809
{
810
    int resolved;
4,776✔
811

812
#if SIXEL_ENABLE_THREADS
813
    if (g_thread_config.override_active) {
3,875!
814
        return g_thread_config.requested_threads;
815
    }
816
#endif
817

818
    sixel_threads_load_env();
4,776✔
819

820
#if SIXEL_ENABLE_THREADS
821
    if (g_thread_config.env_valid) {
3,875✔
822
        resolved = g_thread_config.env_threads;
3,772✔
823
    } else {
1,084✔
824
        resolved = sixel_threads_normalize(0);
103✔
825
    }
826
#else
827
    resolved = 1;
901✔
828
#endif
829

830
    return resolved;
2,010✔
831
}
1,109✔
832

833
/*
834
 * Public setter so CLI/bindings may override the runtime thread preference.
835
 */
836
SIXELAPI void
837
sixel_set_threads(int threads)
×
838
{
839
#if SIXEL_ENABLE_THREADS
840
    g_thread_config.requested_threads = sixel_threads_normalize(threads);
×
841
#else
842
    (void)threads;
843
    g_thread_config.requested_threads = 1;
844
#endif
845
    g_thread_config.override_active = 1;
×
846
}
×
847

848
/* emacs Local Variables:      */
849
/* emacs mode: c               */
850
/* emacs tab-width: 4          */
851
/* emacs indent-tabs-mode: nil */
852
/* emacs c-basic-offset: 4     */
853
/* emacs End:                  */
854
/* vim: set expandtab ts=4 sts=4 sw=4 : */
855
/* EOF */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc