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

kos-lang / kos / 21545960393

30 Jan 2026 11:26PM UTC coverage: 96.276% (+0.03%) from 96.244%
21545960393

push

github

cdragan
core: improve test coverage

4 of 4 new or added lines in 1 file covered. (100.0%)

25 existing lines in 3 files now uncovered.

24507 of 25455 relevant lines covered (96.28%)

876027.15 hits per line

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

90.0
/modules/kos_mod_threads.c
1
/* SPDX-License-Identifier: MIT
2
 * SPDX-FileCopyrightText: Copyright (c) 2014-2024 Chris Dragan
3
 */
4

5
#include "../inc/kos_array.h"
6
#include "../inc/kos_constants.h"
7
#include "../inc/kos_error.h"
8
#include "../inc/kos_instance.h"
9
#include "../inc/kos_malloc.h"
10
#include "../inc/kos_module.h"
11
#include "../inc/kos_object.h"
12
#include "../inc/kos_system.h"
13
#include "../inc/kos_threads.h"
14
#include "../inc/kos_utils.h"
15
#include "../core/kos_debug.h"
16
#include "../core/kos_misc.h"
17
#include "../core/kos_try.h"
18

19
#define __STDC_FORMAT_MACROS
20
#include <inttypes.h>
21

22
#define KOS_MAX_SEM 0x7FFFFFFF
23

24
KOS_DECLARE_STATIC_CONST_STRING(str_count,               "count");
25
KOS_DECLARE_STATIC_CONST_STRING(str_err_bad_module,      "failed to get private data from module thread");
26
KOS_DECLARE_STATIC_CONST_STRING(str_err_cond_var_failed, "failed to create a condition variable");
27
KOS_DECLARE_STATIC_CONST_STRING(str_err_count_too_small, "count argument is less than 1");
28
KOS_DECLARE_STATIC_CONST_STRING(str_err_count_too_large, "count argument exceeds 0x7FFFFFFF");
29
KOS_DECLARE_STATIC_CONST_STRING(str_err_mutex_failed,    "failed to create a mutex");
30
KOS_DECLARE_STATIC_CONST_STRING(str_err_not_number,      "object is not a number");
31
KOS_DECLARE_STATIC_CONST_STRING(str_err_init_too_large,  "init argument exceeds 0x7FFFFFFF");
32
KOS_DECLARE_STATIC_CONST_STRING(str_err_init_too_small,  "init argument is less than 0");
33
KOS_DECLARE_STATIC_CONST_STRING(str_init,                "init");
34
KOS_DECLARE_STATIC_CONST_STRING(str_seconds,             "seconds");
35

36
KOS_DECLARE_PRIVATE_CLASS(mutex_priv_class);
37

38
static void mutex_finalize(KOS_CONTEXT ctx,
6✔
39
                           void       *priv)
40
{
41
    if (priv)
6✔
42
        kos_destroy_mutex((KOS_MUTEX *)&priv);
6✔
43
}
6✔
44

45
/* @item threads mutex()
46
 *
47
 *     mutex()
48
 *
49
 * Mutex object class.
50
 *
51
 * Mutex objects are best used with the `with` statement.
52
 */
53
static KOS_OBJ_ID mutex_ctor(const KOS_CONTEXT             ctx,
6✔
54
                             const KOS_OBJ_ID              this_obj,
55
                             const uint32_t                num_args,
56
                             KOS_ATOMIC(KOS_OBJ_ID) *const args)
