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

saitoha / libsixel / 20815644050

08 Jan 2026 11:42AM UTC coverage: 55.168% (-1.5%) from 56.702%
20815644050

push

github

saitoha
ci: refine

21172 of 38377 relevant lines covered (55.17%)

1148322.52 hits per line

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

79.56
/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
#if defined(HAVE_CONFIG_H)
27
#include "config.h"
28
#endif
29

30
#if defined(__linux__) && !defined(_GNU_SOURCE)
31
# define _GNU_SOURCE
32
#endif
33

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

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

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

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

69
#include <stdio.h>
70
#include <stdlib.h>
71
#include <string.h>
72

73
#if HAVE_ERRNO_H
74
# include <errno.h>
75
#endif
76
#if HAVE_LIMITS_H
77
# include <limits.h>
78
#endif
79
#if HAVE_UNISTD_H
80
# include <unistd.h>
81
#endif
82
#if HAVE_SYS_TYPES_H
83
# include <sys/types.h>
84
#endif
85
#if defined(__APPLE__)
86
# if HAVE_SYS_SYSCTL_H
87
#  include <sys/sysctl.h>
88
# endif
89
#endif
90

91
#include "compat_stub.h"
92
#include "threading.h"
93

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

113
#if SIXEL_USE_WIN32_THREADS
114
# if !defined(UNICODE)
115
#  define UNICODE
116
# endif
117
# if !defined(_UNICODE)
118
#  define _UNICODE
119
# endif
120
# if !defined(WIN32_LEAN_AND_MEAN)
121
#  define WIN32_LEAN_AND_MEAN
122
# endif
123
# include <windows.h>
124
# include <process.h>
125
#endif
126

127
/*
128
 * Provide a thin portability layer for synchronization primitives so the
129
 * encoder can run on POSIX and Windows platforms without altering the public
130
 * API surface.
131
 */
132

133
#if SIXEL_USE_PTHREADS
134
# include <sched.h>
135

136
/*
137
 * Abort the process when a pthread call fails in a context where recovery is
138
 * impossible. Encoding without locking guarantees would corrupt state, so we
139
 * surface an explicit diagnostic before terminating.
140
 */
141
static void
142
sixel_pthread_abort(const char *what, int error)
143
{
144
    fprintf(stderr, "libsixel: %s failed: %d\n", what, error);
145
    abort();
146
}
147

148
/*
149
 * Entry point passed to pthread_create. It forwards execution to the user
150
 * supplied callback and stores the integer status for later inspection.
151
 */
152
static void *
153
sixel_thread_trampoline(void *arg)
154
{
155
    sixel_thread_t *thread;
156

157
    thread = (sixel_thread_t *)arg;
158
    thread->result = thread->fn(thread->arg);
159
    return NULL;
160
}
161

162
SIXELAPI int
163
sixel_mutex_init(sixel_mutex_t *mutex)
164
{
165
    int rc;
166

167
    if (mutex == NULL) {
168
        return SIXEL_BAD_ARGUMENT;
169
    }
170
    /*
171
     * Default attributes already provide a non-recursive mutex, which is
172
     * sufficient for the encoder's synchronization requirements.
173
     */
174
    rc = pthread_mutex_init(&mutex->native, NULL);
175
    if (rc != 0) {
176
        errno = rc;
177
        return SIXEL_RUNTIME_ERROR;
178
    }
179
    return SIXEL_OK;
180
}
181

182
SIXELAPI void
183
sixel_mutex_destroy(sixel_mutex_t *mutex)
184
{
185
    int rc;
186

187
    if (mutex == NULL) {
188
        return;
189
    }
190
    rc = pthread_mutex_destroy(&mutex->native);
191
    if (rc != 0) {
192
        sixel_pthread_abort("pthread_mutex_destroy", rc);
193
    }
194
}
195

196
SIXELAPI void
197
sixel_mutex_lock(sixel_mutex_t *mutex)
198
{
199
    int rc;
200

201
    if (mutex == NULL) {
202
        return;
203
    }
204
    rc = pthread_mutex_lock(&mutex->native);
205
    if (rc != 0) {
206
        sixel_pthread_abort("pthread_mutex_lock", rc);
207
    }
208
}
209

