Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

C guide

Rust is Deloxide’s primary interface, but C applications can use the same detector and tracked synchronization through include/deloxide.h.

Build the library and C API:

cargo build --release --features c-api

Add other features when needed:

cargo build --release --features \
  c-api,logging-and-visualization,lock-order-graph,stress-test

Include include/deloxide.h and link the produced static or dynamic deloxide library. Exact filenames and platform libraries depend on the target. The repository’s c_examples/basic_mutex.c is the smallest buildable example.

Initialization and callback

Initialize once before creating tracked objects:

#include "deloxide.h"
#include <stdio.h>

static void on_deadlock(const char *json) {
    fprintf(stderr, "Deloxide report: %s\n", json);
}

int main(void) {
    int rc = deloxide_init(NULL, on_deadlock);
    if (rc != 0) {
        fprintf(stderr, "deloxide_init failed: %d\n", rc);
        return 1;
    }

    /* create locks and threads */
    return 0;
}

The callback receives a borrowed NUL-terminated JSON string. Copy it if another thread must retain it; do not free it or keep the pointer after the callback returns. Keep callback work bounded.

Initialization returns 0 on success and 1 if it has already run. Invalid log paths and logger failures use negative codes. Passing a non-null log path without the logging feature returns -3; the public header currently omits that code.

Mutex

void *mutex = deloxide_create_mutex();
if (mutex == NULL) return 1;

if (deloxide_lock_mutex(mutex) != 0) return 1;
/* protected work */
if (deloxide_unlock_mutex(mutex) != 0) return 1;

deloxide_destroy_mutex(mutex);

LOCK_MUTEX(mutex) and UNLOCK_MUTEX(mutex) provide checked convenience macros that terminate on failure. Destroy a mutex only after every thread has stopped using it.

RwLock

void *state = deloxide_create_rwlock();
if (state == NULL) return 1;

if (deloxide_rw_lock_read(state) != 0) return 1;
/* read shared state */
if (deloxide_rw_unlock_read(state) != 0) return 1;

if (deloxide_rw_lock_write(state) != 0) return 1;
/* update shared state */
if (deloxide_rw_unlock_write(state) != 0) return 1;

deloxide_destroy_rwlock(state);

The RWLOCK_READ, RWUNLOCK_READ, RWLOCK_WRITE, and RWUNLOCK_WRITE macros are the shorter checked form. A thread may hold read guards for different RwLocks, but it must release each matching guard correctly.

Condition variables

A Deloxide condition variable waits with a Deloxide mutex:

void *mutex = deloxide_create_mutex();
void *ready = deloxide_create_condvar();

if (deloxide_lock_mutex(mutex) != 0) return 1;
while (!predicate_is_ready()) {
    int rc = deloxide_condvar_wait(ready, mutex);
    if (rc != 0) return 1;
}
if (deloxide_unlock_mutex(mutex) != 0) return 1;

deloxide_destroy_condvar(ready);
deloxide_destroy_mutex(mutex);

deloxide_condvar_wait_timeout returns 1 when the timeout expires and 0 when notified. Negative values indicate invalid handles, a mutex not held by the caller, or another wait failure. Notify with deloxide_condvar_notify_one or deloxide_condvar_notify_all.

Tracked threads

Any native thread using a Deloxide lock contributes synchronization events. Register lifecycle events when logs should also show the thread relationship:

uintptr_t tid = deloxide_get_thread_id();
deloxide_register_thread_spawn(tid, parent_tid);

/* thread work */

deloxide_register_thread_exit(tid);

On POSIX, DEFINE_TRACKED_THREAD(worker) and CREATE_TRACKED_THREAD(thread, worker, arg) wrap this protocol around pthread_create. Those macros are not available on Windows; call the manual registration functions from the Windows thread entry point.

Logging, visualization, and stress

With logging-and-visualization, pass a log path to deloxide_init, flush it with deloxide_flush_logs, and open it with deloxide_showcase or deloxide_showcase_current.

With stress-test, C can enable random scheduling delays with deloxide_enable_random_stress, enable targeted component delays with deloxide_enable_component_stress, and return to normal scheduling with deloxide_disable_stress.

The C header is the exact API reference. This chapter focuses on correct lifecycle and common usage rather than duplicating every status-code comment.