57
{
58
    KOS_LOCAL  mutex;
59
    KOS_OBJ_ID proto;
60
    KOS_MUTEX  mutex_obj = KOS_NULL;
6✔
61
    int        error     = KOS_SUCCESS;
6✔
62

63
    KOS_init_local(ctx, &mutex);
6✔
64

65
    error = kos_create_mutex(&mutex_obj);
6✔
66

67
    if (error)
6✔
68
        RAISE_EXCEPTION_STR(str_err_mutex_failed);
×
69

70
    proto = KOS_get_module(ctx);
6✔
71
    TRY_OBJID(proto);
6✔
72

73
    proto = KOS_atomic_read_relaxed_obj(OBJPTR(MODULE, proto)->priv);
6✔
74
    if (IS_BAD_PTR(proto) || kos_seq_fail())
6✔
75
        RAISE_EXCEPTION_STR(str_err_bad_module);
×
76

77
    proto = KOS_array_read(ctx, proto, 0);
6✔
78
    TRY_OBJID(proto);
6✔
79

80
    mutex.o = KOS_new_object_with_private(ctx,
6✔
81
                                          proto,
82
                                          &mutex_priv_class,
83
                                          mutex_finalize);
84
    TRY_OBJID(mutex.o);
6✔
85

86
    KOS_object_set_private_ptr(mutex.o, mutex_obj);
6✔
87
    mutex_obj = KOS_NULL;
6✔
88

89
cleanup:
6✔
90
    if (mutex_obj)
6✔
91
        kos_destroy_mutex(&mutex_obj);
×
92

93
    mutex.o = KOS_destroy_top_local(ctx, &mutex);
6✔
94

95
    return error ? KOS_BADPTR : mutex.o;
6✔
96
}
97

98
/* @item threads mutex.prototype.acquire()
99
 *
100
 *     mutex.prototype.acquire()
101
 *
102
 * Locks the mutex object.
103
 *
104
 * If the mutex is already locked by another thread, this function will wait
105
 * until it is unlocked.
106
 *
107
 * Returns `this` mutex object.
108
 */
109
static KOS_OBJ_ID mutex_acquire(const KOS_CONTEXT             ctx,
43✔
110
                                const KOS_OBJ_ID              this_obj,
111
                                const uint32_t                num_args,
112
                                KOS_ATOMIC(KOS_OBJ_ID) *const args)
113
{
114
    KOS_LOCAL mutex;
115
    KOS_MUTEX mutex_obj;
116

117
    KOS_init_local_with(ctx, &mutex, this_obj);
43✔
118

119
    mutex_obj = (KOS_MUTEX)KOS_object_get_private(mutex.o, &mutex_priv_class);
43✔
120

121
    if (mutex_obj) {
43✔
122
        KOS_suspend_context(ctx);
43✔
123

124
        kos_lock_mutex(mutex_obj);
43✔
125

126
        KOS_resume_context(ctx);
43✔
127
    }
128

129
    return KOS_destroy_top_local(ctx, &mutex);
43✔
130
}
131

132
/* @item threads mutex.prototype.release()
133
 *
134
 *     mutex.prototype.release()
135
 *
136
 * Unlocks the mutex object, if it is held by the current thread.
137
 *
138
 * If the mutex is not held by the current thread, this function does nothing.
139
 *
140
 * Returns `this` mutex object.
141
 */
142
static KOS_OBJ_ID mutex_release(const KOS_CONTEXT             ctx,
43✔
143
                                const KOS_OBJ_ID              this_obj,
144
                                const uint32_t                num_args,
145
                                KOS_ATOMIC(KOS_OBJ_ID) *const args)
146
{
147
    const KOS_MUTEX mutex_obj = (KOS_MUTEX)KOS_object_get_private(this_obj, &mutex_priv_class);
43✔
148

149
    if (mutex_obj)
43✔
150
        kos_unlock_mutex(mutex_obj);
43✔
151

152
    return this_obj;
43✔
153
}
154

155
typedef struct KOS_SEMAPHORE_S {
156
    KOS_MUTEX            mutex;
157
    KOS_COND_VAR         cond_var;
158
    KOS_ATOMIC(uint32_t) value;
159
} KOS_SEMAPHORE;
160

161
KOS_DECLARE_PRIVATE_CLASS(semaphore_priv_class);
162

163
static void semaphore_finalize(KOS_CONTEXT ctx,
8✔
164
                               void       *priv)
165
{
166
    if (priv) {
8✔
167
        KOS_SEMAPHORE *const sem = (KOS_SEMAPHORE *)priv;
8✔
168

169
        if (sem->mutex)
8✔
170
            kos_destroy_mutex(&sem->mutex);
6✔
171

172
        if (sem->cond_var)
8✔
173
            kos_destroy_cond_var(&sem->cond_var);
6✔
174

175
        KOS_free(sem);
8✔
176
    }
177
}
8✔
178

