Using the libuv Loop Data Pointer
Overview
libuv's event loop provides a data pointer field that can store user-defined data. This is a best practice for avoiding global variables, especially in multithreaded environments.
Basic Concepts
The uv_loop_t data Field
c
typedef struct uv_loop_s {
// ... other fields ...
void* data; // user data pointer
// ... other fields ...
} uv_loop_t;Why Use the data Pointer?
- Avoid global variables: global variables cause race conditions in multithreaded environments
- Thread safety: each event loop has its own data, isolated from one another
- Code encapsulation: groups related data together, improving maintainability
- Context passing: makes it easy to access application context from within callbacks
Basic Usage
1. Define an Application Context Structure
c
/**
* @brief Application context structure
*
* Encapsulates all application-related data
*/
typedef struct {
uvhttp_server_t* server;
uvhttp_router_t* router;
int request_count;
time_t start_time;
// other application data...
} app_context_t;2. Create and Set the Context
c
int main() {
uv_loop_t* loop = uv_default_loop();
// Create the application context
app_context_t* ctx = malloc(sizeof(app_context_t));
if (!ctx) {
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
// Initialize the context
ctx->server = NULL;
ctx->router = NULL;
ctx->request_count = 0;
ctx->start_time = time(NULL);
// Create the server
uvhttp_server_new(loop, &ctx->server);
uvhttp_router_new(&ctx->router);
uvhttp_server_set_router(ctx->server, ctx->router);
// Set the context into the event loop's data pointer
loop->data = ctx;
// Add routes (needs access to the context)
uvhttp_router_add_route(ctx->router, "/", home_handler);
// Start the server
uvhttp_server_listen(ctx->server, "0.0.0.0", 8080);
// Run the event loop
uv_run(loop, UV_RUN_DEFAULT);
// Cleanup
if (ctx->server) uvhttp_server_free(ctx->server);
free(ctx);
return 0;
}3. Access the Context Inside a Callback
c
/**
* @brief Request handler
*
* Retrieves the application context from the event loop's data pointer
*/
int request_handler(uvhttp_request_t* req, uvhttp_response_t* res) {
// Get the event loop
uv_loop_t* loop = uv_default_loop();
// Get the application context from the event loop
app_context_t* ctx = (app_context_t*)loop->data;
// Access context data
ctx->request_count++;
// Use data from the context
char response[256];
snprintf(response, sizeof(response),
"{\"request_count\":%d,\"uptime\":%ld}",
ctx->request_count,
time(NULL) - ctx->start_time);
uvhttp_response_set_status(res, 200);
uvhttp_response_set_header(res, "Content-Type", "application/json");
uvhttp_response_set_body(res, response, strlen(response));
return uvhttp_response_send(res);
}Complete Examples
Example 1: A Simple Counter
c
#include "uvhttp.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>
/**
* @brief Application context
*/
typedef struct {
uvhttp_server_t* server;
uvhttp_router_t* router;
int request_count;
time_t start_time;
} app_context_t;
/**
* @brief Stats handler
*/
int stats_handler(uvhttp_request_t* req, uvhttp_response_t* res) {
uv_loop_t* loop = uv_default_loop();
app_context_t* ctx = (app_context_t*)loop->data;
char response[512];
snprintf(response, sizeof(response),
"{\n"
" \"request_count\": %d,\n"
" \"uptime_seconds\": %ld,\n"
" \"active_connections\": %zu\n"
"}",
ctx->request_count,
time(NULL) - ctx->start_time,
ctx->server ? ctx->server->active_connections : 0);
uvhttp_response_set_status(res, 200);
uvhttp_response_set_header(res, "Content-Type", "application/json");
uvhttp_response_set_body(res, response, strlen(response));
ctx->request_count++;
return uvhttp_response_send(res);
}
/**
* @brief Signal handling
*/
void signal_handler(int sig) {
printf("\nReceived signal %d\n", sig);
exit(0);
}
int main() {
signal(SIGINT, signal_handler);
uv_loop_t* loop = uv_default_loop();
// Create and initialize the context
app_context_t* ctx = malloc(sizeof(app_context_t));
ctx->server = NULL;
ctx->router = NULL;
ctx->request_count = 0;
ctx->start_time = time(NULL);
// Set it into the event loop
loop->data = ctx;
// Create the server
uvhttp_server_new(loop, &ctx->server);
uvhttp_router_new(&ctx->router);
uvhttp_server_set_router(ctx->server, ctx->router);
// Add routes
uvhttp_router_add_route(ctx->router, "/stats", stats_handler);
// Start the server
uvhttp_server_listen(ctx->server, "0.0.0.0", 8080);
printf("Server running at http://localhost:8080/stats\n");
// Run the event loop
uv_run(loop, UV_RUN_DEFAULT);
// Cleanup
if (ctx->server) uvhttp_server_free(ctx->server);
free(ctx);
return 0;
}Example 2: Multithreaded Environment
c
#include "uvhttp.h"
#include <uv.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define THREAD_COUNT 4
/**
* @brief Worker thread context
*/
typedef struct {
int thread_id;
uv_loop_t* loop;
uvhttp_server_t* server;
uvhttp_router_t* router;
int request_count;
} worker_context_t;
/**
* @brief Request handler
*/
int worker_handler(uvhttp_request_t* req, uvhttp_response_t* res) {
uv_loop_t* loop = uv_default_loop();
worker_context_t* ctx = (worker_context_t*)loop->data;
char response[256];
snprintf(response, sizeof(response),
"{\"thread_id\":%d,\"request_count\":%d}",
ctx->thread_id,
++ctx->request_count);
uvhttp_response_set_status(res, 200);
uvhttp_response_set_header(res, "Content-Type", "application/json");
uvhttp_response_set_body(res, response, strlen(response));
return uvhttp_response_send(res);
}
/**
* @brief Worker thread function
*/
void* worker_thread(void* arg) {
worker_context_t* ctx = (worker_context_t*)arg;
// Create a dedicated event loop
ctx->loop = uv_loop_new();
// Set the context into the event loop
ctx->loop->data = ctx;
// Create the server
uvhttp_server_new(ctx->loop, &ctx->server);
uvhttp_router_new(&ctx->router);
uvhttp_server_set_router(ctx->server, ctx->router);
// Add routes
uvhttp_router_add_route(ctx->router, "/", worker_handler);
// Start the server (on a different port)
int port = 8080 + ctx->thread_id;
uvhttp_server_listen(ctx->server, "0.0.0.0", port);
printf("Thread %d listening on port %d\n", ctx->thread_id, port);
// Run the event loop
uv_run(ctx->loop, UV_RUN_DEFAULT);
// Cleanup
uvhttp_server_free(ctx->server);
uv_loop_close(ctx->loop);
free(ctx->loop);
return NULL;
}
int main() {
pthread_t threads[THREAD_COUNT];
worker_context_t contexts[THREAD_COUNT];
// Create worker threads
for (int i = 0; i < THREAD_COUNT; i++) {
contexts[i].thread_id = i;
contexts[i].request_count = 0;
pthread_create(&threads[i], NULL, worker_thread, &contexts[i]);
}
printf("Multithreaded server started with %d threads\n", THREAD_COUNT);
// Wait for all threads
for (int i = 0; i < THREAD_COUNT; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}Advanced Usage
1. Nested Contexts
c
typedef struct {
app_context_t* app_ctx;
int worker_id;
int local_count;
} worker_context_t;
// In the main thread
app_context_t* app_ctx = malloc(sizeof(app_context_t));
app_ctx->server = server;
app_ctx->router = router;
loop->data = app_ctx;
// In a worker thread
worker_context_t* worker_ctx = malloc(sizeof(worker_context_t));
worker_ctx->app_ctx = app_ctx; // shared application context
worker_ctx->worker_id = id;
worker_ctx->local_count = 0;
worker_loop->data = worker_ctx;2. Context Initialization Function
c
/**
* @brief Initialize the application context
*/
app_context_t* app_context_create(uv_loop_t* loop) {
app_context_t* ctx = malloc(sizeof(app_context_t));
if (!ctx) return NULL;
ctx->server = NULL;
ctx->router = NULL;
uvhttp_server_new(loop, &ctx->server);
uvhttp_router_new(&ctx->router);
ctx->request_count = 0;
ctx->start_time = time(NULL);
if (ctx->server && ctx->router) {
uvhttp_server_set_router(ctx->server, ctx->router);
loop->data = ctx;
return ctx;
}
// Creation failed, clean up
if (ctx->server) uvhttp_server_free(ctx->server);
free(ctx);
return NULL;
}
/**
* @brief Destroy the application context
*/
void app_context_destroy(app_context_t* ctx) {
if (!ctx) return;
if (ctx->server) uvhttp_server_free(ctx->server);
free(ctx);
}3. Use in Async Callbacks
c
typedef struct {
uv_work_t work_req;
app_context_t* ctx;
char query[256];
} async_query_t;
void async_work_cb(uv_work_t* req) {
async_query_t* async = (async_query_t*)req->data;
// Perform async work
printf("Executing query: %s\n", async->query);
}
void async_after_cb(uv_work_t* req, int status) {
async_query_t* async = (async_query_t*)req->data;
app_context_t* ctx = async->ctx;
// Access the context
ctx->request_count++;
free(async);
free(req);
}
int async_handler(uvhttp_request_t* req, uvhttp_response_t* res) {
uv_loop_t* loop = uv_default_loop();
app_context_t* ctx = (app_context_t*)loop->data;
// Create an async request
async_query_t* async = malloc(sizeof(async_query_t));
async->ctx = ctx;
strcpy(async->query, "SELECT * FROM users");
uv_work_t* work_req = malloc(sizeof(uv_work_t));
work_req->data = async;
uv_queue_work(loop, work_req, async_work_cb, async_after_cb);
return 0;
}Best Practices
1. Always Check the data Pointer
c
int handler(uvhttp_request_t* req, uvhttp_response_t* res) {
uv_loop_t* loop = uv_default_loop();
app_context_t* ctx = (app_context_t*)loop->data;
// Check whether the context exists
if (!ctx) {
const char* error = "{\"error\":\"context not initialized\"}";
uvhttp_response_set_status(res, 500);
uvhttp_response_set_header(res, "Content-Type", "application/json");
uvhttp_response_set_body(res, error, strlen(error));
return uvhttp_response_send(res);
}
// Use the context
// ...
}2. Reset the data Pointer During Cleanup
c
void cleanup(app_context_t* ctx, uv_loop_t* loop) {
if (ctx->server) {
uvhttp_server_free(ctx->server);
ctx->server = NULL;
}
// Reset the data pointer
loop->data = NULL;
free(ctx);
}3. Use a Macro to Simplify Access
c
#define GET_CTX(loop, ctx_type) ((ctx_type*)((loop)->data))
int handler(uvhttp_request_t* req, uvhttp_response_t* res) {
uv_loop_t* loop = uv_default_loop();
app_context_t* ctx = GET_CTX(loop, app_context_t);
if (!ctx) {
// error handling
}
// Use ctx
}4. Thread-Safe Context Access
c
typedef struct {
uvhttp_server_t* server;
uvhttp_router_t* router;
int request_count;
pthread_mutex_t mutex;
} app_context_t;
int handler(uvhttp_request_t* req, uvhttp_response_t* res) {
uv_loop_t* loop = uv_default_loop();
app_context_t* ctx = GET_CTX(loop, app_context_t);
// Lock to protect shared data
pthread_mutex_lock(&ctx->mutex);
ctx->request_count++;
int count = ctx->request_count;
pthread_mutex_unlock(&ctx->mutex);
// Use the data
// ...
}FAQ
Q: How do I share data across multiple event loops?
A: Use a shared context structure; each event loop's data pointer points to the same context:
c
app_context_t* shared_ctx = malloc(sizeof(app_context_t));
// Thread 1
loop1->data = shared_ctx;
// Thread 2
loop2->data = shared_ctx;
// Remember to protect shared data with a mutexQ: Will libuv modify the data pointer?
A: No. libuv never modifies the data pointer; it is entirely under user control.
Q: Can I store arbitrary data in the data pointer?
A: Yes. data is of type void* and can store any pointer. Using a struct to organize related data is recommended.
Q: How do I handle the case where the data pointer is NULL?
A: Always check whether the data pointer is NULL, especially in library functions or callbacks:
c
void callback(uv_async_t* handle) {
uv_loop_t* loop = handle->loop;
app_context_t* ctx = (app_context_t*)loop->data;
if (!ctx) {
fprintf(stderr, "Error: context not initialized\n");
return;
}
// Continue processing
}Summary
Using the libuv loop's data pointer is a best practice for avoiding global variables:
- Encapsulation: encapsulates related data in a struct
- Thread safety: each event loop has its own data copy
- Flexibility: the context can be modified dynamically at runtime
- Maintainability: clear code structure, easy to understand and maintain
In multithreaded and complex applications, always use the data pointer to manage application state!