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

saitoha / libsixel / 20153856935

12 Dec 2025 01:43AM UTC coverage: 51.18% (+0.03%) from 51.152%
20153856935

push

github

saitoha
fix: handle unused simd_level when SIMD backends absent

11897 of 41488 branches covered (28.68%)

20210 of 39488 relevant lines covered (51.18%)

3990690.88 hits per line

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

93.25
/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(__linux__)
27
#define _GNU_SOURCE
28
#endif
29

30
#include "config.h"
31

32
#if defined(__APPLE__) && !defined(_DARWIN_C_SOURCE)
33
/*
34
 * Expose BSD-flavoured typedefs such as u_int from the macOS SDK when the
35
 * build defines _POSIX_C_SOURCE. The platform headers hide these legacy names
36
 * otherwise, and sys/sysctl.h requires them for data structures like
37
 * struct kinfo_proc.
38
 */
39
# define _DARWIN_C_SOURCE
40
#endif
41

42
#include <stdio.h>
43
#include <stdlib.h>
44
#include <string.h>
45

46
#if HAVE_ERRNO_H
47
# include <errno.h>
48
#endif
49
#if HAVE_LIMITS_H
50
# include <limits.h>
51
#endif
52
#if HAVE_UNISTD_H
53
# include <unistd.h>
54
#endif
55
#if HAVE_SYS_TYPES_H
56
# include <sys/types.h>
57
#endif
58
#if HAVE_SYS_SYSCTL_H
59
# include <sys/sysctl.h>
60
#endif
61

62
#include "compat_stub.h"
63
#include "threading.h"
64

65
/*
66
 * Backend selection is performed here so the header remains lightweight.
67
 * WITH_WINPTHREAD forces the pthread path even on Windows to honor
68
 * user-provided configuration switches.
69
 */
70
#if defined(WITH_WINPTHREAD) && WITH_WINPTHREAD
71
# define SIXEL_USE_PTHREADS 1
72
# define SIXEL_USE_WIN32_THREADS 0
73
#elif defined(_WIN32) && !defined(__CYGWIN__) && !defined(__MSYS__)
74
# define SIXEL_USE_PTHREADS 0
75
# define SIXEL_USE_WIN32_THREADS 1
76
#elif SIXEL_ENABLE_THREADS
77
# define SIXEL_USE_PTHREADS 1
78
# define SIXEL_USE_WIN32_THREADS 0
79
#else
80
# define SIXEL_USE_PTHREADS 0
81
# define SIXEL_USE_WIN32_THREADS 0
82
#endif
83

84
#if SIXEL_USE_WIN32_THREADS
85
# include <windows.h>
86
# include <process.h>
87
#endif
88

89
/*
90
 * Provide a thin portability layer for synchronization primitives so the
91
 * encoder can run on POSIX and Windows platforms without altering the public
92
 * API surface.
93
 */
94

95
#if SIXEL_USE_PTHREADS
96
# include <sched.h>
97

98
/*
99
 * Abort the process when a pthread call fails in a context where recovery is
100
 * impossible. Encoding without locking guarantees would corrupt state, so we
101
 * surface an explicit diagnostic before terminating.
102
 */
103
static void
104
sixel_pthread_abort(const char *what, int error)
105
{
106
    fprintf(stderr, "libsixel: %s failed: %d\n", what, error);
107
    abort();
108
}
109

110
/*
111
 * Entry point passed to pthread_create. It forwards execution to the user
112
 * supplied callback and stores the integer status for later inspection.
113
 */
114
static void *
115
sixel_thread_trampoline(void *arg)
254✔
116
{
117
    sixel_thread_t *thread;
254✔
118

119
    thread = (sixel_thread_t *)arg;
254✔
120
    thread->result = thread->fn(thread->arg);
254✔
121
    return NULL;
254✔
122
}
66✔
123

124
SIXELAPI int
125
sixel_mutex_init(sixel_mutex_t *mutex)
416✔
126
{
127
    int rc;
416✔
128

129
    if (mutex == NULL) {
416!
130
        return SIXEL_BAD_ARGUMENT;
131
    }
132
    /*
133
     * Default attributes already provide a non-recursive mutex, which is
134
     * sufficient for the encoder's synchronization requirements.
135
     */
136
    rc = pthread_mutex_init(&mutex->native, NULL);
416✔
137
    if (rc != 0) {
416!
138
        errno = rc;
139
        return SIXEL_RUNTIME_ERROR;
140
    }
141
    return SIXEL_OK;
164✔
142
}
164✔
143