179
static const KOS_CONVERT sem_args[2] = {
180
    KOS_DEFINE_OPTIONAL_ARG(str_init, TO_SMALL_INT(0)),
181
    KOS_DEFINE_TAIL_ARG()
182
};
183

184
/* @item threads semaphore()
185
 *
186
 *     semaphore(init = 0)
187
 *
188
 * Semaphore object class.
189
 *
190
 * A semaphore is an integer number which can be incremented (release)
191
 * or decremented (acquire).  If an `acquire()` function is called on
192
 * a semaphore which has a zero-value, the function will block until
193
 * another thread increments the semaphore.
194
 *
195
 * `init` is the initial integer value for the new semaphore object.
196
 *
197
 * Semaphore objects can be used with the `with` statement.
198
 */
199
static KOS_OBJ_ID semaphore_ctor(const KOS_CONTEXT             ctx,
8✔
200
                                 const KOS_OBJ_ID              this_obj,
201
                                 const uint32_t                num_args,
202
                                 KOS_ATOMIC(KOS_OBJ_ID) *const args)
203
{
204
    KOS_LOCAL      semaphore;
205
    KOS_OBJ_ID     proto;
206
    KOS_SEMAPHORE *sem   = KOS_NULL;
8✔
207
    int            error = KOS_SUCCESS;
8✔
208

209
    assert(num_args >= 1);
8✔
210

211
    KOS_init_local(ctx, &semaphore);
8✔
212

213
    sem = (KOS_SEMAPHORE *)KOS_malloc(sizeof(KOS_SEMAPHORE));
8✔
214

215
    if ( ! sem) {
8✔
216
        KOS_raise_exception(ctx, KOS_STR_OUT_OF_MEMORY);
×
217
        goto cleanup;
×
218
    }
219

220
    sem->mutex    = KOS_NULL;
8✔
221
    sem->cond_var = KOS_NULL;
8✔
222

223
    {
224
        int64_t value64;
225

226
        TRY(KOS_get_integer(ctx, args[0], &value64));
10✔
227

228
        if (value64 < 0)
8✔
229
            RAISE_EXCEPTION_STR(str_err_init_too_small);
1✔
230

231
        if (value64 > KOS_MAX_SEM)
7✔
232
            RAISE_EXCEPTION_STR(str_err_init_too_large);
1✔
233

234
        KOS_atomic_write_relaxed_u32(sem->value, (uint32_t)value64);
6✔
235
    }
236

237
    error = kos_create_mutex(&sem->mutex);
6✔
238

239
    if (error)
6✔
240
        RAISE_EXCEPTION_STR(str_err_mutex_failed);
×
241

242
    error = kos_create_cond_var(&sem->cond_var);
6✔
243

244
    if (error)
6✔
245
        RAISE_EXCEPTION_STR(str_err_cond_var_failed);
×
246

247
    proto = KOS_get_module(ctx);
6✔
248
    TRY_OBJID(proto);
6✔
249

250
    proto = KOS_atomic_read_relaxed_obj(OBJPTR(MODULE, proto)->priv);
6✔
251
    if (IS_BAD_PTR(proto) || kos_seq_fail())
6✔
252
        RAISE_EXCEPTION_STR(str_err_bad_module);
×
253

254
    proto = KOS_array_read(ctx, proto, 1);
6✔
255
    TRY_OBJID(proto);
6✔
256

257
    semaphore.o = KOS_new_object_with_private(ctx,
6✔
258
                                              proto,
259
                                              &semaphore_priv_class,
260
                                              semaphore_finalize);
261
    TRY_OBJID(semaphore.o);
6✔
262

263
    KOS_object_set_private_ptr(semaphore.o, sem);
6✔
264
    sem = KOS_NULL;
6✔
265

266
cleanup:
8✔
267
    if (sem)
8✔
268
        semaphore_finalize(ctx, sem);
2✔
269

270
    semaphore.o = KOS_destroy_top_local(ctx, &semaphore);
8✔
271

272
    return error ? KOS_BADPTR : semaphore.o;
8✔
273
}
274

275
static const KOS_CONVERT count_arg[2] = {
276
    { KOS_CONST_ID(str_count), TO_SMALL_INT(1), 0, 0, KOS_NATIVE_INT64 },
277
    KOS_DEFINE_TAIL_ARG()
278
};
279

