English | 中文
Cooperative Async Technology
Synchronous code. High-performance async I/O.
libcat is the C coroutine and I/O core behind Swow. Write with functions, loops and local variables; stackful coroutines and libuv handle asynchronous concurrency.
Switch coroutines in user space. The default context implementation saves and restores registers and stack state in assembly. Switching coroutines does not require waking another system thread.
Wait on one connection. Serve the others. The event loop schedules multiple connections without thread count growing in step with connection count. A socket call can suspend its coroutine before returning while other coroutines keep running.
Move less data. Reuse allocations. Plaintext reads fill caller-provided buffers directly. Vector writes can submit existing buffers together. Sockets reuse write requests when no other write occupies them.
Implementation references
- Assembly for the default context implementation.
- Read buffers, vector writes and cached write requests.
- Direct reads on supported Unix socket paths: return when data is available, then use the event-driven path when waiting is necessary.
Specific paths depend on the platform, socket type and encryption state. Default scheduling is cooperative: long CPU work or external blocking calls occupy the scheduler thread. Blocking tasks can use the worker interface.
| Capability | What you can do |
|---|---|
| Stackful coroutines | Create, resume and suspend; preserve nested calls and local variables; configure stack size and inspect state, switch counts and elapsed time |
| Channels | Buffered and unbuffered communication, producers and consumers, timed waits, multi-channel selection, closure and data cleanup |
| Wait groups | Join coroutine tasks, wait for a group of concurrent operations and set a wait timeout |
| Time and waiting | Coroutine sleep and delay, timed waits, high-resolution time queries and cached time reads |
| Capability | What you can do |
|---|---|
| Sockets | TCP, UDP, IPv4/IPv6, Unix local streams and datagrams; connect, accept, read, write, vector writes and handle passing; operation-specific timeouts |
| TLS / SSL | OpenSSL integration, handshakes and encrypted transport, certificate-chain and peer-name verification, SNI, ALPN and protocol configuration |
| HTTP and multipart | Incremental request and response parsing; URL, status, header and body events; chunked messages and multipart data |
| WebSocket | Frame headers, opcodes, payload lengths, masking-key configuration, masking and unmasking utilities |
| DNS | Wait for name resolution in a coroutine, query addresses and IPs, and configure lookup timeouts |
| poll / select | Bring readable and writable waits for existing descriptors into coroutines while retaining poll/select-style calls |
| Capability | What you can do |
|---|---|
| cURL | Keep CURL * handles, options and callbacks; wait for transfers and organize concurrent requests through coroutine-aware easy and multi interfaces |
| PostgreSQL / libpq | Coroutine connections and queries, parameterized queries, prepared statements and execution; retain libpq connection and result objects |
| Files and directories | Read, write, seek, sync, inspect metadata, rename, traverse directories and lock files; use the sendfile interface |
| Processes | Start child processes, wait for exit in a coroutine and inspect exit status; use fork/waitpid interfaces on supported platforms |
| Signals | Wait for signals in a coroutine, configure wait timeouts and send signals to processes |
| Workers and asynchronous notifications | Execute blocking tasks on workers while the caller waits; bring thread notifications into the event loop and arrange completion cleanup |
| Capability | What you can do |
|---|---|
| Dynamic buffers | Grow, append, format and export data; manage capacity and actual length |
| Debug logs and watchdog | Module logs, coroutine state and switch statistics, scheduler deadlock diagnostics and blocking detection; build with ASan support |
OpenSSL, cURL and PostgreSQL are enabled through build configuration. System-specific capabilities depend on the platform.
Each connection has its own stack and buffer. Read, send and close in order; other connections keep working while this one waits for data.
static cat_data_t *echo_connection(cat_data_t *data)
{
cat_socket_t *connection = (cat_socket_t *) data;
char buffer[4096];
ssize_t length;
while ((length = cat_socket_recv(connection, buffer, sizeof(buffer))) > 0) {
if (!cat_socket_send(connection, buffer, (size_t) length)) {
break;
}
}
cat_socket_close(connection);
return NULL;
}Starting the connection coroutine
Include cat_api.h and initialize the runtime and scheduler. Pass an accepted connection created with cat_socket_create(NULL, ...):
if (cat_coroutine_run(NULL, echo_connection, connection) == NULL) {
cat_socket_close(connection);
}The coroutine closes the connection after a successful start; the caller closes it if starting fails. Peer closure or a read/write error ends processing. Use cat_get_last_error_message() for error diagnostics. See the HTTP echo example for a complete listener entry point.
For a GET request that receives Hello, CAT!, the core flow after parser initialization is below. The coroutine handles an accepted connection; a request arriving in fragments continues through the same loop.
while (!cat_http_parser_is_completed(&parser)) {
ssize_t length = cat_socket_recv(connection, buffer, sizeof(buffer));
if (length <= 0 || !cat_http_parser_execute(&parser, buffer, (size_t) length)) {
goto _close;
}
}
if (!cat_socket_send(connection, response, strlen(response))) {
fprintf(stderr, "%s\n", cat_get_last_error_message());
}Full handler: GET responds with Hello, CAT!
Include cat_api.h and cat_http.h. Ownership follows the TCP example; use http_connection as the entry point. The example closes each connection after one request; invalid requests or an early disconnect end processing.
static cat_data_t *http_connection(cat_data_t *data)
{
cat_socket_t *connection = (cat_socket_t *) data;
cat_http_parser_t parser;
char buffer[4096];
cat_http_parser_init(&parser);
if (!cat_http_parser_set_type(&parser, CAT_HTTP_PARSER_TYPE_REQUEST)) {
goto _close;
}
cat_http_parser_set_events(&parser, CAT_HTTP_PARSER_EVENT_MESSAGE_COMPLETE);
while (!cat_http_parser_is_completed(&parser)) {
ssize_t length = cat_socket_recv(connection, buffer, sizeof(buffer));
if (length <= 0 || !cat_http_parser_execute(&parser, buffer, (size_t) length)) {
goto _close;
}
}
const char response[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain; charset=utf-8\r\n"
"Content-Length: 12\r\n"
"Connection: close\r\n\r\n"
"Hello, CAT!\n";
if (!cat_socket_send(connection, response, strlen(response))) {
fprintf(stderr, "%s\n", cat_get_last_error_message());
}
_close:
cat_socket_close(connection);
return NULL;
}Use CMake 3.18+, a C11 compiler and the assembler toolchain required by the default context implementation.
git clone https://github.com/libcat/libcat.git
cd libcat
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DLIBCAT_BUILD_TESTS=OFF
cmake --build build --config Release --target cat --parallelThe target is the static library cat. Check CMake output for enabled OpenSSL, libcurl and libpq modules. For include paths, compile definitions and link dependencies when embedding the library, see the CMake configuration and Swow integration.
The HTTP benchmark entry point provides workload settings. Report measurements with hardware, build configuration, throughput, latency and resource usage. With GoogleTest and a C++20 toolchain installed, run tests with ./run-tests.sh; see the CI configuration for services and platform setup.
Send minimal reproductions through Issues, and contribute code, tests or documentation through Pull Requests. libcat uses the Apache License 2.0; third-party licenses are in LICENSE.full.