# spdlog Thread Pool Message Types: Understanding async_msg_type in gabime/spdlog

> Explore spdlog's `async_msg_type` and discover how log, flush, and terminate messages ensure efficient asynchronous logging, buffer flushing, and graceful thread termination.

- Repository: [Gabi Melman/spdlog](https://github.com/gabime/spdlog)
- Tags: internals
- Published: 2026-07-22

---

**The spdlog library processes three distinct message types—`log`, `flush`, and `terminate`—through its internal thread pool to enable asynchronous logging, explicit buffer flushing, and graceful worker thread shutdown.**

The spdlog library (gabime/spdlog) implements high-performance asynchronous logging via an internal thread pool that decouples log production from I/O operations. Each work item in this system is encapsulated in an `async_msg` structure tagged with a specific message type defined in [`include/spdlog/details/thread_pool.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/thread_pool.h). Understanding these message types is essential for debugging async behavior and extending the library’s functionality.

## The Three Message Types Defined

In [`include/spdlog/details/thread_pool.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/thread_pool.h) (lines 23–28), the `async_msg_type` enum class defines three distinct operational modes:

- **`async_msg_type::log`** – Represents a standard logging request containing formatted log data
- **`async_msg_type::flush`** – Signals the worker to synchronize all pending log entries for a specific logger
- **`async_msg_type::terminate`** – A control message that triggers graceful shutdown of the worker thread

### Log Messages (async_msg_type::log)

The `log` type is the most frequent message processed by the thread pool. When an async logger generates output, it posts an `async_msg` with `msg_type` set to `async_msg_type::log`. According to the implementation in [`include/spdlog/details/thread_pool-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/thread_pool-inl.h), the `process_next_msg_()` method handles this by invoking the logger's backend sink:

```cpp
case async_msg_type::log: {
    incoming_async_msg.worker_ptr->backend_sink_it_(incoming_async_msg);
    return true;  // Keep thread alive
}

```

### Flush Messages (async_msg_type::flush)

When `logger->flush()` is called on an async logger, the library enqueues a `flush` message. This triggers `backend_flush_()` on the target logger's worker, ensuring all buffered content reaches the destination sink before returning control. The implementation returns `true` to keep the worker thread running:

```cpp
case async_msg_type::flush: {
    incoming_async_msg.worker_ptr->backend_flush_();
    return true;
}

```

### Terminate Messages (async_msg_type::terminate)

During thread pool destruction, the system posts `terminate` messages to wake sleeping workers and signal them to exit their processing loops. Unlike log and flush operations, this handler returns `false` to break the main loop in `process_next_msg_()`:

```cpp
case async_msg_type::terminate: {
    return false;  // Exit processing loop
}

```

## Message Processing Logic in process_next_msg_()

The dispatch logic resides in [`include/spdlog/details/thread_pool-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/thread_pool-inl.h) within the `process_next_msg_()` method (lines 96–116). This method blocks on a queue until a message arrives, then uses a switch statement to route the work to the appropriate handler:

```cpp
switch (incoming_async_msg.msg_type) {
    case async_msg_type::log: {
        incoming_async_msg.worker_ptr->backend_sink_it_(incoming_async_msg);
        return true;
    }
    case async_msg_type::flush: {
        incoming_async_msg.worker_ptr->backend_flush_();
        return true;
    }
    case async_msg_type::terminate: {
        return false;
    }
    default: assert(false);
}

```

## Practical Usage Examples

### Posting Log Messages

When using an async logger, log messages automatically enter the thread pool as `log` type messages:

```cpp
#include <spdlog/spdlog.h>
#include <spdlog/async.h>

int main() {
    auto tp = std::make_shared<spdlog::details::thread_pool>(1024, 2);
    auto logger = std::make_shared<spdlog::async_logger>("my_logger", 
        spdlog::stdout_color_mt("console"), tp, spdlog::async_overflow_policy::block);
    
    // This posts async_msg_type::log to the thread pool
    logger->info("Processing user request");
}

```

### Requesting Explicit Flushes

To ensure critical log entries are written immediately, trigger a flush operation:

```cpp
logger->critical("System failure imminent");
logger->flush();  // Posts async_msg_type::flush and blocks until complete

```

### Thread Pool Lifecycle Management

The `terminate` message is handled automatically when the thread pool destructor runs:

```cpp
{
    spdlog::details::thread_pool tp{1024, 2};
    // Workers active...
}  // Destructor enqueues terminate messages for all workers

```

## Summary

- **Three message types**: The spdlog thread pool recognizes `log`, `flush`, and `terminate` messages defined in [`include/spdlog/details/thread_pool.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/thread_pool.h).
- **Log operations**: Standard logging requests invoke `backend_sink_it_()` to write formatted data through the sink chain.
- **Flush operations**: Explicit synchronization calls trigger `backend_flush_()` to drain pending buffers.
- **Graceful shutdown**: Terminate messages break the worker loop in `process_next_msg_()`, allowing threads to exit cleanly during pool destruction.

## Frequently Asked Questions

### What happens if I call flush() on an async logger?

When you invoke `flush()` on an async logger, the library posts a message with type `async_msg_type::flush` to the thread pool. The worker executes `backend_flush_()` to ensure all previously queued log messages are written to their destinations before the flush operation completes.

### How does spdlog ensure thread pool workers shut down cleanly?

During thread pool destruction, the destructor enqueues `async_msg_type::terminate` messages for each worker thread. The `process_next_msg_()` method returns `false` when processing a terminate message, breaking the main loop and allowing the thread function to return naturally.

### Can I create custom message types for the spdlog thread pool?

The `async_msg_type` enum is not designed for extension via user-defined types. The switch statement in [`include/spdlog/details/thread_pool-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/thread_pool-inl.h) explicitly handles only `log`, `flush`, and `terminate` cases, with a default `assert(false)` for unknown values. Extending functionality typically requires modifying the spdlog source or implementing a custom sink rather than adding message types.

### Where is the async_msg structure defined?

The `async_msg` structure and the `async_msg_type` enum are declared in [`include/spdlog/details/thread_pool.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/thread_pool.h) (lines 23–28). This structure encapsulates the message type, log content, and worker pointer necessary for the thread pool to route operations to the correct async logger instance.