280
static int get_count_arg(const KOS_CONTEXT             ctx,
49✔
281
                         KOS_ATOMIC(KOS_OBJ_ID) *const args,
282
                         uint32_t                     *count)
283
{
284
    int64_t count64 = 0;
49✔
285
    int     error;
286

287
    error = KOS_extract_native_from_args(ctx, 1, args, "argument", count_arg, KOS_NULL, &count64);
49✔
288
    if (error)
49✔
289
        return error;
×
290

291
    if (count64 < 1) {
49✔
292
        KOS_raise_exception(ctx, KOS_CONST_ID(str_err_count_too_small));
2✔
293
        return KOS_ERROR_EXCEPTION;
2✔
294
    }
295

296
    if (count64 > KOS_MAX_SEM) {
47✔
297
        KOS_raise_exception(ctx, KOS_CONST_ID(str_err_count_too_large));
2✔
298
        return KOS_ERROR_EXCEPTION;
2✔
299
    }
300

301
    *count = (uint32_t)(uint64_t)count64;
45✔
302

303
    return KOS_SUCCESS;
45✔
304
}
305

306
/* @item threads semaphore.prototype.acquire()
307
 *
308
 *     semaphore.prototype.acquire(count = 1)
309
 *
310
 * Subtracts `count` from the semaphore value.
311
 *
312
 * `count` defaults to 1.  If `count` is less than 1 or greater than 0x7FFFFFFF
313
 * throws an exception.
314
 *
315
 * If the semaphore value is already 0, blocks until another thread increments it,
316
 * then performs the decrement operation.  This is repeated until the value has
317
 * been decremented `count` times.  The decrement operation is non-atomic meaning
318
 * that if two threads are trying to acquire with `count > 1`, each of them could
319
 * decrement the value by 1 multiple times.
320
 *
321
 * Returns `this` semaphore object.
322
 */
323
static KOS_OBJ_ID semaphore_acquire(const KOS_CONTEXT             ctx,
33✔
324
                                    const KOS_OBJ_ID              this_obj,
325
                                    const uint32_t                num_args,
326
                                    KOS_ATOMIC(KOS_OBJ_ID) *const args)
327
{
328
    KOS_LOCAL      semaphore;
329
    KOS_SEMAPHORE *sem   = KOS_NULL;
33✔
330
    uint32_t       count = 0;
33✔
331

332
    KOS_init_local_with(ctx, &semaphore, this_obj);
33✔
333

334
    sem = (KOS_SEMAPHORE *)KOS_object_get_private(semaphore.o, &semaphore_priv_class);
33✔
335

336
    if (get_count_arg(ctx, args, &count)) {
33✔
337
        KOS_destroy_top_local(ctx, &semaphore);
2✔
338
        return KOS_BADPTR;
2✔
339
    }
340

341
    if (sem) {
31✔
342

343
        int suspended = 0;
31✔
344

345
        do {
346
            const uint32_t old_value = KOS_atomic_read_relaxed_u32(sem->value);
31✔
347
            const uint32_t dec_value = (old_value > count) ? count : old_value;
31✔
348

349
            if ( ! dec_value) {
31✔
350

UNCOV
351
                if ( ! suspended) {
×
UNCOV
352
                    KOS_suspend_context(ctx);
×
353

UNCOV
354
                    kos_lock_mutex(sem->mutex);
×
355

UNCOV
356
                    suspended = 1;
×
357

UNCOV
358
                    continue;
×
359
                }
360

UNCOV
361
                kos_wait_cond_var(sem->cond_var, sem->mutex);
×
362

UNCOV
363
                continue;
×
364
            }
365

366
            if ( ! KOS_atomic_cas_weak_u32(sem->value, old_value, old_value - dec_value))
31✔
367
                continue;
×
368

369
            count -= dec_value;
31✔
370
        } while (count);
31✔
371

372
        if (suspended) {
31✔
UNCOV
373
            kos_unlock_mutex(sem->mutex);
×
374

UNCOV
375
            KOS_resume_context(ctx);
×
376
        }
377
    }
378

379
    return KOS_destroy_top_local(ctx, &semaphore);
31✔
380
}
381