210
SIXELAPI void
211
sixel_mutex_unlock(sixel_mutex_t *mutex)
212
{
213
    int rc;
214

215
    if (mutex == NULL) {
216
        return;
217
    }
218
    rc = pthread_mutex_unlock(&mutex->native);
219
    if (rc != 0) {
220
        sixel_pthread_abort("pthread_mutex_unlock", rc);
221
    }
222
}
223

224
SIXELAPI int
225
sixel_cond_init(sixel_cond_t *cond)
226
{
227
    int rc;
228

229
    if (cond == NULL) {
230
        return SIXEL_BAD_ARGUMENT;
231
    }
232
    /*
233
     * Conditions wake waiters in FIFO order per pthreads documentation. No
234
     * custom attributes are needed for the thread pool queue.
235
     */
236
    rc = pthread_cond_init(&cond->native, NULL);
237
    if (rc != 0) {
238
        errno = rc;
239
        return SIXEL_RUNTIME_ERROR;
240
    }
241
    return SIXEL_OK;
242
}
243

244
SIXELAPI void
245
sixel_cond_destroy(sixel_cond_t *cond)
246
{
247
    int rc;
248

249
    if (cond == NULL) {
250
        return;
251
    }
252
    rc = pthread_cond_destroy(&cond->native);
253
    if (rc != 0) {
254
        sixel_pthread_abort("pthread_cond_destroy", rc);
255
    }
256
}
257

258
SIXELAPI void
259
sixel_cond_wait(sixel_cond_t *cond, sixel_mutex_t *mutex)
260
{
261
    int rc;
262

263
    if (cond == NULL || mutex == NULL) {
264
        return;
265
    }
266
    rc = pthread_cond_wait(&cond->native, &mutex->native);
267
    if (rc != 0) {
268
        sixel_pthread_abort("pthread_cond_wait", rc);
269
    }
270
}
271

272
SIXELAPI void
273
sixel_cond_signal(sixel_cond_t *cond)
274
{
275
    int rc;
276

277
    if (cond == NULL) {
278
        return;
279
    }
280
    rc = pthread_cond_signal(&cond->native);
281
    if (rc != 0) {
282
        sixel_pthread_abort("pthread_cond_signal", rc);
283
    }
284
}
285

286
SIXELAPI void
287
sixel_cond_broadcast(sixel_cond_t *cond)
288
{
289
    int rc;
290

291
    if (cond == NULL) {
292
        return;
293
    }
294
    rc = pthread_cond_broadcast(&cond->native);
295
    if (rc != 0) {
296
        sixel_pthread_abort("pthread_cond_broadcast", rc);
297
    }
298
}
299

300
SIXELAPI int
301
sixel_thread_create(sixel_thread_t *thread, sixel_thread_fn fn, void *arg)
302
{
303
    int rc;
304

305
    if (thread == NULL || fn == NULL) {
306
        return SIXEL_BAD_ARGUMENT;
307
    }
308
    /*
309
     * Store context before launching so the trampoline can record the
310
     * callback result inside the same structure without extra allocations.
311
     */
312
    thread->fn = fn;
313
    thread->arg = arg;
314
    thread->result = SIXEL_OK;
315
    thread->started = 0;
316
    rc = pthread_create(&thread->handle, NULL, sixel_thread_trampoline,
317
                        thread);
318
    if (rc != 0) {
319
        errno = rc;
320
        return SIXEL_RUNTIME_ERROR;
321
    }
322
    thread->started = 1;
323
    return SIXEL_OK;
324
}
325

326
SIXELAPI void
327
sixel_thread_join(sixel_thread_t *thread)
328
{
329
    int rc;
330

331
    if (thread == NULL || !thread->started) {
332
        return;
333
    }
334
    rc = pthread_join(thread->handle, NULL);
335
    if (rc != 0) {
336
        sixel_pthread_abort("pthread_join", rc);
337
    }
338
    thread->started = 0;
339
}
340

341
/*
342
 * Best-effort CPU affinity setter for the calling thread. Platforms without
343
 * pthread_setaffinity_np simply ignore the request so callers can use the
344
 * helper unconditionally.
345
 */
