# How mulle_allocator's Fail Handler Works and How to Customize It

> Discover how the mulle_allocator fail handler works and customize it globally or per-instance. Learn to manage allocation failures effectively in your mulle-allocator projects.

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

---

**The mulle_allocator fail handler is a function pointer stored in the `fail` field of every `struct mulle_allocator` that gets invoked when memory allocation fails; you can customize it globally using `mulle_allocator_set_fail()` or per-instance by directly assigning to the `fail` field.**

The mulle-c/mulle-allocator library provides deterministic memory management by ensuring allocation routines never return NULL on failure. Instead, every allocator stores a **fail handler** callback that executes whenever `mulle_allocator_malloc`, `mulle_allocator_calloc`, or `mulle_allocator_realloc` cannot satisfy a request, allowing applications to define custom out-of-memory behavior.

## How the Fail Handler Works

### The fail Field in struct mulle_allocator

The fail handler is stored as a function pointer inside `struct mulle_allocator` (defined in [`src/mulle-allocator-struct.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator-struct.h)). The field has the following signature:

```c
void (*fail)(struct mulle_allocator *, void *, size_t) _MULLE_C_NO_RETURN;

```

When any allocation routine cannot obtain memory, it invokes this handler with three arguments: the allocator instance, the original block pointer (if reallocating), and the requested size. As implemented in [`src/mulle-allocator.c`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.c), the check follows this pattern:

```c
if (MULLE_C_UNLIKELY(!q))
    (*p->fail)(p, block, size);

```

### Default Failure Behavior: mulle_allocation_fail

By default, every allocator points to `mulle_allocation_fail`, implemented in [`src/mulle-allocator.c`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.c) at lines 45-51. This handler prints the system error and immediately terminates the program:

```c
MULLE_C_NO_RETURN
void mulle_allocation_fail(struct mulle_allocator *p,
                           void *block,
                           size_t size)
{
    perror("memory allocation:");
    abort();

    MULLE_C_UNUSED(p);
    MULLE_C_UNUSED(block);
    MULLE_C_UNUSED(size);
}

```

Because the function is annotated with `_MULLE_C_NO_RETURN`, the compiler assumes the allocation helpers never need to handle execution after the fail call returns.

## How to Customize the Fail Handler

### Using mulle_allocator_set_fail for Safe Updates

The public API provides `mulle_allocator_set_fail` (declared in [`src/mulle-allocator.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.h), lines 80-88) to safely install a custom handler. This inline helper accepts NULL to mean "use default" and automatically targets the global default allocator when passed NULL:

```c
static inline void mulle_allocator_set_fail(struct mulle_allocator *p,
                                            mulle_allocator_fail_t *f _MULLE_C_NO_RETURN)
{
    if (!p)
        p = &mulle_allocator_default;
    p->fail = f ? f : mulle_allocation_fail;
}

```

To install a global handler that logs the failure size before aborting:

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

static void log_and_abort(struct mulle_allocator *a, void *b, size_t n)
    _MULLE_C_NO_RETURN
{
    fprintf(stderr, "[FAIL] Unable to allocate %zu bytes\n", n);
    abort();
}

int main(void)
{
    mulle_allocator_set_fail(&mulle_allocator_default, log_and_abort);
    void *p = mulle_malloc(SIZE_MAX);   // triggers handler
    (void)p;
    return 0;
}

```

### Direct Field Assignment for Per-Allocator Control

Since the `fail` field is public, you can modify it directly on specific allocator instances without affecting the global default. This technique is demonstrated in [`test/fails/fail-malloc.c`](https://github.com/mulle-c/mulle-allocator/blob/main/test/fails/fail-malloc.c):

```c
static void fail(struct mulle_allocator *allocator, void *unused, size_t ignored)
{
    printf("custom failure – terminating cleanly\n");
    exit(0);
}

int main(void)
{
    mulle_default_allocator.fail = fail;   // replaces default handler globally
    mulle_malloc(-1);                      // provokes failure
    return -1;
}

```

For isolated error handling, create a stack-based allocator copy with its own handler:

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

static void my_fail(struct mulle_allocator *a, void *b, size_t n)
    _MULLE_C_NO_RETURN
{
    fprintf(stderr, "my allocator failed for %zu bytes – cleaning up\n", n);
    exit(EXIT_FAILURE);
}

int main(void)
{
    struct mulle_allocator myalloc = mulle_allocator_default;
    myalloc.fail = my_fail;                                 // override only for this instance

    void *p = _mulle_allocator_malloc(&myalloc, 0);       // force failure
    (void)p;
}

```

