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

saitoha / libsixel / 20068035931

09 Dec 2025 03:01PM UTC coverage: 41.067% (-0.2%) from 41.292%
20068035931

push

github

saitoha
lookup: add vpte tile size tuning via environment variables

10771 of 40760 branches covered (26.43%)

0 of 66 new or added lines in 2 files covered. (0.0%)

1228 existing lines in 4 files now uncovered.

14798 of 36034 relevant lines covered (41.07%)

2724277.3 hits per line

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

92.2
/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 "threading.h"
63

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

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

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

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

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

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

118
    thread = (sixel_thread_t *)arg;
24✔
119
    thread->result = thread->fn(thread->arg);
24✔
120
    return NULL;
24✔
121
}
8✔
122

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

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

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

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

157
SIXELAPI void
158
sixel_mutex_lock(sixel_mutex_t *mutex)
24✔
159
{
160
    int rc;
24✔
161

162
    if (mutex == NULL) {
24!
163
        return;
164
    }
165
    rc = pthread_mutex_lock(&mutex->native);
24✔
166
    if (rc != 0) {
24!
167
        sixel_pthread_abort("pthread_mutex_lock", rc);
168
    }
169
}
8!
170

171
SIXELAPI void
172
sixel_mutex_unlock(sixel_mutex_t *mutex)
24✔
173
{
174
    int rc;
24✔
175

176
    if (mutex == NULL) {
24!
177
        return;
178
    }
179
    rc = pthread_mutex_unlock(&mutex->native);
24✔
180
    if (rc != 0) {
24!
181
        sixel_pthread_abort("pthread_mutex_unlock", rc);
182
    }
183
}
8!
184

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

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

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

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

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

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

233
SIXELAPI void
234
sixel_cond_signal(sixel_cond_t *cond)
235
{
236
    int rc;
237

238
    if (cond == NULL) {
×
239
        return;
240
    }
241
    rc = pthread_cond_signal(&cond->native);
242
    if (rc != 0) {
×
243
        sixel_pthread_abort("pthread_cond_signal", rc);
244
    }
245
}
×
246

247
SIXELAPI void
248
sixel_cond_broadcast(sixel_cond_t *cond)
6✔
249
{
250
    int rc;
6✔
251

252
    if (cond == NULL) {
6!
253
        return;
254
    }
255
    rc = pthread_cond_broadcast(&cond->native);
6✔
256
    if (rc != 0) {
6!
257
        sixel_pthread_abort("pthread_cond_broadcast", rc);
258
    }
259
}
2!
260

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

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

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

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

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

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

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

328
    return SIXEL_OK;
329
#endif
330
}
331