382
/* @item threads semaphore.prototype.release()
383
 *
384
 *     semaphore.prototype.release()
385
 *
386
 * Increments the semaphore value and signals other threads that may be
387
 * waiting on `acquire()`.
388
 *
389
 * Returns `this` semaphore object.
390
 */
391
static KOS_OBJ_ID semaphore_release(const KOS_CONTEXT             ctx,
16✔
392
                                    const KOS_OBJ_ID              this_obj,
393
                                    const uint32_t                num_args,
394
                                    KOS_ATOMIC(KOS_OBJ_ID) *const args)
395
{
396
    KOS_LOCAL      semaphore;
397
    KOS_SEMAPHORE *sem   = KOS_NULL;
16✔
398
    uint32_t       count = 0;
16✔
399

400
    KOS_init_local_with(ctx, &semaphore, this_obj);
16✔
401

402
    sem = (KOS_SEMAPHORE *)KOS_object_get_private(semaphore.o, &semaphore_priv_class);
16✔
403

404
    if (get_count_arg(ctx, args, &count)) {
16✔
405
        KOS_destroy_top_local(ctx, &semaphore);
2✔
406
        return KOS_BADPTR;
2✔
407
    }
408

409
    if (sem) {
14✔
410
        uint32_t old_value;
411
        uint32_t max_inc;
412

413
        KOS_suspend_context(ctx);
14✔
414

415
        kos_lock_mutex(sem->mutex);
14✔
416

417
        do {
418
            old_value = KOS_atomic_read_relaxed_u32(sem->value);
14✔
419
            max_inc   = (uint32_t)KOS_MAX_SEM - old_value;
14✔
420

421
            if (count > max_inc) {
14✔
422
                kos_unlock_mutex(sem->mutex);
1✔
423

424
                KOS_resume_context(ctx);
1✔
425

426
                KOS_destroy_top_local(ctx, &semaphore);
1✔
427

428
                KOS_raise_printf(ctx, "semaphore value %u cannot be increased by %u",
1✔
429
                                 old_value, count);
430
                return KOS_BADPTR;
1✔
431
            }
432

433
        } while ( ! KOS_atomic_cas_weak_u32(sem->value, old_value, old_value + count));
13✔
434

435
        kos_unlock_mutex(sem->mutex);
13✔
436

437
        kos_broadcast_cond_var(sem->cond_var);
13✔
438

439
        KOS_resume_context(ctx);
13✔
440
    }
441

442
    return KOS_destroy_top_local(ctx, &semaphore);
13✔
443
}
444

445
static KOS_OBJ_ID semaphore_value(const KOS_CONTEXT             ctx,
6✔
446
                                  const KOS_OBJ_ID              this_obj,
447
                                  const uint32_t                num_args,
448
                                  KOS_ATOMIC(KOS_OBJ_ID) *const args)
449
{
450
    KOS_SEMAPHORE *const sem = (KOS_SEMAPHORE *)KOS_object_get_private(this_obj, &semaphore_priv_class);
6✔
451

452
    if (sem) {
6✔
453
        const uint32_t value = KOS_atomic_read_relaxed_u32(sem->value);
6✔
454

455
        return KOS_new_int(ctx, (int64_t)value);
6✔
456
    }
457

458
    return KOS_VOID;
×
459
}
460

461
const KOS_CONVERT sleep_args[2] = {
462
    KOS_DEFINE_OPTIONAL_ARG(str_seconds, TO_SMALL_INT(0)),
463
    KOS_DEFINE_TAIL_ARG()
464
};
465

466
/* @item threads sleep()
467
 *
468
 *     sleep(seconds = 0)
469
 *
470
 * Sleeps the current thread for the specified number of seconds
471
 *
472
 * `seconds` specifies how long to sleep.  This can be an `integer` or a `float`.
473
 *
474
 * Returns `void`.
475
 */
476
static KOS_OBJ_ID kos_sleep(const KOS_CONTEXT             ctx,
7✔
477
                            const KOS_OBJ_ID              this_obj,
478
                            const uint32_t                num_args,
479
                            KOS_ATOMIC(KOS_OBJ_ID) *const args)