144
SIXELAPI void
145
sixel_mutex_destroy(sixel_mutex_t *mutex)
416✔
146
{
147
    int rc;
416✔
148

149
    if (mutex == NULL) {
416!
150
        return;
151
    }
152
    rc = pthread_mutex_destroy(&mutex->native);
416✔
153
    if (rc != 0) {
416!
154
        sixel_pthread_abort("pthread_mutex_destroy", rc);
155
    }
156
}
164!
157

158
SIXELAPI void
159
sixel_mutex_lock(sixel_mutex_t *mutex)
18,198✔
160
{
161
    int rc;
18,198✔
162

163
    if (mutex == NULL) {
18,198!
164
        return;
165
    }
166
    rc = pthread_mutex_lock(&mutex->native);
18,198✔
167
    if (rc != 0) {
18,198!
168
        sixel_pthread_abort("pthread_mutex_lock", rc);
169
    }
170
}
5,987!
171

172
SIXELAPI void
173
sixel_mutex_unlock(sixel_mutex_t *mutex)
18,200✔
174
{
175
    int rc;
18,200✔
176

177
    if (mutex == NULL) {
18,200!
178
        return;
179
    }
180
    rc = pthread_mutex_unlock(&mutex->native);
18,200✔
181
    if (rc != 0) {
18,200!
182
        sixel_pthread_abort("pthread_mutex_unlock", rc);
183
    }
184
}
5,989!
185

186
SIXELAPI int
187
sixel_cond_init(sixel_cond_t *cond)
222✔
188
{
189
    int rc;
222✔
190

191
    if (cond == NULL) {
222!
192
        return SIXEL_BAD_ARGUMENT;
193
    }
194
    /*
195
     * Conditions wake waiters in FIFO order per pthreads documentation. No
196
     * custom attributes are needed for the thread pool queue.
197
     */
198
    rc = pthread_cond_init(&cond->native, NULL);
222✔
199
    if (rc != 0) {
222!
200
        errno = rc;
201
        return SIXEL_RUNTIME_ERROR;
202
    }
203
    return SIXEL_OK;
60✔
204
}
60✔
205

206
SIXELAPI void
207
sixel_cond_destroy(sixel_cond_t *cond)
222✔
208
{
209
    int rc;
222✔
210

211
    if (cond == NULL) {
222!
212
        return;
213
    }
214
    rc = pthread_cond_destroy(&cond->native);
222✔
215
    if (rc != 0) {
222!
216
        sixel_pthread_abort("pthread_cond_destroy", rc);
217
    }
218
}
60!
219

220
SIXELAPI void
221
sixel_cond_wait(sixel_cond_t *cond, sixel_mutex_t *mutex)
4,341✔
222
{
223
    int rc;
4,341✔
224

225
    if (cond == NULL || mutex == NULL) {
4,341!
226
        return;
2✔
227
    }
228
    rc = pthread_cond_wait(&cond->native, &mutex->native);
4,341✔
229
    if (rc != 0) {
4,341!
230
        sixel_pthread_abort("pthread_cond_wait", rc);
231
    }
232
}
1,066!
233

234
SIXELAPI void
235
sixel_cond_signal(sixel_cond_t *cond)
5,128✔
236
{
237
    int rc;
5,128✔
238

239
    if (cond == NULL) {
5,128!
240
        return;
241
    }
242
    rc = pthread_cond_signal(&cond->native);
5,128✔
243
    if (rc != 0) {
5,128!
244
        sixel_pthread_abort("pthread_cond_signal", rc);
245
    }
246
}
1,684!
247

248
SIXELAPI void
249
sixel_cond_broadcast(sixel_cond_t *cond)
3,718✔
250
{
251
    int rc;
3,718✔
252

253
    if (cond == NULL) {
3,718!
254
        return;
255
    }
256
    rc = pthread_cond_broadcast(&cond->native);
3,718✔
257
    if (rc != 0) {
3,718!
258
        sixel_pthread_abort("pthread_cond_broadcast", rc);
259
    }
260
}
1,203!
261