346
SIXELAPI int
347
sixel_thread_pin_self(int cpu_index)
348
{
349
#if defined(__linux__) && defined(CPU_SET)
350
    cpu_set_t set;
351
    int rc;
352

353
    if (cpu_index < 0) {
354
        return SIXEL_BAD_ARGUMENT;
355
    }
356
    CPU_ZERO(&set);
357
    CPU_SET((unsigned int)cpu_index, &set);
358
    rc = pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
359
    if (rc != 0) {
360
        return SIXEL_RUNTIME_ERROR;
361
    }
362

363
    return SIXEL_OK;
364
#else
365
    (void)cpu_index;
366

367
    return SIXEL_OK;
368
#endif
369
}
370

371
SIXELAPI int
372
sixel_get_hw_threads(void)
373
{
374
#if defined(_SC_NPROCESSORS_ONLN)
375
    long count;
376

377
    count = sysconf(_SC_NPROCESSORS_ONLN);
378
    if (count > 0 && count <= (long)INT_MAX) {
379
        return (int)count;
380
    }
381
#endif
382
#if defined(__APPLE__)
383
    {
384
        int mib[2];
385
        size_t size;
386
        int value;
387

388
        mib[0] = CTL_HW;
389
        mib[1] = HW_AVAILCPU;
390
        size = sizeof(value);
391
        if (sysctl(mib, 2, &value, &size, NULL, 0) == 0 && value > 0) {
392
            return value;
393
        }
394
        mib[1] = HW_NCPU;
395
        size = sizeof(value);
396
        if (sysctl(mib, 2, &value, &size, NULL, 0) == 0 && value > 0) {
397
            return value;
398
        }
399
    }
400
#endif
401
    return 1;
402
}
403

404
#elif SIXEL_USE_WIN32_THREADS
405

406
/*
407
 * Abort execution on unrecoverable Win32 API failures to mirror pthread path
408
 * semantics. Printing the failing call and error code helps debugging in
409
 * environments where stderr is available.
410
 */
411
static void
412
sixel_win32_abort(const char *what, DWORD error)
×
413
{
414
    fprintf(stderr, "libsixel: %s failed: %lu\n", what,
×
415
            (unsigned long)error);
416
    abort();
×
417
}
418

419
/*
420
 * Trampoline for _beginthreadex. It records the callback result back into the
421
 * owning sixel_thread_t structure so the caller can retrieve it after join.
422
 */
423
static unsigned __stdcall
424
sixel_win32_thread_start(void *arg)
152✔
425
{
426
    sixel_thread_t *thread;
152✔
427

428
    thread = (sixel_thread_t *)arg;
152✔
429
    thread->result = thread->fn(thread->arg);
152✔
430
    return 0;
152✔
431
}
432

433
SIXELAPI int
434
sixel_mutex_init(sixel_mutex_t *mutex)
175✔
435
{
436
    if (mutex == NULL) {
175✔
437
        return SIXEL_BAD_ARGUMENT;
438
    }
439
    InitializeCriticalSection(&mutex->native);
175✔
440
    return SIXEL_OK;
175✔
441
}
442

443
SIXELAPI void
444
sixel_mutex_destroy(sixel_mutex_t *mutex)
175✔
445
{
446
    if (mutex == NULL) {
175✔
447
        return;
448
    }
449
    DeleteCriticalSection(&mutex->native);
175✔
450
}
451

452
SIXELAPI void
453
sixel_mutex_lock(sixel_mutex_t *mutex)
7,128✔
454
{
455
    if (mutex == NULL) {
7,128✔
456
        return;
457
    }
458
    EnterCriticalSection(&mutex->native);
7,128✔
459
}
460

461
SIXELAPI void
462
sixel_mutex_unlock(sixel_mutex_t *mutex)
7,127✔
463
{
464
    if (mutex == NULL) {
7,127✔
465
        return;
466
    }
467
    LeaveCriticalSection(&mutex->native);
7,127✔
468
}
469