## Practical Implementation Examples

### Global Logging Handler

Install a custom fail handler across your entire application to capture allocation statistics before termination:

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

static void log_and_abort(struct mulle_allocator *a, void *b, size_t n)
    _MULLE_C_NO_RETURN
{
    fprintf(stderr, "[FAIL] Unable to allocate %zu bytes\n", n);
    abort();               // terminates the program
}

int main(void)
{
    mulle_allocator_set_fail(&mulle_allocator_default, log_and_abort);
    /* This will trigger the custom handler */
    void *p = mulle_malloc(SIZE_MAX);   // unrealistic huge request
    (void)p;
    return 0;
}

```

### Per-Allocator Handler for Isolated Error Handling

When you need specific error behavior for a subsystem without affecting global allocations, copy the default allocator and override its `fail` field:

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

static void my_fail(struct mulle_allocator *a, void *b, size_t n)
    _MULLE_C_NO_RETURN
{
    fprintf(stderr, "my allocator failed for %zu bytes – cleaning up\n", n);
    // perform any cleanup needed, then exit
    exit(EXIT_FAILURE);
}

int main(void)
{
    struct mulle_allocator myalloc = mulle_allocator_default; // copy defaults
    myalloc.fail = my_fail;                                 // override only for this instance

    void *p = _mulle_allocator_malloc(&myalloc, 0);        // force failure
    (void)p;
}

```

### Test-Style Minimal Replacement

For testing failure paths, use a minimal handler that exits successfully rather than aborting:

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

static void quiet_success(struct mulle_allocator *a, void *b, size_t n)
    _MULLE_C_NO_RETURN
{
    printf("Allocation failed – but we consider it ok\n");
    exit(0);
}

int main(void)
{
    mulle_default_allocator.fail = quiet_success;   // replace globally
    mulle_malloc(-1);                              // triggers fail handler
}

```

## Summary

- **Storage**: Every `struct mulle_allocator` contains a `fail` function pointer field that stores the current failure handler.
- **Default**: The built-in `mulle_allocation_fail` handler in [`src/mulle-allocator.c`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.c) prints an error via `perror()` and calls `abort()`.
- **Customization**: Use `mulle_allocator_set_fail()` (from [`src/mulle-allocator.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.h)) for safe global updates, or assign directly to `allocator->fail` for per-instance control.
- **Parameters**: Handlers receive the allocator instance, the original block pointer (NULL for fresh allocations), and the requested size.
- **Contract**: Fail handlers must be annotated with `_MULLE_C_NO_RETURN`, indicating they terminate the process via `abort()`, `exit()`, `longjmp()`, or similar.

## Frequently Asked Questions

### What parameters does the mulle_allocator fail handler receive?

The fail handler receives three parameters: a pointer to the `struct mulle_allocator` that experienced the failure, the original `block` pointer being reallocated (or NULL for `malloc`/`calloc`), and the `size` in bytes that could not be allocated. These allow the handler to log context-specific error details or attempt recovery based on the allocator's state.

### Can I use different fail handlers for different allocators?

Yes. Because the `fail` field is part of `struct mulle_allocator`, you can create separate allocator instances—either on the stack or dynamically—and assign unique handlers to each. This allows subsystems to handle out-of-memory conditions independently without affecting the global default allocator used by the rest of the application.

### How do I restore the default fail handler?

Pass `NULL` to `mulle_allocator_set_fail()`, or explicitly assign `mulle_allocation_fail` to the `fail` field. The `mulle_allocator_set_fail` helper automatically reverts to the default implementation when passed a NULL function pointer, ensuring you do not leave the allocator in an undefined state.

### What happens if the fail handler actually returns?

The fail handler is annotated with `_MULLE_C_NO_RETURN`, which tells the compiler the function never returns. If you violate this contract and allow the handler to return normally, the behavior is undefined; typically, the allocation routine will fall through to subsequent code that assumes memory was allocated, likely causing immediate crashes or memory corruption. Always terminate execution or perform a non-local exit such as `longjmp()`.