262
SIXELAPI int
263
sixel_thread_create(sixel_thread_t *thread, sixel_thread_fn fn, void *arg)
254✔
264
{
265
    int rc;
254✔
266

267
    if (thread == NULL || fn == NULL) {
254!
268
        return SIXEL_BAD_ARGUMENT;
269
    }
270
    /*
271
     * Store context before launching so the trampoline can record the
272
     * callback result inside the same structure without extra allocations.
273
     */
274
    thread->fn = fn;
254✔
275
    thread->arg = arg;
254✔
276
    thread->result = SIXEL_OK;
254✔
277
    thread->started = 0;
254✔
278
    rc = pthread_create(&thread->handle, NULL, sixel_thread_trampoline,
320✔
279
                        thread);
66✔
280
    if (rc != 0) {
254!
281
        errno = rc;
282
        return SIXEL_RUNTIME_ERROR;
283
    }
284
    thread->started = 1;
254✔
285
    return SIXEL_OK;
254✔
286
}
66✔
287

288
SIXELAPI void
289
sixel_thread_join(sixel_thread_t *thread)
254✔
290
{
291
    int rc;
254✔
292

293
    if (thread == NULL || !thread->started) {
254!
294
        return;
295
    }
296
    rc = pthread_join(thread->handle, NULL);
254✔
297
    if (rc != 0) {
254!
298
        sixel_pthread_abort("pthread_join", rc);
299
    }
300
    thread->started = 0;
254✔
301
}
66!
302

303
/*
304
 * Best-effort CPU affinity setter for the calling thread. Platforms without
305
 * pthread_setaffinity_np simply ignore the request so callers can use the
306
 * helper unconditionally.
307
 */
308
SIXELAPI int
309
sixel_thread_pin_self(int cpu_index)
310
{
311
#if defined(__linux__) && defined(CPU_SET)
312
    cpu_set_t set;
313
    int rc;
314

315
    if (cpu_index < 0) {
×
316
        return SIXEL_BAD_ARGUMENT;
317
    }
318
    CPU_ZERO(&set);
319
    CPU_SET((unsigned int)cpu_index, &set);
×
320
    rc = pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
321
    if (rc != 0) {
×
322
        return SIXEL_RUNTIME_ERROR;
323
    }
324

325
    return SIXEL_OK;
326
#else
327
    (void)cpu_index;
328

329
    return SIXEL_OK;
330
#endif
331
}
332

333
SIXELAPI int
334
sixel_get_hw_threads(void)
66✔
335
{
336
#if defined(_SC_NPROCESSORS_ONLN)
337
    long count;
66✔
338

339
    count = sysconf(_SC_NPROCESSORS_ONLN);
66✔
340
    if (count > 0 && count <= (long)INT_MAX) {
66!
341
        return (int)count;
66✔
342
    }
343
#endif
344
#if defined(__APPLE__)
345
    {
346
        int mib[2];
347
        size_t size;
348
        int value;
349

350
        mib[0] = CTL_HW;
351
        mib[1] = HW_AVAILCPU;
352
        size = sizeof(value);
353
        if (sysctl(mib, 2, &value, &size, NULL, 0) == 0 && value > 0) {
×
354
            return value;
355
        }
356
        mib[1] = HW_NCPU;
357
        size = sizeof(value);
358
        if (sysctl(mib, 2, &value, &size, NULL, 0) == 0 && value > 0) {
×
359
            return value;
360
        }
361
    }
×
362
#endif
363
    return 1;
364
}
22✔
365

366
#elif SIXEL_USE_WIN32_THREADS
367

368
/*
369
 * Abort execution on unrecoverable Win32 API failures to mirror pthread path
370
 * semantics. Printing the failing call and error code helps debugging in
371
 * environments where stderr is available.
372
 */
373
static void
374
sixel_win32_abort(const char *what, DWORD error)
375
{
376
    fprintf(stderr, "libsixel: %s failed: %lu\n", what,
377
            (unsigned long)error);
378
    abort();
379
}
380

381
/*
382
 * Trampoline for _beginthreadex. It records the callback result back into the
383
 * owning sixel_thread_t structure so the caller can retrieve it after join.
384
 */
385
static unsigned __stdcall
386
sixel_win32_thread_start(void *arg)
387
{
388
    sixel_thread_t *thread;
389

390
    thread = (sixel_thread_t *)arg;
391
    thread->result = thread->fn(thread->arg);
392
    return 0;
393
}
394