470
SIXELAPI int
471
sixel_cond_init(sixel_cond_t *cond)
130✔
472
{
473
    if (cond == NULL) {
130✔
474
        return SIXEL_BAD_ARGUMENT;
475
    }
476
    InitializeConditionVariable(&cond->native);
130✔
477
    return SIXEL_OK;
130✔
478
}
479

480
SIXELAPI void
481
sixel_cond_destroy(sixel_cond_t *cond)
130✔
482
{
483
    /* CONDITION_VARIABLE does not need explicit teardown. */
484
    (void)cond;
130✔
485
}
130✔
486

487
SIXELAPI void
488
sixel_cond_wait(sixel_cond_t *cond, sixel_mutex_t *mutex)
1,586✔
489
{
490
    BOOL rc;
1,586✔
491
    DWORD error;
1,586✔
492

493
    if (cond == NULL || mutex == NULL) {
1,586✔
494
        return;
495
    }
496
    rc = SleepConditionVariableCS(&cond->native, &mutex->native, INFINITE);
1,586✔
497
    if (rc == 0) {
1,586✔
498
        error = GetLastError();
×
499
        sixel_win32_abort("SleepConditionVariableCS", error);
×
500
    }
501
}
502

503
SIXELAPI void
504
sixel_cond_signal(sixel_cond_t *cond)
2,194✔
505
{
506
    if (cond == NULL) {
2,194✔
507
        return;
508
    }
509
    WakeConditionVariable(&cond->native);
2,194✔
510
}
511

512
SIXELAPI void
513
sixel_cond_broadcast(sixel_cond_t *cond)
1,256✔
514
{
515
    if (cond == NULL) {
1,256✔
516
        return;
517
    }
518
    WakeAllConditionVariable(&cond->native);
1,256✔
519
}
520

521
SIXELAPI int
522
sixel_thread_create(sixel_thread_t *thread, sixel_thread_fn fn, void *arg)
152✔
523
{
524
    uintptr_t handle;
152✔
525

526
    if (thread == NULL || fn == NULL) {
152✔
527
        return SIXEL_BAD_ARGUMENT;
528
    }
529
    thread->fn = fn;
152✔
530
    thread->arg = arg;
152✔
531
    thread->result = SIXEL_OK;
152✔
532
    thread->started = 0;
152✔
533
    handle = _beginthreadex(NULL, 0, sixel_win32_thread_start, thread, 0,
152✔
534
                            NULL);
535
    if (handle == 0) {
152✔
536
        return SIXEL_RUNTIME_ERROR;
537
    }
538
    thread->handle = (HANDLE)handle;
152✔
539
    thread->started = 1;
152✔
540
    return SIXEL_OK;
152✔
541
}
542

543
SIXELAPI void
544
sixel_thread_join(sixel_thread_t *thread)
152✔
545
{
546
    DWORD rc;
152✔
547
    DWORD error;
152✔
548

549
    if (thread == NULL || !thread->started) {
152✔
550
        return;
551
    }
552
    rc = WaitForSingleObject(thread->handle, INFINITE);
152✔
553
    if (rc != WAIT_OBJECT_0) {
152✔
554
        error = (rc == WAIT_FAILED) ? GetLastError() : rc;
×
555
        sixel_win32_abort("WaitForSingleObject", error);
×
556
    }
557
    CloseHandle(thread->handle);
152✔
558
    thread->handle = NULL;
152✔
559
    thread->started = 0;
152✔
560
}
561

562
SIXELAPI int
563
sixel_thread_pin_self(int cpu_index)
58✔
564
{
565
    DWORD_PTR mask;
58✔
566
    DWORD_PTR previous;
58✔
567

568
    if (cpu_index < 0) {
58✔
569
        return SIXEL_BAD_ARGUMENT;
570
    }
571
    mask = ((DWORD_PTR)1) << (DWORD_PTR)cpu_index;
58✔
572
    previous = SetThreadAffinityMask(GetCurrentThread(), mask);
58✔
573
    if (previous == 0) {
58✔
574
        return SIXEL_RUNTIME_ERROR;
×
575
    }
576

577
    return SIXEL_OK;
578
}
579

