# Can mulle-allocator Be Used in Signal Handlers or with setjmp/longjmp?

> Discover if mulle-allocator is safe for signal handlers or setjmp longjmp. Learn how to use it signal-safely with custom configurations.

- Repository: [mulle-c/mulle-allocator](https://github.com/mulle-c/mulle-allocator)
- Tags: deep-dive
- Published: 2026-03-07

---

**No, mulle-allocator cannot be used safely in signal handlers with the default configuration because it relies on non-async-signal-safe `malloc`/`realloc`, but it works fine with `setjmp`/`longjmp` and can be made signal-safe with a custom allocator.**

The `mulle-c/mulle-allocator` library provides a thin, extensible wrapper around C memory allocation functions. While the default implementation forwards calls to the standard library, its architecture allows you to substitute async-signal-safe alternatives for use in constrained environments like signal handlers.

## Why the Default mulle-allocator Fails in Signal Handlers

The default allocator (`mulle_stdlib_allocator`) is **not async-signal-safe** because it delegates directly to `malloc`, `realloc`, and `free`. According to POSIX, these standard library functions are not safe to call from within a signal handler due to potential deadlocks if the signal interrupts an ongoing allocation.

In [`src/mulle-allocator.c`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.c) (lines 101–108), the core allocation wrapper demonstrates this dependency:

```c
static inline void *_mulle_allocator_malloc( struct mulle_allocator *p,
                                            size_t size)
{
    void *q = (*p->realloc)( NULL, size, p );   // ← uses realloc()
    if (MULLE_C_UNLIKELY(!q))
        (*p->fail)( p, NULL, size );           // ← abort()
    return q;
}

```

Because this code path invokes `realloc` (and by extension `malloc`), using `mulle_malloc` or `mulle_allocator_malloc` with the default allocator inside a signal handler invokes undefined behavior. The `fail` callback further compounds this issue by calling `abort()`, which is also not async-signal-safe, as evidenced in [`test/fails/fail-malloc-abort.c`](https://github.com/mulle-c/mulle-allocator/blob/main/test/fails/fail-malloc-abort.c).

## Using mulle-allocator with setjmp/longjmp

Unlike signal handlers, `setjmp`/`longjmp` impose no special restrictions on `mulle-allocator`. The library maintains **no hidden global state**; it operates solely on the function pointers stored within the `mulle_allocator` struct passed to each call.

When you call `mulle_allocator_malloc` before a `setjmp` and again after a `longjmp`, the allocator functions normally. The only consideration is standard C memory management: any pointer allocated before the jump but not freed becomes a leak, as `longjmp` unwinds the stack without calling destructors.

```c
#include <setjmp.h>
#include <mulle-allocator/mulle-allocator.h>

jmp_buf env;

void risky(void)
{
    char *p = mulle_malloc(128);   // allocate
    /* … something that may longjmp … */
    longjmp(env, 1);               // unwind stack, p is leaked (as always)
}

int main(void)
{
    if (setjmp(env) == 0) {
        risky();                   // first entry
    } else {
        /* after longjmp – we can still allocate */
        char *q = mulle_malloc(64);
        mulle_free(q);
    }
    return 0;
}

```

## Making mulle-allocator Safe for Signal Handlers

To use `mulle-allocator` inside a signal handler, you must provide a **custom allocator struct** that uses only async-signal-safe operations. The library's design explicitly supports this through its user-supplied function pointers.

A typical approach implements a lock-free bump allocator using a static buffer. This avoids `malloc` entirely and uses only signal-safe operations like accessing static storage and arithmetic.

```c
#include <mulle-allocator/mulle-allocator.h>
#include <signal.h>

/* ----- custom bump allocator ----- */
static void *sigalloc_realloc(void *ptr, size_t size,
                              struct mulle_allocator *a)
{
    (void)ptr; (void)a;
    static unsigned char buf[1024];
    static size_t pos;
    if (!size) return NULL;               // mimics free behavior
    if (pos + size > sizeof buf)          // out of space → fail
        return NULL;
    void *p = buf + pos;
    pos += size;
    return p;
}

static struct mulle_allocator sig_allocator = {
    .calloc  = NULL,
    .realloc = sigalloc_realloc,
    .free    = NULL,                      // nothing to free in bump allocator
    .fail    = mulle_allocation_fail,
    .abafree = mulle_allocator_no_aba_abort,
    .aba     = NULL
};

/* ----- signal handler ----- */
static void sig_handler(int sig)
{
    (void)sig;
    /* safe allocation inside a signal handler */
    char *msg = mulle_allocator_malloc(&sig_allocator, 32);
    if (msg) {
        msg[0] = 'H'; msg[1] = 'i'; msg[2] = '\0';
        /* cannot free – the allocator never frees */
    }
    _exit(0);        // async-signal-safe termination
}

int main(void)
{
    struct sigaction sa = { .sa_handler = sig_handler };
    sigaction(SIGUSR1, &sa, NULL);
    pause();
    return 0;
}

```

The global allocator instances defined in [`src/mulle-allocator.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.h)—such as `mulle_allocator_default`, `mulle_allocator_stdlib`, and `mulle_allocator_stdlib_nofree`—all use the standard library and are therefore unsafe for signal handlers. You must initialize your own `struct mulle_allocator` with signal-safe function pointers as shown above.

## Summary

- **Signal handlers:** The default `mulle-allocator` is unsafe because [`src/mulle-allocator.c`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.c) implements `_mulle_allocator_malloc` using non-async-signal-safe `realloc` and `abort` calls.
- **setjmp/longjmp:** Fully supported; the allocator is stateless and works across jumps, though you must manage memory leaks manually.
- **Customization:** You can create signal-safe allocators by providing custom `realloc`, `free`, and `fail` function pointers that use only async-signal-safe operations like static buffer management.
- **Key files:** [`src/mulle-allocator.c`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.c) contains the core wrapper logic, while [`src/mulle-allocator.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.h) defines the public API and global allocator instances.

## Frequently Asked Questions

### Is malloc async-signal-safe?

No. POSIX explicitly lists `malloc`, `realloc`, and `free` as functions that are **not** async-signal-safe. If a signal interrupts a thread currently executing inside `malloc`, calling `malloc` again in the handler can cause a deadlock due to internal locking.

### Can I use mulle_free in a signal handler?

Only if the allocator was configured with a signal-safe `free` implementation. The default `mulle_free` calls the standard `free`, which is not async-signal-safe. If you use a custom bump allocator that sets the `free` pointer to `NULL` or a no-op function, you avoid this risk, though you lose the ability to reclaim memory.

### What happens to allocations after longjmp?

Any memory allocated via `mulle_allocator_malloc` before a `longjmp` remains allocated after the jump, but the pointer variable holding the address may be invalid if it was a local automatic variable in the unwound stack frame. This is standard C behavior; `mulle-allocator` does not add any special cleanup or tracking, so you must free such memory before the jump or accept the leak.

### How do I create a signal-safe allocator?

Define a `struct mulle_allocator` with function pointers that use only async-signal-safe operations. Replace `realloc` with a bump allocator or mmap-based allocator that avoids locks, set `free` to `NULL` if you do not need deallocation, and ensure the `fail` callback uses `_exit` rather than `abort`. Pass this custom struct to `mulle_allocator_malloc` instead of using the default global instances.