395
SIXELAPI int
396
sixel_mutex_init(sixel_mutex_t *mutex)
397
{
398
    if (mutex == NULL) {
399
        return SIXEL_BAD_ARGUMENT;
400
    }
401
    InitializeCriticalSection(&mutex->native);
402
    return SIXEL_OK;
403
}
404

405
SIXELAPI void
406
sixel_mutex_destroy(sixel_mutex_t *mutex)
407
{
408
    if (mutex == NULL) {
409
        return;
410
    }
411
    DeleteCriticalSection(&mutex->native);
412
}
413

414
SIXELAPI void
415
sixel_mutex_lock(sixel_mutex_t *mutex)
416
{
417
    if (mutex == NULL) {
418
        return;
419
    }
420
    EnterCriticalSection(&mutex->native);
421
}
422

423
SIXELAPI void
424
sixel_mutex_unlock(sixel_mutex_t *mutex)
425
{
426
    if (mutex == NULL) {
427
        return;
428
    }
429
    LeaveCriticalSection(&mutex->native);
430
}
431

432
SIXELAPI int
433
sixel_cond_init(sixel_cond_t *cond)
434
{
435
    if (cond == NULL) {
436
        return SIXEL_BAD_ARGUMENT;
437
    }
438
    InitializeConditionVariable(&cond->native);
439
    return SIXEL_OK;
440
}
441

442
SIXELAPI void
443
sixel_cond_destroy(sixel_cond_t *cond)
444
{
445
    /* CONDITION_VARIABLE does not need explicit teardown. */
446
    (void)cond;
447
}
448

449
SIXELAPI void
450
sixel_cond_wait(sixel_cond_t *cond, sixel_mutex_t *mutex)
451
{
452
    BOOL rc;
453
    DWORD error;
454

455
    if (cond == NULL || mutex == NULL) {
456
        return;
457
    }
458
    rc = SleepConditionVariableCS(&cond->native, &mutex->native, INFINITE);
459
    if (rc == 0) {
460
        error = GetLastError();
461
        sixel_win32_abort("SleepConditionVariableCS", error);
462
    }
463
}
464

465
SIXELAPI void
466
sixel_cond_signal(sixel_cond_t *cond)
467
{
468
    if (cond == NULL) {
469
        return;
470
    }
471
    WakeConditionVariable(&cond->native);
472
}
473

474
SIXELAPI void
475
sixel_cond_broadcast(sixel_cond_t *cond)
476
{
477
    if (cond == NULL) {
478
        return;
479
    }
480
    WakeAllConditionVariable(&cond->native);
481
}
482

483
SIXELAPI int
484
sixel_thread_create(sixel_thread_t *thread, sixel_thread_fn fn, void *arg)
485
{
486
    uintptr_t handle;
487

488
    if (thread == NULL || fn == NULL) {
489
        return SIXEL_BAD_ARGUMENT;
490
    }
491
    thread->fn = fn;
492
    thread->arg = arg;
493
    thread->result = SIXEL_OK;
494
    thread->started = 0;
495
    handle = _beginthreadex(NULL, 0, sixel_win32_thread_start, thread, 0,
496
                            NULL);
497
    if (handle == 0) {
498
        return SIXEL_RUNTIME_ERROR;
499
    }
500
    thread->handle = (HANDLE)handle;
501
    thread->started = 1;
502
    return SIXEL_OK;
503
}
504

505
SIXELAPI void
506
sixel_thread_join(sixel_thread_t *thread)
507
{
508
    DWORD rc;
509
    DWORD error;
510

511
    if (thread == NULL || !thread->started) {
512
        return;
513
    }
514
    rc = WaitForSingleObject(thread->handle, INFINITE);
515
    if (rc != WAIT_OBJECT_0) {
516
        error = (rc == WAIT_FAILED) ? GetLastError() : rc;
517
        sixel_win32_abort("WaitForSingleObject", error);
518
    }
519
    CloseHandle(thread->handle);
520
    thread->handle = NULL;
521
    thread->started = 0;
522
}
523

524
SIXELAPI int
525
sixel_thread_pin_self(int cpu_index)
526
{
527
    DWORD_PTR mask;
528
    DWORD_PTR previous;
529

530
    if (cpu_index < 0) {
531
        return SIXEL_BAD_ARGUMENT;
532
    }
533
    mask = ((DWORD_PTR)1) << (DWORD_PTR)cpu_index;
534
    previous = SetThreadAffinityMask(GetCurrentThread(), mask);
535
    if (previous == 0) {
536
        return SIXEL_RUNTIME_ERROR;
537
    }
538

539
    return SIXEL_OK;
540
}
541