580
SIXELAPI int
581
sixel_get_hw_threads(void)
49✔
582
{
583
    DWORD count;
49✔
584
    SYSTEM_INFO info;
49✔
585

586
    count = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
49✔
587
    if (count == 0) {
49✔
588
        GetSystemInfo(&info);
×
589
        count = info.dwNumberOfProcessors;
×
590
    }
591
    if (count == 0) {
×
592
        count = 1;
593
    }
594
    return (int)count;
49✔
595
}
596

597
#else
598
/*
599
 * Thread support is disabled. Provide stub implementations so callers that
600
 * inadvertently use the API receive a deterministic failure rather than a
601
 * linker error. Mutex and condition helpers become no-ops while creation
602
 * attempts return an explicit runtime error.
603
 */
604
SIXELAPI int
605
sixel_mutex_init(sixel_mutex_t *mutex)
606
{
607
    (void)mutex;
608
    return SIXEL_RUNTIME_ERROR;
609
}
610

611
SIXELAPI void
612
sixel_mutex_destroy(sixel_mutex_t *mutex)
613
{
614
    (void)mutex;
615
}
616

617
SIXELAPI void
618
sixel_mutex_lock(sixel_mutex_t *mutex)
619
{
620
    (void)mutex;
621
}
622

623
SIXELAPI void
624
sixel_mutex_unlock(sixel_mutex_t *mutex)
625
{
626
    (void)mutex;
627
}
628

629
SIXELAPI int
630
sixel_cond_init(sixel_cond_t *cond)
631
{
632
    (void)cond;
633
    return SIXEL_RUNTIME_ERROR;
634
}
635

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

642
SIXELAPI void
643
sixel_cond_wait(sixel_cond_t *cond, sixel_mutex_t *mutex)
644
{
645
    (void)cond;
646
    (void)mutex;
647
}
648

649
SIXELAPI void
650
sixel_cond_signal(sixel_cond_t *cond)
651
{
652
    (void)cond;
653
}
654

655
SIXELAPI void
656
sixel_cond_broadcast(sixel_cond_t *cond)
657
{
658
    (void)cond;
659
}
660

661
SIXELAPI int
662
sixel_thread_create(sixel_thread_t *thread, sixel_thread_fn fn, void *arg)
663
{
664
    (void)thread;
665
    (void)fn;
666
    (void)arg;
667
    return SIXEL_RUNTIME_ERROR;
668
}
669

670
SIXELAPI void
671
sixel_thread_join(sixel_thread_t *thread)
672
{
673
    (void)thread;
674
}
675

676
SIXELAPI int
677
sixel_thread_pin_self(int cpu_index)
678
{
679
    (void)cpu_index;
680

681
    return SIXEL_OK;
682
}
683

684
SIXELAPI int
685
sixel_get_hw_threads(void)
686
{
687
    return 1;
688
}
689

690
#endif /* SIXEL_USE_PTHREADS */
691

692
/*
693
 * Thread configuration keeps the precedence rules centralized:
694
 *   1. Library callers may override via `sixel_set_threads`.
695
 *   2. Otherwise, `SIXEL_THREADS` from the environment is honored.
696
 *   3. Fallback defaults to single threaded execution.
697
 */
698
typedef struct sixel_thread_config_state {
699
    int requested_threads;
700
    int override_active;
701
    int env_threads;
702
    int env_valid;
703
    int env_checked;
704
} sixel_thread_config_state_t;
705

706
static sixel_thread_config_state_t g_thread_config = {
707
    1,
708
    0,
709
    1,
710
    0,
711
    0
712
};
713

714
static int
715
sixel_threads_token_is_auto(char const *text)
168✔
716
{
717
    if (text == NULL) {
168✔
718
        return 0;
719
    }
720

721
    if ((text[0] == 'a' || text[0] == 'A') &&
168✔
722
        (text[1] == 'u' || text[1] == 'U') &&
×
723
        (text[2] == 't' || text[2] == 'T') &&
×
724
        (text[3] == 'o' || text[3] == 'O') &&
×
725
        text[4] == '\0') {
×
726
        return 1;
×
727
    }
728

729
    return 0;
730
}
731