332
SIXELAPI int
333
sixel_get_hw_threads(void)
334
{
335
    long count;
336
#if defined(_SC_NPROCESSORS_ONLN)
337
    count = sysconf(_SC_NPROCESSORS_ONLN);
338
    if (count > 0 && count <= (long)INT_MAX) {
×
339
        return (int)count;
340
    }
341
#endif
342
#if defined(__APPLE__)
343
    {
344
        int mib[2];
345
        size_t size;
346
        int value;
347

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

364
#elif SIXEL_USE_WIN32_THREADS
365

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

522
SIXELAPI int
523
sixel_thread_pin_self(int cpu_index)
524
{
525
    DWORD_PTR mask;
526
    DWORD_PTR previous;
527

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

537
    return SIXEL_OK;
538
}
539

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

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

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

571
SIXELAPI void
572
sixel_mutex_destroy(sixel_mutex_t *mutex)
573
{
574
    (void)mutex;
575
}
576

577
SIXELAPI void
578
sixel_mutex_lock(sixel_mutex_t *mutex)
579
{
580
    (void)mutex;
581
}
582

583
SIXELAPI void
584
sixel_mutex_unlock(sixel_mutex_t *mutex)
585
{
586
    (void)mutex;
587
}
588

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

596
SIXELAPI void
597
sixel_cond_destroy(sixel_cond_t *cond)
598
{
599
    (void)cond;
600
}
601

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

609
SIXELAPI void
610
sixel_cond_signal(sixel_cond_t *cond)
611
{
612
    (void)cond;
613
}
614

615
SIXELAPI void
616
sixel_cond_broadcast(sixel_cond_t *cond)
617
{
618
    (void)cond;
619
}
620

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

630
SIXELAPI void
631
sixel_thread_join(sixel_thread_t *thread)
632
{
633
    (void)thread;
634
}
635

636
SIXELAPI int
637
sixel_thread_pin_self(int cpu_index)
638
{
639
    (void)cpu_index;
640

641
    return SIXEL_OK;
642
}
643

644
SIXELAPI int
645
sixel_get_hw_threads(void)
646
{
647
    return 1;
648
}
649

650
#endif /* SIXEL_USE_PTHREADS */
651

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

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

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

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

689
    return 0;
206✔
690
}
206✔
691

692
SIXELAPI int
693
sixel_threads_normalize(int requested)
485✔
694
{
695
    int normalized;
485✔
696

697
#if SIXEL_ENABLE_THREADS
698
    int hw_threads;
346✔
699

700
    if (requested <= 0) {
346!
701
        hw_threads = sixel_get_hw_threads();
702
        if (hw_threads < 1) {
×
703
            hw_threads = 1;
704
        }
705
        normalized = hw_threads;
706
    } else {
707
        normalized = requested;
206✔
708
    }
709

710
    if (normalized < 1) {
206!
711
        normalized = 1;
712
    }
713
#else
714
    (void)requested;
139✔
715
    normalized = 1;
139✔
716
#endif
717

718
    return normalized;
691✔
719
}
206✔
720

721
static int
722
sixel_threads_parse_env_value(char const *text, int *value)
625✔
723
{
724
    long parsed;
625✔
725
    char *endptr;
625✔
726
    int normalized;
625✔
727

728
    if (text == NULL || value == NULL) {
625!
729
        return 0;
730
    }
731

732
    if (sixel_threads_token_is_auto(text)) {
625!
UNCOV
733
        normalized = sixel_threads_normalize(0);
×
UNCOV
734
        *value = normalized;
×
UNCOV
735
        return 1;
×
736
    }
737

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

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

752
    *value = normalized;
625✔
753
    return 1;
625✔
754
}
206✔
755

756
static void
757
sixel_threads_load_env(void)
2,473✔
758
{
759
    char const *text;
2,473✔
760
    int parsed;
2,473✔
761

762
    if (g_thread_config.env_checked) {
2,473✔
763
        return;
1,848✔
764
    }
765

766
    g_thread_config.env_checked = 1;
625✔
767
    g_thread_config.env_valid = 0;
625✔
768

769
    text = getenv("SIXEL_THREADS");
625✔
770
    if (text == NULL || text[0] == '\0') {
625!
771
        return;
772
    }
773

774
    if (sixel_threads_parse_env_value(text, &parsed)) {
625!
775
        g_thread_config.env_threads = parsed;
625✔
776
        g_thread_config.env_valid = 1;
625✔
777
    }
206✔
778
}
755!
779

780
SIXELAPI int
781
sixel_threads_resolve(void)
2,473✔
782
{
783
    int resolved;
2,473✔
784

785
#if SIXEL_ENABLE_THREADS
786
    if (g_thread_config.override_active) {
1,917!
787
        return g_thread_config.requested_threads;
788
    }
789
#endif
790

791
    sixel_threads_load_env();
2,473✔
792

793
#if SIXEL_ENABLE_THREADS
794
    if (g_thread_config.env_valid) {
1,917!
795
        resolved = g_thread_config.env_threads;
1,917✔
796
    } else {
755✔
797
        resolved = sixel_threads_normalize(0);
798
    }
799
#else
800
    resolved = 1;
556✔
801
#endif
802

803
    return resolved;
1,311✔
804
}
755✔
805

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

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