542
SIXELAPI int
543
sixel_get_hw_threads(void)
544
{
545
    DWORD count;
546
    SYSTEM_INFO info;
547

548
    count = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
549
    if (count == 0) {
550
        GetSystemInfo(&info);
551
        count = info.dwNumberOfProcessors;
552
    }
553
    if (count == 0) {
554
        count = 1;
555
    }
556
    return (int)count;
557
}
558

559
#else
560
/*
561
 * Thread support is disabled. Provide stub implementations so callers that
562
 * inadvertently use the API receive a deterministic failure rather than a
563
 * linker error. Mutex and condition helpers become no-ops while creation
564
 * attempts return an explicit runtime error.
565
 */
566
SIXELAPI int
567
sixel_mutex_init(sixel_mutex_t *mutex)
87✔
568
{
569
    (void)mutex;
87✔
570
    return SIXEL_RUNTIME_ERROR;
87✔
571
}
572

573
SIXELAPI void
574
sixel_mutex_destroy(sixel_mutex_t *mutex)
575
{
576
    (void)mutex;
577
}
578

579
SIXELAPI void
580
sixel_mutex_lock(sixel_mutex_t *mutex)
581
{
582
    (void)mutex;
583
}
584

585
SIXELAPI void
586
sixel_mutex_unlock(sixel_mutex_t *mutex)
587
{
588
    (void)mutex;
589
}
590

591
SIXELAPI int
592
sixel_cond_init(sixel_cond_t *cond)
593
{
594
    (void)cond;
595
    return SIXEL_RUNTIME_ERROR;
596
}
597

598
SIXELAPI void
599
sixel_cond_destroy(sixel_cond_t *cond)
600
{
601
    (void)cond;
602
}
603

604
SIXELAPI void
605
sixel_cond_wait(sixel_cond_t *cond, sixel_mutex_t *mutex)
606
{
607
    (void)cond;
608
    (void)mutex;
609
}
610

611
SIXELAPI void
612
sixel_cond_signal(sixel_cond_t *cond)
613
{
614
    (void)cond;
615
}
616

617
SIXELAPI void
618
sixel_cond_broadcast(sixel_cond_t *cond)
619
{
620
    (void)cond;
621
}
622

623
SIXELAPI int
624
sixel_thread_create(sixel_thread_t *thread, sixel_thread_fn fn, void *arg)
625
{
626
    (void)thread;
627
    (void)fn;
628
    (void)arg;
629
    return SIXEL_RUNTIME_ERROR;
630
}
631

632
SIXELAPI void
633
sixel_thread_join(sixel_thread_t *thread)
634
{
635
    (void)thread;
636
}
637

638
SIXELAPI int
639
sixel_thread_pin_self(int cpu_index)
640
{
641
    (void)cpu_index;
642

643
    return SIXEL_OK;
644
}
645

646
SIXELAPI int
647
sixel_get_hw_threads(void)
648
{
649
    return 1;
650
}
651

652
#endif /* SIXEL_USE_PTHREADS */
653

654
/*
655
 * Thread configuration keeps the precedence rules centralized:
656
 *   1. Library callers may override via `sixel_set_threads`.
657
 *   2. Otherwise, `SIXEL_THREADS` from the environment is honored.
658
 *   3. Fallback defaults to single threaded execution.
659
 */
660
typedef struct sixel_thread_config_state {
661
    int requested_threads;
662
    int override_active;
663
    int env_threads;
664
    int env_valid;
665
    int env_checked;
666
} sixel_thread_config_state_t;
667

668
static sixel_thread_config_state_t g_thread_config = {
669
    1,
670
    0,
671
    1,
672
    0,
673
    0
674
};
675

676
static int
677
sixel_threads_token_is_auto(char const *text)
634✔
678
{
679
    if (text == NULL) {
634!
680
        return 0;
681
    }
682

683
    if ((text[0] == 'a' || text[0] == 'A') &&
634!
684
        (text[1] == 'u' || text[1] == 'U') &&
×
685
        (text[2] == 't' || text[2] == 'T') &&
×
686
        (text[3] == 'o' || text[3] == 'O') &&
×
687
        text[4] == '\0') {
×
688
        return 1;
×
689
    }
690

691
    return 0;
208✔
692
}
208✔
693