732
SIXELAPI int
733
sixel_threads_normalize(int requested)
25✔
734
{
735
    int normalized;
25✔
736

737
#if SIXEL_ENABLE_THREADS
738
    int hw_threads;
25✔
739

740
    if (requested <= 0) {
×
741
        hw_threads = sixel_get_hw_threads();
×
742
        if (hw_threads < 1) {
×
743
            hw_threads = 1;
744
        }
745
        normalized = hw_threads;
746
    } else {
747
        normalized = requested;
748
    }
749

750
    if (normalized < 1) {
751
        normalized = 1;
752
    }
753
#else
754
    (void)requested;
755
    normalized = 1;
756
#endif
757

758
    return normalized;
25✔
759
}
760

761
static int
762
sixel_threads_parse_env_value(char const *text, int *value)
168✔
763
{
764
    long parsed;
168✔
765
    char *endptr;
168✔
766
    int normalized;
168✔
767

768
    if (text == NULL || value == NULL) {
168✔
769
        return 0;
770
    }
771

772
    if (sixel_threads_token_is_auto(text)) {
168✔
773
        normalized = sixel_threads_normalize(0);
×
774
        *value = normalized;
×
775
        return 1;
×
776
    }
777

778
    errno = 0;
168✔
779
    parsed = strtol(text, &endptr, 10);
168✔
780
    if (endptr == text || *endptr != '\0' || errno == ERANGE) {
168✔
781
        return 0;
×
782
    }
783

784
    if (parsed < 1) {
168✔
785
        normalized = sixel_threads_normalize(1);
786
    } else if (parsed > INT_MAX) {
787
        normalized = sixel_threads_normalize(INT_MAX);
788
    } else {
789
        normalized = sixel_threads_normalize((int)parsed);
790
    }
791

792
    *value = normalized;
168✔
793
    return 1;
168✔
794
}
795

796
static void
797
sixel_threads_load_env(void)
918✔
798
{
799
    char const *text;
918✔
800
    int parsed;
918✔
801

802
    if (g_thread_config.env_checked) {
918✔
803
        return;
750✔
804
    }
805

806
    g_thread_config.env_checked = 1;
179✔
807
    g_thread_config.env_valid = 0;
179✔
808

809
    text = sixel_compat_getenv("SIXEL_THREADS");
179✔
810
    if (text == NULL || text[0] == '\0') {
179✔
811
        return;
812
    }
813

814
    if (sixel_threads_parse_env_value(text, &parsed)) {
168✔
815
        g_thread_config.env_threads = parsed;
168✔
816
        g_thread_config.env_valid = 1;
168✔
817
    }
818
}
819

820
SIXELAPI int
821
sixel_threads_resolve(void)
918✔
822
{
823
    int resolved;
918✔
824

825
#if SIXEL_ENABLE_THREADS
826
    if (g_thread_config.override_active) {
918✔
827
        return g_thread_config.requested_threads;
×
828
    }
829
#endif
830

831
    sixel_threads_load_env();
918✔
832

833
#if SIXEL_ENABLE_THREADS
834
    if (g_thread_config.env_valid) {
918✔
835
        resolved = g_thread_config.env_threads;
893✔
836
    } else {
837
        resolved = sixel_threads_normalize(0);
25✔
838
    }
839
#else
840
    resolved = 1;
841
#endif
842

843
    return resolved;
844
}
845

846
/*
847
 * Public setter so CLI/bindings may override the runtime thread preference.
848
 */
849
SIXELAPI void
850
sixel_set_threads(int threads)
×
851
{
852
#if SIXEL_ENABLE_THREADS
853
    g_thread_config.requested_threads = sixel_threads_normalize(threads);
×
854
#else
855
    (void)threads;
856
    g_thread_config.requested_threads = 1;
857
#endif
858
    g_thread_config.override_active = 1;
×
859
}
×
860

861
/* emacs Local Variables:      */
862
/* emacs mode: c               */
863
/* emacs tab-width: 4          */
864
/* emacs indent-tabs-mode: nil */
865
/* emacs c-basic-offset: 4     */
866
/* emacs End:                  */
867
/* vim: set expandtab ts=4 sts=4 sw=4 : */
868
/* 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