ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
Packet Queue

📬 Thread-safe per-client packet queues More...

Data Structures

struct  queued_packet_t
 Single packet ready to send (header already in network byte order) More...
 
struct  packet_node
 Node in the packet queue linked list. More...
 
struct  node_pool
 Memory pool for packet nodes to reduce malloc/free overhead. More...
 
struct  packet_queue_t
 Thread-safe packet queue for producer-consumer communication. More...
 

Typedefs

typedef struct packet_node packet_node_t
 Forward declaration for packet queue node.
 
typedef struct node_pool node_pool_t
 Memory pool for packet nodes to reduce malloc/free overhead.
 

Variables

packet_header_t queued_packet_t::header
 Complete packet header (already in network byte order)
 
void * queued_packet_t::data
 Packet payload data (can be NULL for header-only packets)
 
size_t queued_packet_t::data_len
 Length of payload data in bytes.
 
bool queued_packet_t::owns_data
 If true, free data when packet is freed.
 
buffer_pool_t * queued_packet_t::buffer_pool
 Pool that allocated the data (NULL if malloc'd)
 
queued_packet_t packet_node::packet
 The queued packet data.
 
atomic_ptr_t packet_node::next
 Pointer to next node in linked list (NULL for tail) - atomic for lock-free operations.
 
atomic_ptr_t node_pool::free_list
 Stack of free nodes (LIFO for cache locality) - atomic for lock-free access.
 
packet_node_t * node_pool::nodes
 Pre-allocated array of all nodes.
 
size_t node_pool::pool_size
 Total number of nodes in pool.
 
atomic_t node_pool::used_count
 Number of nodes currently in use - atomic for lock-free tracking.
 
atomic_ptr_t packet_queue_t::head
 Front of queue (dequeue from here) - atomic for lock-free access.
 
atomic_ptr_t packet_queue_t::tail
 Back of queue (enqueue here) - atomic for lock-free access.
 
atomic_t packet_queue_t::count
 Number of packets currently in queue - atomic for lock-free access.
 
size_t packet_queue_t::max_size
 Maximum queue size (0 = unlimited)
 
atomic_t packet_queue_t::bytes_queued
 Total bytes of data queued (for monitoring) - atomic for lock-free access.
 
node_pool_t * packet_queue_t::node_pool
 Optional memory pool for nodes (NULL = use malloc/free)
 
buffer_pool_t * packet_queue_t::buffer_pool
 Optional memory pool for data buffers (NULL = use malloc/free)
 
atomic_t packet_queue_t::packets_enqueued
 Total packets enqueued (statistics) - atomic for lock-free access.
 
atomic_t packet_queue_t::packets_dequeued
 Total packets dequeued (statistics) - atomic for lock-free access.
 
atomic_t packet_queue_t::packets_dropped
 Total packets dropped due to queue full (statistics) - atomic for lock-free access.
 
atomic_t packet_queue_t::shutdown
 Shutdown flag (true = dequeue returns NULL) - atomic for lock-free access.
 

Node Pool Functions

node_pool_t * node_pool_create (size_t pool_size)
 Create a memory pool for packet queue nodes.
 
void node_pool_destroy (node_pool_t *pool)
 Destroy a node pool and free all memory.
 
packet_node_t * node_pool_get (node_pool_t *pool)
 Get a free node from the pool.
 
void node_pool_put (node_pool_t *pool, packet_node_t *node)
 Return a node to the pool.
 

Queue Management Functions

packet_queue_t * packet_queue_create (size_t max_size)
 Create a new packet queue.
 
packet_queue_t * packet_queue_create_with_pool (size_t max_size, size_t pool_size)
 Create a packet queue with node pool.
 
packet_queue_t * packet_queue_create_with_pools (size_t max_size, size_t node_pool_size, bool use_buffer_pool)
 Create a packet queue with both node and buffer pools.
 
void packet_queue_stop (packet_queue_t *queue)
 Destroy a packet queue and free all resources.
 

Queue Operations

int packet_queue_enqueue (packet_queue_t *queue, packet_type_t type, const void *data, size_t data_len, uint32_t client_id, bool copy_data)
 Enqueue a packet into the queue.
 
int packet_queue_enqueue_packet (packet_queue_t *queue, const queued_packet_t *packet)
 Enqueue a pre-built packet (for special cases like compressed frames)
 
queued_packet_t * packet_queue_dequeue (packet_queue_t *queue)
 Dequeue a packet from the queue (non-blocking)
 
queued_packet_t * packet_queue_try_dequeue (packet_queue_t *queue)
 Try to dequeue a packet without blocking.
 
void packet_queue_free_packet (queued_packet_t *packet)
 Free a dequeued packet.
 

Queue Status Functions

size_t packet_queue_size (packet_queue_t *queue)
 Get current number of packets in queue.
 
bool packet_queue_is_empty (packet_queue_t *queue)
 Check if queue is empty.
 
bool packet_queue_is_full (packet_queue_t *queue)
 Check if queue is full.
 

Queue Control Functions

void packet_queue_destroy (packet_queue_t *queue)
 Signal queue shutdown (causes dequeue to return NULL)
 
void packet_queue_clear (packet_queue_t *queue)
 Clear all packets from queue.
 

Statistics Functions

void packet_queue_get_stats (packet_queue_t *queue, uint64_t *enqueued, uint64_t *dequeued, uint64_t *dropped)
 Get queue statistics.
 
bool packet_queue_validate_packet (const queued_packet_t *packet)
 Validate packet integrity.
 

Detailed Description

📬 Thread-safe per-client packet queues

This module implements a high-performance thread-safe queue for network packets, enabling producer threads (audio mixer, video broadcast) to enqueue packets while consumer threads (per-client send threads) dequeue and transmit them. This design eliminates shared bottlenecks and enables linear scaling across multiple clients.

CORE RESPONSIBILITIES:

  1. Thread-safe packet queuing for producer-consumer architecture
  2. Per-client queue isolation (each client has separate audio/video queues)
  3. Memory-efficient packet node pooling to reduce malloc/free overhead
  4. Queue size limits with automatic packet dropping under load
  5. Graceful shutdown handling for clean thread termination
  6. Comprehensive statistics collection for performance monitoring

ARCHITECTURAL OVERVIEW:

PRODUCER-CONSUMER MODEL:

QUEUE DESIGN:

MEMORY MANAGEMENT:

THREAD SAFETY:

SYNCHRONIZATION PRIMITIVES:

LOCK-FREE PATTERN:

INTEGRATION WITH OTHER MODULES:

PERFORMANCE CHARACTERISTICS:

Note
Each client should have separate queues for audio and video to enable prioritization and prevent one stream from blocking the other.
When max_size > 0, full queues drop oldest packets automatically. This prevents memory exhaustion but may cause frame drops under load.
Queue shutdown causes dequeue operations to return NULL, allowing consumer threads to exit gracefully without blocking forever.
Warning
Always free dequeued packets with packet_queue_free_packet() to properly release memory (either to pool or free()).
Queue destruction does NOT free packets still in the queue. Clear the queue first or ensure all packets are dequeued.
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025
Version
2.0 (Post-Modularization)

Packet Queue

Overview

Welcome! Let's talk about packet queues—the unsung heroes that keep ascii-chat's network communication smooth and organized.

Imagine you're at a busy restaurant. Orders are coming in from multiple tables, and the kitchen needs to process them one at a time. You don't want orders getting mixed up or lost, right? That's exactly what packet queues do for network data—they line up incoming packets so each client's data gets processed in order, without chaos.

Each client connected to the server gets their own dedicated packet queue. When packets arrive over the network, they get placed in the appropriate client's queue. Then, a separate thread can process packets at its own pace without blocking the network thread. It's like having a separate order ticket for each table—clean, organized, and efficient.

Implementation: lib/packet_queue.c/h

What makes packet queues useful?

  • Per-client queues: Each client has their own queue (no cross-contamination!)
  • Thread-safe: Multiple threads can safely interact with the queue
  • Flexible ownership: Choose whether the queue copies data or just references it
  • Buffer pool integration: Works with our buffer pool for efficient memory usage
  • Statistics tracking: See how well your queues are performing

Architecture

Queue Structure

// Single packet node in the queue
typedef struct packet_node {
packet_header_t header; // Type, length, CRC, client_id
void *data; // Payload
struct packet_node *next; // Next packet in queue
// Per-client packet queue
typedef struct packet_queue {
packet_node_t *head; // Front of queue (dequeue here)
packet_node_t *tail; // Back of queue (enqueue here)
size_t size; // Current packet count
size_t max_size; // Maximum allowed packets (0 = unlimited)
bool owns_data; // If true, queue frees packet data
mutex_t mutex; // Thread safety
// Statistics
uint64_t packets_enqueued;
uint64_t packets_dequeued;
uint64_t packets_dropped; // Dropped due to max_size limit
size_t peak_size; // Peak queue depth
mutex_t mutex
unsigned long long uint64_t
Definition common.h:59
atomic_ptr_t next
Pointer to next node in linked list (NULL for tail) - atomic for lock-free operations.
Definition queue.h:151
struct packet_node packet_node_t
Forward declaration for packet queue node.
Definition queue.h:137
Mutex type (POSIX: pthread_mutex_t with debug tracking)
Network packet header structure.
Definition packet.h:598
Node in the packet queue linked list.
Definition queue.h:147
Thread-safe packet queue for producer-consumer communication.
Definition queue.h:211

Data Ownership

Queues can either copy packet data or reference it:

Deep copy mode (owns_data = true):

  • Queue allocates its own copy of packet data
  • Safe for producer to free original data immediately
  • Queue frees data when packet dequeued
  • Higher memory usage, but safer

Reference mode (owns_data = false):

  • Queue stores pointer to original data
  • Producer must keep data alive until dequeued
  • Consumer responsible for freeing data
  • Lower memory usage, but requires careful lifetime management
// Deep copy mode (safer) - copy_data=true
char *data = SAFE_MALLOC(1024, char*);
strcpy(data, "Hello");
packet_queue_enqueue(q, PACKET_TYPE_ASCII_FRAME, data, strlen(data), client_id, true);
SAFE_FREE(data); // Can free immediately since copy_data=true!
// Reference mode (lower overhead) - copy_data=false
char *data2 = SAFE_MALLOC(1024, char*);
strcpy(data2, "Hello");
packet_queue_enqueue(q2, PACKET_TYPE_ASCII_FRAME, data2, strlen(data2), client_id, false);
// data2 must stay alive until dequeued since copy_data=false!
// Dequeue returns a queued_packet_t pointer
if (packet) {
process_packet(packet->data);
packet_queue_free_packet(q2, packet); // Free packet properly
}
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
packet_queue_t * packet_queue_create(size_t max_size)
Create a new packet queue.
Definition queue.c:125
void packet_queue_free_packet(queued_packet_t *packet)
Free a dequeued packet.
Definition queue.c:568
int packet_queue_enqueue(packet_queue_t *queue, packet_type_t type, const void *data, size_t data_len, uint32_t client_id, bool copy_data)
Enqueue a packet into the queue.
Definition queue.c:209
queued_packet_t * packet_queue_dequeue(packet_queue_t *queue)
Dequeue a packet from the queue (non-blocking)
Definition queue.c:479
void * data
Packet payload data (can be NULL for header-only packets)
Definition queue.h:124
@ PACKET_TYPE_ASCII_FRAME
Complete ASCII frame with all metadata.
Definition packet.h:361
Single packet ready to send (header already in network byte order)
Definition queue.h:120

API Reference

Creation/Destruction

// Create packet queue
packet_queue_t *queue = packet_queue_create(100, true);
if (!queue) {
log_error("Failed to create packet queue");
return NULL;
}
// Use queue...
// Destroy queue
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
void packet_queue_destroy(packet_queue_t *queue)
Signal queue shutdown (causes dequeue to return NULL)
Definition queue.c:173

Integration

Buffer Pool Integration:

  • Packet queues use buffer pool for efficient memory allocation
  • Node pool pre-allocates packet nodes
  • Data pool pre-allocates packet data buffers
  • Reduces malloc/free overhead in high-throughput scenarios

Network Integration:

  • Each client has a dedicated packet queue
  • Network receive thread enqueues packets
  • Client processing thread dequeues packets
  • Decouples network I/O from packet processing

Performance

Throughput:

  • Enqueue: ~1M packets/second (mutex-protected)
  • Dequeue: ~1M packets/second (mutex-protected)
  • Memory overhead: ~40 bytes per queued packet

Scalability:

  • Per-client queues eliminate contention between clients
  • Statistics tracking has minimal overhead
  • Buffer pool reduces allocation overhead

Thread Safety

Mutex Protection:

  • All queue operations are mutex-protected
  • Thread-safe for concurrent enqueue/dequeue
  • No lock-free optimizations (simplicity over speed)

Concurrent Access:

  • Multiple threads can enqueue concurrently
  • Only one thread should dequeue (per queue)
  • Statistics are updated atomically
See also
packet_queue.h
buffer_pool.h

Typedef Documentation

◆ node_pool_t

typedef struct node_pool node_pool_t

#include <queue.h>

Memory pool for packet nodes to reduce malloc/free overhead.

Pre-allocates a fixed-size array of packet nodes and maintains a lock-free free list using atomic compare-and-swap operations. This eliminates per-packet malloc/free calls and avoids lock contention for high-frequency packet queue operations.

Note
Pool operations are thread-safe via lock-free atomics (no mutexes).
If pool is exhausted, operations fall back to malloc/free.

◆ packet_node_t

typedef struct packet_node packet_node_t

#include <queue.h>

Forward declaration for packet queue node.

Definition at line 137 of file queue.h.

Function Documentation

◆ node_pool_create()

node_pool_t * node_pool_create ( size_t  pool_size)

#include <queue.h>

Create a memory pool for packet queue nodes.

Parameters
pool_sizeNumber of nodes to pre-allocate
Returns
Pointer to node pool, or NULL on failure

Pre-allocates a fixed-size array of packet nodes to eliminate per-packet malloc/free overhead. All nodes are initialized and added to the free list.

Note
Pool size should be chosen based on expected queue depth. Too small causes fallback to malloc, too large wastes memory.

Definition at line 25 of file queue.c.

25 {
26 if (pool_size == 0) {
27 return NULL;
28 }
29
32
33 // Allocate all nodes at once
34 pool->nodes = SAFE_MALLOC(sizeof(packet_node_t) * pool_size, packet_node_t *);
35
36 // Link all nodes into free list (using atomic stores for consistency with atomic type)
37 for (size_t i = 0; i < pool_size - 1; i++) {
38 atomic_ptr_store(&pool->nodes[i].next, (void *)&pool->nodes[i + 1]);
39 }
40 atomic_ptr_store(&pool->nodes[pool_size - 1].next, NULL);
41
42 // Initialize atomics for lock-free operations
43 atomic_ptr_store(&pool->free_list, (void *)&pool->nodes[0]);
44 pool->pool_size = pool_size;
45 atomic_store_u64(&pool->used_count, 0);
46
47 NAMED_REGISTER_NODE_POOL(pool, "node_pool", NULL);
48
49 return pool;
50}
void atomic_ptr_store(atomic_ptr_t *a, void *value)
Atomically store a pointer.
Definition atomic.c:280
void atomic_store_u64(atomic_t *a, uint64_t value)
Atomically store a uint64_t value.
Definition atomic.c:241
buffer_pool_t * pool
#define NAMED_REGISTER_NODE_POOL(pool, name, parent_ptr)
Register a node pool with automatic format specifier.
atomic_ptr_t free_list
Lock-free stack of available buffers (void* cast from buffer_node_t*)
Definition buffer_pool.h:86
Memory pool for packet nodes to reduce malloc/free overhead.
Definition queue.h:167

References atomic_ptr_store(), atomic_store_u64(), buffer_pool::free_list, NAMED_REGISTER_NODE_POOL, pool, and SAFE_MALLOC.

Referenced by packet_queue_create_with_pools().

◆ node_pool_destroy()

void node_pool_destroy ( node_pool_t *  pool)

#include <queue.h>

Destroy a node pool and free all memory.

Parameters
poolNode pool to destroy (can be NULL)

Frees all pre-allocated nodes and the pool structure itself.

Warning
Pool must not be in use by any queues when destroyed.

Definition at line 52 of file queue.c.

52 {
53 if (!pool) {
54 return;
55 }
56
58
59 SAFE_FREE(pool->nodes);
61}
#define NAMED_UNREGISTER(ptr)
Unregister a pointer.

References NAMED_UNREGISTER, pool, and SAFE_FREE.

Referenced by packet_queue_destroy().

◆ node_pool_get()

packet_node_t * node_pool_get ( node_pool_t *  pool)

#include <queue.h>

Get a free node from the pool.

Parameters
poolNode pool
Returns
Pointer to free node, or NULL if pool is exhausted

Removes a node from the free list. If pool is exhausted, returns NULL (caller should fall back to malloc).

Note
Thread-safe (protected by pool_mutex).

Definition at line 63 of file queue.c.

63 {
64 if (!pool) {
65 // No pool, fallback to malloc
66 packet_node_t *node;
67 node = SAFE_MALLOC(sizeof(packet_node_t), packet_node_t *);
68 return node;
69 }
70
71 // Lock-free pop from free_list stack using CAS (same pattern as buffer_pool)
73 while (node) {
75 if (atomic_ptr_cas(&pool->free_list, (void **)&node, next)) {
76 // Successfully popped - clear next pointer and increment used_count
77 atomic_ptr_store(&node->next, (void *)(packet_node_t *)NULL);
78 atomic_fetch_add_u64(&pool->used_count, 1);
79 return node;
80 }
81 // CAS failed - reload and retry (another thread grabbed the node)
83 }
84
85 // Pool exhausted, fallback to malloc
86 node = SAFE_MALLOC(sizeof(packet_node_t), packet_node_t *);
87 size_t used = atomic_load_u64(&pool->used_count);
88 log_debug("Memory pool exhausted, falling back to SAFE_MALLOC (used: %zu/%zu)", used, pool->pool_size);
89
90 return node;
91}
uint64_t atomic_fetch_add_u64(atomic_t *a, uint64_t delta)
Atomically add to a uint64_t and return the previous value.
Definition atomic.c:248
void * atomic_ptr_load(atomic_ptr_t *a)
Atomically load a pointer.
Definition atomic.c:272
uint64_t atomic_load_u64(atomic_t *a)
Atomically load a uint64_t value.
Definition atomic.c:233
bool atomic_ptr_cas(atomic_ptr_t *a, void **expected, void *new_value)
Atomically compare-and-swap a pointer.
Definition atomic.c:287
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548

References atomic_fetch_add_u64(), atomic_load_u64(), atomic_ptr_cas(), atomic_ptr_load(), atomic_ptr_store(), buffer_pool::free_list, log_debug, packet_node::next, pool, and SAFE_MALLOC.

Referenced by packet_queue_enqueue(), and packet_queue_enqueue_packet().

◆ node_pool_put()

void node_pool_put ( node_pool_t *  pool,
packet_node_t *  node 
)

#include <queue.h>

Return a node to the pool.

Parameters
poolNode pool
nodeNode to return (must have been allocated from this pool)

Returns a node to the free list for reuse. Node should not be in use by any queue when returned.

Note
Thread-safe (protected by pool_mutex).
Warning
Do not return nodes that are still in use by queues.

Definition at line 93 of file queue.c.

93 {
94 if (!node) {
95 return;
96 }
97
98 if (!pool) {
99 // No pool, just free
100 SAFE_FREE(node);
101 return;
102 }
103
104 // Check if this node is from our pool
105 bool is_pool_node = (node >= pool->nodes && node < pool->nodes + pool->pool_size);
106
107 if (is_pool_node) {
108 // Lock-free push to free_list stack using CAS
110 do {
111 atomic_ptr_store(&node->next, (void *)head);
112 } while (!atomic_ptr_cas(&pool->free_list, (void **)&head, node));
113 atomic_fetch_sub_u64(&pool->used_count, 1);
114 } else {
115 // This was malloc'd, so free it
116 SAFE_FREE(node);
117 }
118}
uint64_t atomic_fetch_sub_u64(atomic_t *a, uint64_t delta)
Atomically subtract from a uint64_t and return the previous value.
Definition atomic.c:256

References atomic_fetch_sub_u64(), atomic_ptr_cas(), atomic_ptr_load(), atomic_ptr_store(), buffer_pool::free_list, packet_node::next, pool, and SAFE_FREE.

Referenced by packet_queue_enqueue(), packet_queue_enqueue_packet(), and packet_queue_try_dequeue().

◆ packet_queue_clear()

void packet_queue_clear ( packet_queue_t *  queue)

#include <queue.h>

Clear all packets from queue.

Parameters
queuePacket queue

Removes and frees all packets currently in the queue. Useful for cleanup before queue destruction or when resetting queue state.

Note
Thread-safe (lock-free using atomic operations).
This function frees all queued packets, so ensure nothing is still referencing them.

Definition at line 626 of file queue.c.

626 {
627 if (!queue)
628 return;
629
630 // Lock-free clear: drain queue by repeatedly dequeuing until empty
631 queued_packet_t *packet;
632 while ((packet = packet_queue_try_dequeue(queue)) != NULL) {
634 }
635}
queued_packet_t * packet_queue_try_dequeue(packet_queue_t *queue)
Try to dequeue a packet without blocking.
Definition queue.c:484

References packet_queue_free_packet(), and packet_queue_try_dequeue().

Referenced by packet_queue_destroy().

◆ packet_queue_create()

packet_queue_t * packet_queue_create ( size_t  max_size)

#include <queue.h>

Create a new packet queue.

Parameters
max_sizeMaximum queue size (0 = unlimited)
Returns
Pointer to new queue, or NULL on failure

Creates a packet queue without memory pools. Queue will use malloc/free for all node and data allocations.

Note
For high-performance scenarios, use packet_queue_create_with_pools() instead to enable zero-allocation operation.

Definition at line 125 of file queue.c.

125 {
126 return packet_queue_create_with_pool(max_size, 0); // No pool by default
127}
packet_queue_t * packet_queue_create_with_pool(size_t max_size, size_t pool_size)
Create a packet queue with node pool.
Definition queue.c:129

References packet_queue_create_with_pool().

Referenced by add_client().

◆ packet_queue_create_with_pool()

packet_queue_t * packet_queue_create_with_pool ( size_t  max_size,
size_t  pool_size 
)

#include <queue.h>

Create a packet queue with node pool.

Parameters
max_sizeMaximum queue size (0 = unlimited)
pool_sizeNumber of nodes to pre-allocate in pool
Returns
Pointer to new queue, or NULL on failure

Creates a packet queue with optional node pool to reduce malloc overhead. Queue will still use malloc/free for packet data allocations.

Note
For zero-allocation operation, use packet_queue_create_with_pools() with use_buffer_pool = true.

Definition at line 129 of file queue.c.

129 {
130 return packet_queue_create_with_pools(max_size, pool_size, false);
131}
packet_queue_t * packet_queue_create_with_pools(size_t max_size, size_t node_pool_size, bool use_buffer_pool)
Create a packet queue with both node and buffer pools.
Definition queue.c:133

References packet_queue_create_with_pools().

Referenced by packet_queue_create().

◆ packet_queue_create_with_pools()

packet_queue_t * packet_queue_create_with_pools ( size_t  max_size,
size_t  node_pool_size,
bool  use_buffer_pool 
)

#include <queue.h>

Create a packet queue with both node and buffer pools.

Parameters
max_sizeMaximum queue size (0 = unlimited)
node_pool_sizeNumber of nodes to pre-allocate
use_buffer_poolIf true, use global buffer pool for packet data
Returns
Pointer to new queue, or NULL on failure

Creates a high-performance packet queue with both node and buffer pools enabled. This enables zero-allocation operation when pools are configured.

Note
When use_buffer_pool is true, queue uses global buffer pool singleton. Buffer pool must be initialized before queue creation.

Definition at line 133 of file queue.c.

133 {
134 packet_queue_t *queue;
135 queue = SAFE_MALLOC(sizeof(packet_queue_t), packet_queue_t *);
136
137 // Initialize atomic fields
138 // For atomic pointer types, use atomic_store with relaxed ordering for initialization
139 atomic_ptr_store(&queue->head, (void *)(packet_node_t *)NULL);
140 atomic_ptr_store(&queue->tail, (void *)(packet_node_t *)NULL);
141 atomic_store_u64(&queue->count, 0);
142 queue->max_size = max_size;
143 atomic_store_u64(&queue->bytes_queued, 0);
144
145 // Create memory pools if requested
146 queue->node_pool = node_pool_size > 0 ? node_pool_create(node_pool_size) : NULL;
147 queue->buffer_pool = use_buffer_pool ? buffer_pool_create(0, 0) : NULL;
148
149 // Initialize atomic statistics
153 atomic_store_bool(&queue->shutdown, false);
154
155 NAMED_REGISTER_PACKET_QUEUE(queue, "packet_queue", NULL);
156 if (queue->node_pool) {
157 NAMED_REGISTER_NODE_POOL(queue->node_pool, "node_pool", (uintptr_t)(const void *)(queue));
158 }
159
160 // Register atomic fields for sync state monitoring - descriptive names for lock-free queue tracking
161 NAMED_REGISTER_ATOMIC(&queue->count, "current_packet_count", (uintptr_t)(const void *)(queue));
162 NAMED_REGISTER_ATOMIC(&queue->bytes_queued, "total_bytes_currently_queued", (uintptr_t)(const void *)(queue));
163 NAMED_REGISTER_ATOMIC(&queue->packets_enqueued, "lifetime_packets_enqueued_total", (uintptr_t)(const void *)(queue));
164 NAMED_REGISTER_ATOMIC(&queue->packets_dequeued, "lifetime_packets_dequeued_total", (uintptr_t)(const void *)(queue));
165 NAMED_REGISTER_ATOMIC(&queue->packets_dropped, "lifetime_packets_dropped_overflow", (uintptr_t)(const void *)(queue));
166 NAMED_REGISTER_ATOMIC(&queue->shutdown, "is_shutdown_requested", (uintptr_t)(const void *)(queue));
167 NAMED_REGISTER_ATOMIC_PTR(&queue->head, "head_node_pointer", (uintptr_t)(const void *)(queue));
168 NAMED_REGISTER_ATOMIC_PTR(&queue->tail, "tail_node_pointer", (uintptr_t)(const void *)(queue));
169
170 return queue;
171}
void atomic_store_bool(atomic_t *a, bool value)
Atomically store a boolean value.
Definition atomic.c:177
buffer_pool_t * buffer_pool_create(size_t max_bytes, uint64_t shrink_delay_ns)
Create a new buffer pool.
Definition buffer_pool.c:51
#define NAMED_REGISTER_ATOMIC(a, name, parent_ptr)
Register an atomic_t with automatic format specifier.
#define NAMED_REGISTER_PACKET_QUEUE(queue, name, parent_ptr)
Register a packet queue with automatic format specifier.
#define NAMED_REGISTER_ATOMIC_PTR(a, name, parent_ptr)
Register an _Atomic(void *) with automatic format specifier.
size_t max_size
Maximum queue size (0 = unlimited)
Definition queue.h:219
atomic_t packets_dequeued
Total packets dequeued (statistics) - atomic for lock-free access.
Definition queue.h:231
buffer_pool_t * buffer_pool
Optional memory pool for data buffers (NULL = use malloc/free)
Definition queue.h:226
atomic_t packets_enqueued
Total packets enqueued (statistics) - atomic for lock-free access.
Definition queue.h:229
atomic_ptr_t head
Front of queue (dequeue from here) - atomic for lock-free access.
Definition queue.h:213
atomic_t bytes_queued
Total bytes of data queued (for monitoring) - atomic for lock-free access.
Definition queue.h:221
atomic_t count
Number of packets currently in queue - atomic for lock-free access.
Definition queue.h:217
atomic_ptr_t tail
Back of queue (enqueue here) - atomic for lock-free access.
Definition queue.h:215
atomic_t packets_dropped
Total packets dropped due to queue full (statistics) - atomic for lock-free access.
Definition queue.h:233
atomic_t shutdown
Shutdown flag (true = dequeue returns NULL) - atomic for lock-free access.
Definition queue.h:236
node_pool_t * node_pool
Optional memory pool for nodes (NULL = use malloc/free)
Definition queue.h:224
node_pool_t * node_pool_create(size_t pool_size)
Create a memory pool for packet queue nodes.
Definition queue.c:25

References atomic_ptr_store(), atomic_store_bool(), atomic_store_u64(), packet_queue_t::buffer_pool, buffer_pool_create(), packet_queue_t::bytes_queued, packet_queue_t::count, packet_queue_t::head, packet_queue_t::max_size, NAMED_REGISTER_ATOMIC, NAMED_REGISTER_ATOMIC_PTR, NAMED_REGISTER_NODE_POOL, NAMED_REGISTER_PACKET_QUEUE, packet_queue_t::node_pool, node_pool_create(), packet_queue_t::packets_dequeued, packet_queue_t::packets_dropped, packet_queue_t::packets_enqueued, SAFE_MALLOC, packet_queue_t::shutdown, and packet_queue_t::tail.

Referenced by add_webrtc_client(), and packet_queue_create_with_pool().

◆ packet_queue_dequeue()

queued_packet_t * packet_queue_dequeue ( packet_queue_t *  queue)

#include <queue.h>

Dequeue a packet from the queue (non-blocking)

Parameters
queuePacket queue
Returns
Pointer to dequeued packet, or NULL if queue is empty or shutdown

Removes and returns the packet at the head of the queue. Returns NULL immediately if queue is empty or shutdown (non-blocking operation).

Note
Caller must free the returned packet with packet_queue_free_packet() to properly release memory (either to pool or free()).
Returns NULL when queue is empty or shutdown (allows consumer threads to exit). Caller should retry periodically if queue is expected to have data.
Thread-safe (lock-free using atomic operations).
Warning
Always free dequeued packets with packet_queue_free_packet().

Definition at line 479 of file queue.c.

479 {
480 // Non-blocking dequeue (same as try_dequeue for lock-free design)
481 return packet_queue_try_dequeue(queue);
482}

References packet_queue_try_dequeue().

◆ packet_queue_destroy()

void packet_queue_destroy ( packet_queue_t *  queue)

#include <queue.h>

Signal queue shutdown (causes dequeue to return NULL)

Parameters
queuePacket queue

Sets the shutdown flag, causing all dequeue operations to return NULL. This allows consumer threads to exit gracefully without blocking forever.

Note
After shutdown, enqueue operations still succeed, but dequeue returns NULL.
Thread-safe (wakes all waiting dequeue threads).

Definition at line 173 of file queue.c.

173 {
174 if (!queue)
175 return;
176
177 // Unregister atomic fields
178 NAMED_UNREGISTER(&queue->count);
183 NAMED_UNREGISTER(&queue->shutdown);
184 NAMED_UNREGISTER(&queue->head);
185 NAMED_UNREGISTER(&queue->tail);
186
187 NAMED_UNREGISTER(queue);
188
189 // Signal shutdown first
190 packet_queue_stop(queue);
191
192 // Clear any remaining packets
193 packet_queue_clear(queue);
194
195 // Destroy memory pools if present
196 if (queue->node_pool) {
198 }
199 if (queue->buffer_pool) {
200 buffer_pool_log_stats(queue->buffer_pool, "packet_queue");
202 }
203
204 // No mutex/cond to destroy (lock-free design)
205
206 SAFE_FREE(queue);
207}
void buffer_pool_destroy(buffer_pool_t *pool)
Destroy a buffer pool and free all memory.
void buffer_pool_log_stats(buffer_pool_t *pool, const char *name)
Log pool statistics.
void packet_queue_clear(packet_queue_t *queue)
Clear all packets from queue.
Definition queue.c:626
void packet_queue_stop(packet_queue_t *queue)
Destroy a packet queue and free all resources.
Definition queue.c:616
void node_pool_destroy(node_pool_t *pool)
Destroy a node pool and free all memory.
Definition queue.c:52

References packet_queue_t::buffer_pool, buffer_pool_destroy(), buffer_pool_log_stats(), packet_queue_t::bytes_queued, packet_queue_t::count, packet_queue_t::head, NAMED_UNREGISTER, packet_queue_t::node_pool, node_pool_destroy(), packet_queue_clear(), packet_queue_stop(), packet_queue_t::packets_dequeued, packet_queue_t::packets_dropped, packet_queue_t::packets_enqueued, SAFE_FREE, packet_queue_t::shutdown, and packet_queue_t::tail.

Referenced by add_client(), and cleanup_client_packet_queues().

◆ packet_queue_enqueue()

int packet_queue_enqueue ( packet_queue_t *  queue,
packet_type_t  type,
const void *  data,
size_t  data_len,
uint32_t  client_id,
bool  copy_data 
)

#include <queue.h>

Enqueue a packet into the queue.

Parameters
queuePacket queue
typePacket type (from packet_types.h)
dataPacket payload data (can be NULL)
data_lenLength of payload data in bytes
client_idClient ID for packet header
copy_dataIf true, copy data into pool/malloc buffer; if false, use data pointer directly
Returns
0 on success, -1 on error

Enqueues a new packet at the tail of the queue. If copy_data is true, packet data is copied into a buffer (from pool if available, otherwise malloc). If copy_data is false, the data pointer is used directly (caller must ensure data remains valid until packet is dequeued and freed).

Note
If queue is full (max_size > 0 and count >= max_size), oldest packet is automatically dropped to make room (packet count does not exceed max_size).
Thread-safe (lock-free using atomic operations).
Warning
When copy_data is false, caller must ensure data pointer remains valid until packet is freed.

Definition at line 209 of file queue.c.

210 {
211 if (!queue)
212 return -1;
213
214 // Check if shutdown (atomic read with acquire semantics)
215 if (atomic_load_bool(&queue->shutdown)) {
216 return -1;
217 }
218
219 // Check if queue is full and drop oldest packet if needed (lock-free)
220 size_t current_count = atomic_load_u64(&queue->count);
221 if (queue->max_size > 0 && current_count >= queue->max_size) {
222 // Drop oldest packet (head) using atomic compare-and-swap
224 if (head) {
226 // Atomically update head pointer
227 if (atomic_ptr_cas(&queue->head, (void **)&head, next)) {
228 // Successfully claimed head node
229 if (next == NULL) {
230 // Queue became empty, also update tail
231 atomic_ptr_store(&queue->tail, (void *)(packet_node_t *)NULL);
232 }
233
234 // Update counters atomically
235 size_t bytes = head->packet.data_len;
236 atomic_fetch_sub_u64(&queue->bytes_queued, bytes);
237 atomic_fetch_sub_u64(&queue->count, 1);
239
240 // Free dropped packet data
241 if (head->packet.owns_data && head->packet.data) {
243 }
244 node_pool_put(queue->node_pool, head);
245
246 log_dev_every(4500 * US_PER_MS_INT, "Dropped packet from queue (full): type=%d, client=%u", type, client_id);
247 }
248 // If CAS failed, another thread already dequeued - continue to enqueue
249 }
250 }
251
252 // Create new node (use pool if available)
253 packet_node_t *node = node_pool_get(queue->node_pool);
254 if (!node) {
255 SET_ERRNO(ERROR_MEMORY, "Failed to allocate packet node");
256 return -1;
257 }
258
259 // Build packet header
262 node->packet.header.length = HOST_TO_NET_U32((uint32_t)data_len);
263 node->packet.header.client_id = HOST_TO_NET_U32(client_id);
264 // Calculate CRC32 for the data (0 for empty packets)
265 node->packet.header.crc32 = HOST_TO_NET_U32(data_len > 0 ? asciichat_crc32(data, data_len) : 0);
266
267 // Handle data
268 if (data_len > 0 && data) {
269 if (copy_data) {
270 // Try to allocate from buffer pool (local or global)
271 if (queue->buffer_pool) {
272 node->packet.data = buffer_pool_alloc(queue->buffer_pool, data_len);
273 node->packet.buffer_pool = queue->buffer_pool;
274 } else {
275 // Use global pool if no local pool
276 node->packet.data = buffer_pool_alloc(NULL, data_len);
278 }
279 SAFE_MEMCPY(node->packet.data, data_len, data, data_len);
280 node->packet.owns_data = true;
281 } else {
282 // Use the data pointer directly (caller must ensure it stays valid)
283 node->packet.data = (void *)data;
284 node->packet.owns_data = false;
285 node->packet.buffer_pool = NULL;
286 }
287 } else {
288 node->packet.data = NULL;
289 node->packet.owns_data = false;
290 node->packet.buffer_pool = NULL;
291 }
292
293 node->packet.data_len = data_len;
294 atomic_ptr_store(&node->next, (void *)(packet_node_t *)NULL);
295
296 // Add to queue using lock-free CAS-based enqueue (Michael-Scott algorithm)
297 while (true) {
299
300 if (tail == NULL) {
301 // Empty queue - atomically set both head and tail
302 packet_node_t *expected = NULL;
303 if (atomic_ptr_cas(&queue->head, (void **)&expected, node)) {
304 // Successfully set head (queue was empty)
305 atomic_ptr_store(&queue->tail, (void *)node);
306 break; // Enqueue successful
307 }
308 // CAS failed - another thread initialized queue, retry
309 continue;
310 }
311
312 // Queue is non-empty - try to append to tail
314 packet_node_t *current_tail = (packet_node_t *)atomic_ptr_load(&queue->tail);
315
316 // Verify tail hasn't changed (ABA problem mitigation)
317 if (tail != current_tail) {
318 // Tail was updated by another thread, retry
319 continue;
320 }
321
322 if (next == NULL) {
323 // Tail is actually the last node - try to link new node
324 packet_node_t *expected_null = NULL;
325 if (atomic_ptr_cas(&tail->next, (void **)&expected_null, node)) {
326 // Successfully linked node - try to swing tail forward (best-effort, ignore failure)
327 atomic_ptr_cas(&queue->tail, (void **)&tail, node);
328 break; // Enqueue successful
329 }
330 // CAS failed - another thread appended to tail, retry
331 } else {
332 // Tail is lagging behind - help advance it
333 atomic_ptr_cas(&queue->tail, (void **)&tail, next);
334 // Retry with new tail
335 }
336 }
337
338 // Update counters atomically
339 atomic_fetch_add_u64(&queue->count, (size_t)1);
340 atomic_fetch_add_u64(&queue->bytes_queued, data_len);
342
343 return 0;
344}
bool atomic_load_bool(atomic_t *a)
Atomically load a boolean value.
Definition atomic.c:169
#define HOST_TO_NET_U16(val)
Definition endian.h:96
#define HOST_TO_NET_U32(val)
Definition endian.h:66
#define HOST_TO_NET_U64(val)
Definition endian.h:213
buffer_pool_t * buffer_pool_get_global(void)
void buffer_pool_free(buffer_pool_t *pool, const void *data, size_t size)
Free a buffer back to the pool (lock-free)
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
Allocate a buffer from the pool (lock-free fast path)
unsigned short uint16_t
Definition common.h:57
unsigned int uint32_t
Definition common.h:58
#define SAFE_MEMCPY(dest, dest_size, src, count)
Definition common.h:468
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
@ ERROR_MEMORY
Definition error_codes.h:56
#define US_PER_MS_INT
Definition time.h:160
packet_node_t * node_pool_get(node_pool_t *pool)
Get a free node from the pool.
Definition queue.c:63
bool owns_data
If true, free data when packet is freed.
Definition queue.h:128
size_t data_len
Length of payload data in bytes.
Definition queue.h:126
packet_header_t header
Complete packet header (already in network byte order)
Definition queue.h:122
void node_pool_put(node_pool_t *pool, packet_node_t *node)
Return a node to the pool.
Definition queue.c:93
buffer_pool_t * buffer_pool
Pool that allocated the data (NULL if malloc'd)
Definition queue.h:130
queued_packet_t packet
The queued packet data.
Definition queue.h:149
#define PACKET_MAGIC
Packet magic number (alias for MAGIC_PACKET_VALID)
Definition packet.h:255
#define asciichat_crc32(data, len)
Main CRC32 dispatcher macro - use this in application code.
Definition crc32.h:144
#define log_dev_every(interval_us, fmt,...)
Rate-limited DEV logging.
Definition log/log.h:699
uint32_t client_id
Client ID (0 = server, >0 = client identifier)
Definition packet.h:608
uint32_t length
Payload data length in bytes (0 for header-only packets)
Definition packet.h:604
uint64_t magic
Magic number (PACKET_MAGIC = 0xA5C11C4A1 "ASCIICHAT" in hex) for packet validation.
Definition packet.h:600
uint16_t type
Packet type (packet_type_t enumeration)
Definition packet.h:602
uint32_t crc32
CRC32 checksum of payload data (0 if length == 0)
Definition packet.h:606

References asciichat_crc32, atomic_fetch_add_u64(), atomic_fetch_sub_u64(), atomic_load_bool(), atomic_load_u64(), atomic_ptr_cas(), atomic_ptr_load(), atomic_ptr_store(), queued_packet_t::buffer_pool, packet_queue_t::buffer_pool, buffer_pool_alloc(), buffer_pool_free(), buffer_pool_get_global(), packet_queue_t::bytes_queued, packet_header_t::client_id, packet_queue_t::count, packet_header_t::crc32, queued_packet_t::data, queued_packet_t::data_len, ERROR_MEMORY, packet_queue_t::head, queued_packet_t::header, HOST_TO_NET_U16, HOST_TO_NET_U32, HOST_TO_NET_U64, packet_header_t::length, log_dev_every, packet_header_t::magic, packet_queue_t::max_size, packet_node::next, packet_queue_t::node_pool, node_pool_get(), node_pool_put(), queued_packet_t::owns_data, packet_node::packet, PACKET_MAGIC, packet_queue_t::packets_dropped, packet_queue_t::packets_enqueued, SAFE_MEMCPY, SET_ERRNO, packet_queue_t::shutdown, packet_queue_t::tail, packet_header_t::type, and US_PER_MS_INT.

Referenced by client_audio_render_thread(), client_receive_thread(), and queue_audio_for_client().

◆ packet_queue_enqueue_packet()

int packet_queue_enqueue_packet ( packet_queue_t *  queue,
const queued_packet_t *  packet 
)

#include <queue.h>

Enqueue a pre-built packet (for special cases like compressed frames)

Parameters
queuePacket queue
packetPre-built packet structure
Returns
0 on success, -1 on error

Enqueues a packet that has already been built (header, data, etc.). Useful for special cases like compressed frames where packet construction is done outside the normal enqueue path.

Note
Packet is copied into the queue, but data ownership semantics follow the packet's owns_data flag.
Thread-safe (lock-free using atomic operations).

Definition at line 346 of file queue.c.

346 {
347 if (!queue || !packet) {
348 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: queue=%p, packet=%p", queue, packet);
349 return -1;
350 }
351
352 // Validate packet before enqueueing
353 if (!packet_queue_validate_packet(packet)) {
354 SET_ERRNO(ERROR_INVALID_PARAM, "Refusing to enqueue invalid packet");
355 return -1;
356 }
357
358 // Check if shutdown (atomic read with acquire semantics)
359 if (atomic_load_bool(&queue->shutdown)) {
360 return -1;
361 }
362
363 // Check if queue is full and drop oldest packet if needed (lock-free)
364 size_t current_count = atomic_load_u64(&queue->count);
365 if (queue->max_size > 0 && current_count >= queue->max_size) {
366 // Drop oldest packet (head) using atomic compare-and-swap
368 if (head) {
370 // Atomically update head pointer
371 if (atomic_ptr_cas(&queue->head, (void **)&head, next)) {
372 // Successfully claimed head node
373 if (next == NULL) {
374 // Queue became empty, also update tail
375 atomic_ptr_store(&queue->tail, (void *)(packet_node_t *)NULL);
376 }
377
378 // Update counters atomically
379 size_t bytes = head->packet.data_len;
380 atomic_fetch_sub_u64(&queue->bytes_queued, bytes);
381 atomic_fetch_sub_u64(&queue->count, 1);
383
384 // Free dropped packet data
385 if (head->packet.owns_data && head->packet.data) {
387 }
388 node_pool_put(queue->node_pool, head);
389 }
390 // If CAS failed, another thread already dequeued - continue to enqueue
391 }
392 }
393
394 // Create new node (use pool if available)
395 packet_node_t *node = node_pool_get(queue->node_pool);
396 if (!node) {
397 SET_ERRNO(ERROR_MEMORY, "Failed to allocate packet node");
398 return -1;
399 }
400
401 // Copy the packet header
402 SAFE_MEMCPY(&node->packet, sizeof(queued_packet_t), packet, sizeof(queued_packet_t));
403
404 // Deep copy the data if needed
405 if (packet->data && packet->data_len > 0 && packet->owns_data) {
406 // If the packet owns its data, we need to make a copy
407 // Try to allocate from buffer pool (local or global)
408 void *data_copy;
409 if (queue->buffer_pool) {
410 data_copy = buffer_pool_alloc(queue->buffer_pool, packet->data_len);
411 node->packet.buffer_pool = queue->buffer_pool;
412 } else {
413 // Use global pool if no local pool
414 data_copy = buffer_pool_alloc(NULL, packet->data_len);
416 }
417 SAFE_MEMCPY(data_copy, packet->data_len, packet->data, packet->data_len);
418 node->packet.data = data_copy;
419 node->packet.owns_data = true;
420 } else {
421 // Either no data or packet doesn't own it (shared reference is OK)
422 node->packet.data = packet->data;
423 node->packet.owns_data = packet->owns_data;
424 node->packet.buffer_pool = packet->buffer_pool; // Preserve original pool reference
425 }
426
427 atomic_ptr_store(&node->next, (void *)(packet_node_t *)NULL);
428
429 // Add to queue using lock-free CAS-based enqueue (Michael-Scott algorithm)
430 while (true) {
432
433 if (tail == NULL) {
434 // Empty queue - atomically set both head and tail
435 packet_node_t *expected = NULL;
436 if (atomic_ptr_cas(&queue->head, (void **)&expected, node)) {
437 // Successfully set head (queue was empty)
438 atomic_ptr_store(&queue->tail, (void *)node);
439 break; // Enqueue successful
440 }
441 // CAS failed - another thread initialized queue, retry
442 continue;
443 }
444
445 // Queue is non-empty - try to append to tail
447 packet_node_t *current_tail = (packet_node_t *)atomic_ptr_load(&queue->tail);
448
449 // Verify tail hasn't changed (ABA problem mitigation)
450 if (tail != current_tail) {
451 // Tail was updated by another thread, retry
452 continue;
453 }
454
455 if (next == NULL) {
456 // Tail is actually the last node - try to link new node
457 packet_node_t *expected_null = NULL;
458 if (atomic_ptr_cas(&tail->next, (void **)&expected_null, node)) {
459 // Successfully linked node - try to swing tail forward (best-effort, ignore failure)
460 atomic_ptr_cas(&queue->tail, (void **)&tail, node);
461 break; // Enqueue successful
462 }
463 // CAS failed - another thread appended to tail, retry
464 } else {
465 // Tail is lagging behind - help advance it
466 atomic_ptr_cas(&queue->tail, (void **)&tail, next);
467 // Retry with new tail
468 }
469 }
470
471 // Update counters atomically
472 atomic_fetch_add_u64(&queue->count, (size_t)1);
475
476 return 0;
477}
@ ERROR_INVALID_PARAM
bool packet_queue_validate_packet(const queued_packet_t *packet)
Validate packet integrity.
Definition queue.c:650

References atomic_fetch_add_u64(), atomic_fetch_sub_u64(), atomic_load_bool(), atomic_load_u64(), atomic_ptr_cas(), atomic_ptr_load(), atomic_ptr_store(), queued_packet_t::buffer_pool, packet_queue_t::buffer_pool, buffer_pool_alloc(), buffer_pool_free(), buffer_pool_get_global(), packet_queue_t::bytes_queued, packet_queue_t::count, queued_packet_t::data, queued_packet_t::data_len, ERROR_INVALID_PARAM, ERROR_MEMORY, packet_queue_t::head, packet_queue_t::max_size, packet_node::next, packet_queue_t::node_pool, node_pool_get(), node_pool_put(), queued_packet_t::owns_data, packet_node::packet, packet_queue_validate_packet(), packet_queue_t::packets_dropped, packet_queue_t::packets_enqueued, SAFE_MEMCPY, SET_ERRNO, packet_queue_t::shutdown, and packet_queue_t::tail.

◆ packet_queue_free_packet()

void packet_queue_free_packet ( queued_packet_t *  packet)

#include <queue.h>

Free a dequeued packet.

Parameters
packetPacket to free (can be NULL)

Properly frees packet memory, returning data to buffer pool if applicable, or calling free() if allocated with malloc. Also frees packet structure itself.

Note
Safe to call with NULL (no-op).
Always call this for packets returned by dequeue functions.

Definition at line 568 of file queue.c.

568 {
569 if (!packet)
570 return;
571
572 // Check if packet was already freed (detect double-free)
573 if (packet->header.magic != HOST_TO_NET_U64(PACKET_MAGIC)) {
574 log_warn("Attempted double-free of packet (magic=0x%llx, expected=0x%llx)", NET_TO_HOST_U64(packet->header.magic),
576 return;
577 }
578
579 if (packet->owns_data && packet->data) {
580 // Return to appropriate pool or free
581 if (packet->buffer_pool) {
582 buffer_pool_free(packet->buffer_pool, packet->data, packet->data_len);
583 } else {
584 // This was allocated from global pool or malloc, use buffer_pool_free which handles both
585 buffer_pool_free(NULL, packet->data, packet->data_len);
586 }
587 }
588
589 // Mark as freed to detect future double-free attempts
590 // Use network byte order for consistency on big-endian systems
591 packet->header.magic = HOST_TO_NET_U64(0xBEEFDEADULL); // Different magic in network byte order
592 SAFE_FREE(packet);
593}
#define NET_TO_HOST_U64(val)
Definition endian.h:228
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574

References queued_packet_t::buffer_pool, buffer_pool_free(), queued_packet_t::data, queued_packet_t::data_len, queued_packet_t::header, HOST_TO_NET_U64, log_warn, packet_header_t::magic, NET_TO_HOST_U64, queued_packet_t::owns_data, PACKET_MAGIC, and SAFE_FREE.

Referenced by client_dispatch_thread(), client_send_thread_func(), and packet_queue_clear().

◆ packet_queue_get_stats()

void packet_queue_get_stats ( packet_queue_t *  queue,
uint64_t *  enqueued,
uint64_t *  dequeued,
uint64_t *  dropped 
)

#include <queue.h>

Get queue statistics.

Parameters
queuePacket queue
enqueuedOutput: Total packets enqueued
dequeuedOutput: Total packets dequeued
droppedOutput: Total packets dropped (due to queue full)

Retrieves cumulative statistics for the queue. Useful for performance monitoring and debugging.

Note
Thread-safe (lock-free using atomic operations).
All output parameters must be non-NULL.

Definition at line 637 of file queue.c.

637 {
638 if (!queue)
639 return;
640
641 // Lock-free atomic reads (acquire semantics for consistency)
642 if (enqueued)
643 *enqueued = atomic_load_u64(&queue->packets_enqueued);
644 if (dequeued)
645 *dequeued = atomic_load_u64(&queue->packets_dequeued);
646 if (dropped)
647 *dropped = atomic_load_u64(&queue->packets_dropped);
648}

References atomic_load_u64(), packet_queue_t::packets_dequeued, packet_queue_t::packets_dropped, and packet_queue_t::packets_enqueued.

Referenced by stats_logger_thread().

◆ packet_queue_is_empty()

bool packet_queue_is_empty ( packet_queue_t *  queue)

#include <queue.h>

Check if queue is empty.

Parameters
queuePacket queue
Returns
true if queue is empty, false otherwise

Returns true if queue contains no packets. Snapshot value may change immediately after return due to concurrent operations.

Note
Thread-safe (lock-free using atomic operations).

Definition at line 603 of file queue.c.

603 {
604 return packet_queue_size(queue) == 0;
605}
size_t packet_queue_size(packet_queue_t *queue)
Get current number of packets in queue.
Definition queue.c:595

References packet_queue_size().

◆ packet_queue_is_full()

bool packet_queue_is_full ( packet_queue_t *  queue)

#include <queue.h>

Check if queue is full.

Parameters
queuePacket queue
Returns
true if queue is full (max_size > 0 and count >= max_size), false otherwise

Returns true if queue has reached its maximum size. Unlimited queues (max_size == 0) never return true from this function.

Note
Thread-safe (lock-free using atomic operations).

Definition at line 607 of file queue.c.

607 {
608 if (!queue || queue->max_size == 0)
609 return false;
610
611 // Lock-free atomic read
612 size_t count = atomic_load_u64(&queue->count);
613 return (count >= queue->max_size);
614}

References atomic_load_u64(), packet_queue_t::count, and packet_queue_t::max_size.

◆ packet_queue_size()

size_t packet_queue_size ( packet_queue_t *  queue)

#include <queue.h>

Get current number of packets in queue.

Parameters
queuePacket queue
Returns
Number of packets currently queued

Returns the current queue size (count field). This is a snapshot and may change immediately after return due to concurrent enqueue/dequeue operations.

Note
Thread-safe (lock-free using atomic operations).

Definition at line 595 of file queue.c.

595 {
596 if (!queue)
597 return 0;
598
599 // Lock-free atomic read
600 return atomic_load_u64(&queue->count);
601}

References atomic_load_u64(), and packet_queue_t::count.

Referenced by client_audio_render_thread(), and packet_queue_is_empty().

◆ packet_queue_stop()

void packet_queue_stop ( packet_queue_t *  queue)

#include <queue.h>

Destroy a packet queue and free all resources.

Parameters
queueQueue to destroy (can be NULL)

Destroys the queue and all associated resources (node pool, etc.).

Warning
Packets still in the queue are NOT freed. Clear the queue first or ensure all packets are dequeued before destruction.

Definition at line 616 of file queue.c.

616 {
617 if (!queue) {
618 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: queue=%p", queue);
619 return;
620 }
621
622 // Lock-free atomic store (release semantics ensures visibility to other threads)
623 atomic_store_u64(&queue->shutdown, true);
624}

References atomic_store_u64(), ERROR_INVALID_PARAM, SET_ERRNO, and packet_queue_t::shutdown.

Referenced by disconnect_client_for_bad_data(), packet_queue_destroy(), and remove_client().

◆ packet_queue_try_dequeue()

queued_packet_t * packet_queue_try_dequeue ( packet_queue_t *  queue)

#include <queue.h>

Try to dequeue a packet without blocking.

Parameters
queuePacket queue
Returns
Pointer to dequeued packet, or NULL if queue is empty or shutdown

Non-blocking version of packet_queue_dequeue(). Returns immediately with NULL if queue is empty or shutdown, otherwise returns next packet.

Note
Caller must free the returned packet with packet_queue_free_packet().
Thread-safe (lock-free using atomic operations).

Definition at line 484 of file queue.c.

484 {
485 if (!queue)
486 return NULL;
487
488 // Check if shutdown (atomic read with acquire semantics)
489 if (atomic_load_bool(&queue->shutdown)) {
490 return NULL;
491 }
492
493 // Check if queue is empty (atomic read with acquire semantics)
494 size_t current_count = atomic_load_u64(&queue->count);
495 if (current_count == 0) {
496 return NULL;
497 }
498
499 // Remove from head atomically (lock-free dequeue)
501 if (!head) {
502 return NULL;
503 }
504
505 // Atomically update head pointer
507 if (atomic_ptr_cas(&queue->head, (void **)&head, next)) {
508 // Successfully claimed head node
509 if (next == NULL) {
510 // Queue became empty, also update tail atomically
511 atomic_ptr_store(&queue->tail, (void *)(packet_node_t *)NULL);
512 }
513
514 // Update counters atomically
515 size_t bytes = head->packet.data_len;
516 atomic_fetch_sub_u64(&queue->bytes_queued, bytes);
517 atomic_fetch_sub_u64(&queue->count, 1);
519
520 // Verify packet magic number for corruption detection
522 if (magic != PACKET_MAGIC) {
523 SET_ERRNO(ERROR_BUFFER, "CORRUPTION: Invalid magic in try_dequeued packet: 0x%llx (expected 0x%llx), type=%u",
525 // Still return node to pool but don't return corrupted packet
526 node_pool_put(queue->node_pool, head);
527 return NULL;
528 }
529
530 // Validate CRC if there's data
531 if (head->packet.data_len > 0 && head->packet.data) {
532 uint32_t expected_crc = NET_TO_HOST_U32(head->packet.header.crc32);
533 uint32_t actual_crc = asciichat_crc32(head->packet.data, head->packet.data_len);
534 if (actual_crc != expected_crc) {
536 "CORRUPTION: CRC mismatch in try_dequeued packet: got 0x%x, expected 0x%x, type=%u, len=%zu",
537 actual_crc, expected_crc, NET_TO_HOST_U16(head->packet.header.type), head->packet.data_len);
538 // Free data if packet owns it
539 if (head->packet.owns_data && head->packet.data) {
540 // Use buffer_pool_free for global pool allocations, buffer_pool_free for local pools
541 if (head->packet.buffer_pool) {
543 } else {
544 // This was allocated from global pool or malloc, use buffer_pool_free which handles both
545 buffer_pool_free(NULL, head->packet.data, head->packet.data_len);
546 }
547 // Clear pointer to prevent double-free when packet is copied later.
548 head->packet.data = NULL;
549 head->packet.owns_data = false;
550 }
551 node_pool_put(queue->node_pool, head);
552 return NULL;
553 }
554 }
555
556 // Extract packet and return node to pool
557 queued_packet_t *packet;
558 packet = SAFE_MALLOC(sizeof(queued_packet_t), queued_packet_t *);
559 SAFE_MEMCPY(packet, sizeof(queued_packet_t), &head->packet, sizeof(queued_packet_t));
560 node_pool_put(queue->node_pool, head);
561 return packet;
562 }
563
564 // CAS failed - another thread dequeued, retry if needed (or return NULL for non-blocking)
565 return NULL;
566}
#define NET_TO_HOST_U16(val)
Definition endian.h:111
#define NET_TO_HOST_U32(val)
Definition endian.h:81
@ ERROR_BUFFER

References asciichat_crc32, atomic_fetch_add_u64(), atomic_fetch_sub_u64(), atomic_load_bool(), atomic_load_u64(), atomic_ptr_cas(), atomic_ptr_load(), atomic_ptr_store(), queued_packet_t::buffer_pool, buffer_pool_free(), packet_queue_t::bytes_queued, packet_queue_t::count, packet_header_t::crc32, queued_packet_t::data, queued_packet_t::data_len, ERROR_BUFFER, packet_queue_t::head, queued_packet_t::header, packet_header_t::magic, NET_TO_HOST_U16, NET_TO_HOST_U32, NET_TO_HOST_U64, packet_node::next, packet_queue_t::node_pool, node_pool_put(), queued_packet_t::owns_data, packet_node::packet, PACKET_MAGIC, packet_queue_t::packets_dequeued, SAFE_MALLOC, SAFE_MEMCPY, SET_ERRNO, packet_queue_t::shutdown, packet_queue_t::tail, and packet_header_t::type.

Referenced by client_dispatch_thread(), client_send_thread_func(), packet_queue_clear(), and packet_queue_dequeue().

◆ packet_queue_validate_packet()

bool packet_queue_validate_packet ( const queued_packet_t *  packet)

#include <queue.h>

Validate packet integrity.

Parameters
packetPacket to validate
Returns
true if packet is valid, false otherwise

Validates packet structure and header integrity. Useful for debugging and detecting memory corruption.

Note
This is a basic validation - does not verify payload contents.

Definition at line 650 of file queue.c.

650 {
651 if (!packet) {
652 return false;
653 }
654
655 // Check magic number
656 uint64_t magic = NET_TO_HOST_U64(packet->header.magic);
657 if (magic != PACKET_MAGIC) {
658 SET_ERRNO(ERROR_BUFFER, "Invalid packet magic: 0x%llx (expected 0x%llx)", magic, PACKET_MAGIC);
659 return false;
660 }
661
662 // Check packet type is valid (must be non-zero and within reasonable range)
663 uint16_t type = NET_TO_HOST_U16(packet->header.type);
664 if (type == 0 || type > 10000) {
665 SET_ERRNO(ERROR_BUFFER, "Invalid packet type: %u", type);
666 return false;
667 }
668
669 // Check length matches data_len
670 uint32_t length = NET_TO_HOST_U32(packet->header.length);
671 if (length != packet->data_len) {
672 SET_ERRNO(ERROR_BUFFER, "Packet length mismatch: header says %u, data_len is %zu", length, packet->data_len);
673 return false;
674 }
675
676 // Check CRC if there's data
677 if (packet->data_len > 0 && packet->data) {
678 uint32_t expected_crc = NET_TO_HOST_U32(packet->header.crc32);
679 uint32_t actual_crc = asciichat_crc32(packet->data, packet->data_len);
680 if (actual_crc != expected_crc) {
681 SET_ERRNO(ERROR_BUFFER, "Packet CRC mismatch: got 0x%x, expected 0x%x", actual_crc, expected_crc);
682 return false;
683 }
684 }
685
686 return true;
687}

References asciichat_crc32, packet_header_t::crc32, queued_packet_t::data, queued_packet_t::data_len, ERROR_BUFFER, queued_packet_t::header, packet_header_t::length, packet_header_t::magic, NET_TO_HOST_U16, NET_TO_HOST_U32, NET_TO_HOST_U64, PACKET_MAGIC, SET_ERRNO, and packet_header_t::type.

Referenced by packet_queue_enqueue_packet().

Variable Documentation

◆ buffer_pool [1/2]

buffer_pool_t* queued_packet_t::buffer_pool

Pool that allocated the data (NULL if malloc'd)

Definition at line 130 of file queue.h.

Referenced by packet_queue_enqueue(), packet_queue_enqueue_packet(), packet_queue_free_packet(), and packet_queue_try_dequeue().

◆ buffer_pool [2/2]

buffer_pool_t* packet_queue_t::buffer_pool

Optional memory pool for data buffers (NULL = use malloc/free)

Definition at line 226 of file queue.h.

Referenced by packet_queue_create_with_pools(), packet_queue_destroy(), packet_queue_enqueue(), and packet_queue_enqueue_packet().

◆ bytes_queued

atomic_t packet_queue_t::bytes_queued

Total bytes of data queued (for monitoring) - atomic for lock-free access.

Definition at line 221 of file queue.h.

Referenced by packet_queue_create_with_pools(), packet_queue_destroy(), packet_queue_enqueue(), packet_queue_enqueue_packet(), and packet_queue_try_dequeue().

◆ count

atomic_t packet_queue_t::count

Number of packets currently in queue - atomic for lock-free access.

Definition at line 217 of file queue.h.

Referenced by packet_queue_create_with_pools(), packet_queue_destroy(), packet_queue_enqueue(), packet_queue_enqueue_packet(), packet_queue_is_full(), packet_queue_size(), and packet_queue_try_dequeue().

◆ data

void* queued_packet_t::data

◆ data_len

size_t queued_packet_t::data_len

◆ free_list

atomic_ptr_t node_pool::free_list

Stack of free nodes (LIFO for cache locality) - atomic for lock-free access.

Definition at line 169 of file queue.h.

◆ head

atomic_ptr_t packet_queue_t::head

Front of queue (dequeue from here) - atomic for lock-free access.

Definition at line 213 of file queue.h.

Referenced by packet_queue_create_with_pools(), packet_queue_destroy(), packet_queue_enqueue(), packet_queue_enqueue_packet(), and packet_queue_try_dequeue().

◆ header

packet_header_t queued_packet_t::header

Complete packet header (already in network byte order)

Definition at line 122 of file queue.h.

Referenced by packet_queue_enqueue(), packet_queue_free_packet(), packet_queue_try_dequeue(), and packet_queue_validate_packet().

◆ max_size

size_t packet_queue_t::max_size

Maximum queue size (0 = unlimited)

Definition at line 219 of file queue.h.

Referenced by packet_queue_create_with_pools(), packet_queue_enqueue(), packet_queue_enqueue_packet(), and packet_queue_is_full().

◆ next

atomic_ptr_t packet_node::next

Pointer to next node in linked list (NULL for tail) - atomic for lock-free operations.

Definition at line 151 of file queue.h.

Referenced by node_pool_get(), node_pool_put(), packet_queue_enqueue(), packet_queue_enqueue_packet(), and packet_queue_try_dequeue().

◆ node_pool

node_pool_t* packet_queue_t::node_pool

Optional memory pool for nodes (NULL = use malloc/free)

Definition at line 224 of file queue.h.

Referenced by packet_queue_create_with_pools(), packet_queue_destroy(), packet_queue_enqueue(), packet_queue_enqueue_packet(), and packet_queue_try_dequeue().

◆ nodes

packet_node_t* node_pool::nodes

Pre-allocated array of all nodes.

Definition at line 171 of file queue.h.

◆ owns_data

bool queued_packet_t::owns_data

If true, free data when packet is freed.

Definition at line 128 of file queue.h.

Referenced by packet_queue_enqueue(), packet_queue_enqueue_packet(), packet_queue_free_packet(), and packet_queue_try_dequeue().

◆ packet

queued_packet_t packet_node::packet

The queued packet data.

Definition at line 149 of file queue.h.

Referenced by packet_queue_enqueue(), packet_queue_enqueue_packet(), and packet_queue_try_dequeue().

◆ packets_dequeued

atomic_t packet_queue_t::packets_dequeued

Total packets dequeued (statistics) - atomic for lock-free access.

Definition at line 231 of file queue.h.

Referenced by packet_queue_create_with_pools(), packet_queue_destroy(), packet_queue_get_stats(), and packet_queue_try_dequeue().

◆ packets_dropped

atomic_t packet_queue_t::packets_dropped

Total packets dropped due to queue full (statistics) - atomic for lock-free access.

Definition at line 233 of file queue.h.

Referenced by packet_queue_create_with_pools(), packet_queue_destroy(), packet_queue_enqueue(), packet_queue_enqueue_packet(), and packet_queue_get_stats().

◆ packets_enqueued

atomic_t packet_queue_t::packets_enqueued

Total packets enqueued (statistics) - atomic for lock-free access.

Definition at line 229 of file queue.h.

Referenced by packet_queue_create_with_pools(), packet_queue_destroy(), packet_queue_enqueue(), packet_queue_enqueue_packet(), and packet_queue_get_stats().

◆ pool_size

size_t node_pool::pool_size

Total number of nodes in pool.

Definition at line 173 of file queue.h.

◆ shutdown

atomic_t packet_queue_t::shutdown

Shutdown flag (true = dequeue returns NULL) - atomic for lock-free access.

Definition at line 236 of file queue.h.

Referenced by packet_queue_create_with_pools(), packet_queue_destroy(), packet_queue_enqueue(), packet_queue_enqueue_packet(), packet_queue_stop(), and packet_queue_try_dequeue().

◆ tail

atomic_ptr_t packet_queue_t::tail

Back of queue (enqueue here) - atomic for lock-free access.

Definition at line 215 of file queue.h.

Referenced by packet_queue_create_with_pools(), packet_queue_destroy(), packet_queue_enqueue(), packet_queue_enqueue_packet(), and packet_queue_try_dequeue().

◆ used_count

atomic_t node_pool::used_count

Number of nodes currently in use - atomic for lock-free tracking.

Definition at line 175 of file queue.h.