694
SIXELAPI int
695
sixel_threads_normalize(int requested)
558✔
696
{
697
    int normalized;
558✔
698

699
#if SIXEL_ENABLE_THREADS
700
    int hw_threads;
416✔
701

702
    if (requested <= 0) {
416!
703
        hw_threads = sixel_get_hw_threads();
66✔
704
        if (hw_threads < 1) {
66!
705
            hw_threads = 1;
706
        }
707
        normalized = hw_threads;
22✔
708
    } else {
22✔
709
        normalized = requested;
208✔
710
    }
711

712
    if (normalized < 1) {
230!
713
        normalized = 1;
714
    }
715
#else
716
    (void)requested;
142✔
717
    normalized = 1;
142✔
718
#endif
719

720
    return normalized;
788✔
721
}
230✔
722

723
static int
724
sixel_threads_parse_env_value(char const *text, int *value)
634✔
725
{
726
    long parsed;
634✔
727
    char *endptr;
634✔
728
    int normalized;
634✔
729

730
    if (text == NULL || value == NULL) {
634!
731
        return 0;
732
    }
733

734
    if (sixel_threads_token_is_auto(text)) {
634!
735
        normalized = sixel_threads_normalize(0);
×
736
        *value = normalized;
×
737
        return 1;
×
738
    }
739

740
    errno = 0;
634✔
741
    parsed = strtol(text, &endptr, 10);
634✔
742
    if (endptr == text || *endptr != '\0' || errno == ERANGE) {
634!
743
        return 0;
744
    }
745

746
    if (parsed < 1) {
634!
747
        normalized = sixel_threads_normalize(1);
748
    } else if (parsed > INT_MAX) {
634!
749
        normalized = sixel_threads_normalize(INT_MAX);
750
    } else {
751
        normalized = sixel_threads_normalize((int)parsed);
634✔
752
    }
753

754
    *value = normalized;
634✔
755
    return 1;
634✔
756
}
208✔
757

758
static void
759
sixel_threads_load_env(void)
3,517✔
760
{
761
    char const *text;
3,517✔
762
    int parsed;
3,517✔
763

764
    if (g_thread_config.env_checked) {
3,517✔
765
        return;
2,874✔
766
    }
767

768
    g_thread_config.env_checked = 1;
670✔
769
    g_thread_config.env_valid = 0;
670✔
770

771
    text = sixel_compat_getenv("SIXEL_THREADS");
670✔
772
    if (text == NULL || text[0] == '\0') {
670!
773
        return;
9✔
774
    }
775

776
    if (sixel_threads_parse_env_value(text, &parsed)) {
634!
777
        g_thread_config.env_threads = parsed;
634✔
778
        g_thread_config.env_valid = 1;
634✔
779
    }
208✔
780
}
1,018!
781

782
SIXELAPI int
783
sixel_threads_resolve(void)
3,517✔
784
{
785
    int resolved;
3,517✔
786

787
#if SIXEL_ENABLE_THREADS
788
    if (g_thread_config.override_active) {
2,698!
789
        return g_thread_config.requested_threads;
790
    }
791
#endif
792

793
    sixel_threads_load_env();
3,517✔
794

795
#if SIXEL_ENABLE_THREADS
796
    if (g_thread_config.env_valid) {
2,698✔
797
        resolved = g_thread_config.env_threads;
2,632✔
798
    } else {
996✔
799
        resolved = sixel_threads_normalize(0);
66✔
800
    }
801
#else
802
    resolved = 1;
819✔
803
#endif
804

805
    return resolved;
1,837✔
806
}
1,018✔
807

808
/*
809
 * Public setter so CLI/bindings may override the runtime thread preference.
810
 */
811
SIXELAPI void
812
sixel_set_threads(int threads)
×
813
{
814
#if SIXEL_ENABLE_THREADS
815
    g_thread_config.requested_threads = sixel_threads_normalize(threads);
×
816
#else
817
    (void)threads;
818
    g_thread_config.requested_threads = 1;
819
#endif
820
    g_thread_config.override_active = 1;
×
821
}
×
822

823
/* emacs Local Variables:      */
824
/* emacs mode: c               */
825
/* emacs tab-width: 4          */
826
/* emacs indent-tabs-mode: nil */
827
/* emacs c-basic-offset: 4     */
828
/* emacs End:                  */
829
/* vim: set expandtab ts=4 sts=4 sw=4 : */
830
/* 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