480
{
481
    KOS_NUMERIC arg;
482
    uint64_t    sleep_ns;
483
    int         error = KOS_SUCCESS;
7✔
484

485
    assert(num_args > 0);
7✔
486

487
    arg = KOS_get_numeric(args[0]);
7✔
488

489
    if (arg.type == KOS_INTEGER_VALUE) {
7✔
490
        if (arg.u.i < 0 || arg.u.i > 1000000) {
3✔
491
            KOS_raise_printf(ctx, "invalid sleep time %" PRId64 " seconds", arg.u.i);
2✔
492
            RAISE_ERROR(KOS_ERROR_EXCEPTION);
2✔
493
        }
494
        sleep_ns = (uint64_t)(arg.u.i * 1000000000);
1✔
495
    }
496
    else {
497
        if (arg.type == KOS_NON_NUMERIC)
4✔
498
            RAISE_EXCEPTION_STR(str_err_not_number);
2✔
499

500
        assert(arg.type == KOS_FLOAT_VALUE);
2✔
501

502
        if (arg.u.d < 0 || arg.u.d > 1000000.0) {
2✔
503
            KOS_raise_printf(ctx, "invalid sleep time %f seconds", arg.u.d);
1✔
504
            RAISE_ERROR(KOS_ERROR_EXCEPTION);
1✔
505
        }
506
        sleep_ns = (uint64_t)(arg.u.d * 1000000000.0);
1✔
507
    }
508

509
    KOS_suspend_context(ctx);
2✔
510

511
    KOS_sleep(sleep_ns);
2✔
512

513
    KOS_resume_context(ctx);
2✔
514

515
cleanup:
7✔
516
    return error ? KOS_BADPTR : KOS_VOID;
7✔
517
}
518

519
int kos_module_threads_init(KOS_CONTEXT ctx, KOS_OBJ_ID module_obj)
1✔
520
{
521
    int       error = KOS_SUCCESS;
1✔
522
    KOS_LOCAL module;
523
    KOS_LOCAL priv;
524
    KOS_LOCAL mutex_proto;
525
    KOS_LOCAL semaphore_proto;
526

527
    KOS_init_local_with(ctx, &module, module_obj);
1✔
528
    KOS_init_locals(    ctx, &priv, &mutex_proto, &semaphore_proto, kos_end_locals);
1✔
529

530
    priv.o = KOS_new_array(ctx, 2);
1✔
531
    TRY_OBJID(priv.o);
1✔
532

533
    KOS_atomic_write_relaxed_ptr(OBJPTR(MODULE, module.o)->priv, priv.o);
1✔
534

535
    TRY_ADD_CONSTRUCTOR(    ctx, module.o,                    "mutex",     mutex_ctor,        KOS_NULL, &mutex_proto.o);
1✔
536
    TRY_ADD_MEMBER_FUNCTION(ctx, module.o, mutex_proto.o,     "acquire",   mutex_acquire,     KOS_NULL);
1✔
537
    TRY_ADD_MEMBER_FUNCTION(ctx, module.o, mutex_proto.o,     "release",   mutex_release,     KOS_NULL);
1✔
538

539
    TRY_ADD_CONSTRUCTOR(    ctx, module.o,                    "semaphore", semaphore_ctor,    sem_args, &semaphore_proto.o);
1✔
540
    TRY_ADD_MEMBER_FUNCTION(ctx, module.o, semaphore_proto.o, "acquire",   semaphore_acquire, count_arg);
1✔
541
    TRY_ADD_MEMBER_FUNCTION(ctx, module.o, semaphore_proto.o, "release",   semaphore_release, count_arg);
1✔
542
    TRY_ADD_MEMBER_PROPERTY(ctx, module.o, semaphore_proto.o, "value",     semaphore_value,   KOS_NULL);
1✔
543

544
    TRY_ADD_FUNCTION(       ctx, module.o,                    "sleep",     kos_sleep,         sleep_args);
1✔
545

546
    TRY(KOS_array_write(ctx, priv.o, 0, mutex_proto.o));
1✔
547
    TRY(KOS_array_write(ctx, priv.o, 1, semaphore_proto.o));
1✔
548

549
cleanup:
1✔
550
    KOS_destroy_top_locals(ctx, &priv, &module);
1✔
551

552
    return error;
1✔
553
}
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