Apache Traffic Server (ATS) is a high-performance HTTP/HTTPS caching proxy server written in C++20. It processes large-scale web traffic using an event-driven, multi-threaded architecture with a sophisticated plugin system.
Key Facts:
Some of these rules are enforced automatically by CI (via clang-format and clang-tidy); others are recommended conventions that will not themselves cause CI failures but should still be followed in code reviews. Formatting Rules:
if (condition) { // code here }
Type *ptr; // Correct Type &ref; // Correct Type* ptr; // Wrong
void function() { int x = 5; std::string name = "test"; // Empty line before first code statement process_data(x, name); }
if (x > 0) { // Correct foo(); } if (x > 0) // Wrong - missing braces foo();
// Correct - declarations grouped together void function() { int count = 0; std::string name = "test"; auto *buffer = new_buffer(); // Empty line before first code statement process_data(count, name, buffer); } // Wrong - declarations scattered void function() { int count = 0; process_count(count); std::string name = "test"; // Don't scatter declarations }
Naming Conventions:
CamelCase → HttpSM, NetVConnection, CacheProcessorsnake_case → handle_request(), server_port, cache_keyUPPER_CASE → HTTP_STATUS_OK, MAX_BUFFER_SIZE_ and m_ prefixes in the source code; be consistent with the surrounding code. For new files, prefer the _ prefix because that reflects the community consensus.was_modified rather than modified.C++20 Patterns (Use These):
// GOOD - Modern C++20 auto buffer = std::make_unique<MIOBuffer>(size); for (const auto &entry : container) { if (auto *ptr = entry.get(); ptr != nullptr) { process(ptr); } } // AVOID - Legacy C-style MIOBuffer *buffer = (MIOBuffer*)malloc(sizeof(MIOBuffer)); for (int i = 0; i < container.size(); i++) { process(container[i]); }
Memory Management:
std::unique_ptr, std::shared_ptr)new/delete when possibleats_malloc()/ats_free() for large allocations (not malloc)IOBuffer for network data (zero-copy design)delete this (e.g., continuation-based code)What NOT to Use:
malloc/free for large allocations (use ats_malloc), or prefer heaps or stack allocationsMinimal comments philosophy:
// BAD - stating the obvious // Increment the counter counter++; // GOOD - explaining why // Skip the first element since it's always the sentinel value counter++; // BAD - describing what // Loop through all connections and close them for (auto &conn : connections) { conn.close(); } // GOOD - explaining why (if not obvious) // Must close connections before destroying the acceptor to avoid use-after-free for (auto &conn : connections) { conn.close(); }
When to add comments:
When NOT to add comments:
New source and test files must start with Apache License 2.0 header (.cc, .h, .py, and other code files):
/** @file * * Brief description of file * * @section license License * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */
ATS uses Continuation-based asynchronous programming:
// Continuation is the core callback pattern class MyContinuation : public Continuation { int handle_event(int event, void *data) { switch (event) { case EVENT_SUCCESS: // Handle success return EVENT_DONE; case EVENT_ERROR: // Handle error return EVENT_ERROR; } return EVENT_CONT; } };
Key Rules:
Continuation callbacksEVENT_DONE, EVENT_CONT, or EVENT_ERROR from handlersEThread::schedule() for deferred workThe HttpSM class orchestrates HTTP request processing:
// HttpSM is the central state machine class HttpSM : public Continuation { // Processes requests through various states // Hook into appropriate stage via plugin hooks // Access transaction state via HttpTxn };
Common hooks:
TS_HTTP_READ_REQUEST_HDR_HOOK - After reading client requestTS_HTTP_SEND_REQUEST_HDR_HOOK - Before sending to originTS_HTTP_READ_RESPONSE_HDR_HOOK - After reading origin responseTS_HTTP_SEND_RESPONSE_HDR_HOOK - Before sending to clientRule: Don't create threads. Use the event system or existing thread pools.
// At file scope static DbgCtl dbg_ctl{"http_sm"}; // In code (preferred) Dbg(dbg_ctl, "Processing request for URL: %s", url); // Alternative (less common) DbgPrint(dbg_ctl, "Processing request for URL: %s", url);
Note: Use Dbg() for new code. DbgPrint() exists but is rarely used (~60 vs ~3400 uses).
trafficserver/
├── src/
│ ├── iocore/ # I/O subsystem
│ │ ├── eventsystem/ # Event engine (Continuation.h is core)
│ │ ├── cache/ # Cache implementation
│ │ ├── net/ # Network I/O, TLS, QUIC
│ │ └── dns/ # DNS resolution
│ ├── proxy/ # HTTP proxy logic
│ │ ├── http/ # HTTP/1.1 (HttpSM.cc is central)
│ │ ├── http2/ # HTTP/2
│ │ ├── http3/ # HTTP/3
│ │ ├── hdrs/ # Header parsing
│ │ └── logging/ # Logging
│ ├── tscore/ # Core utilities
│ ├── tsutil/ # Utilities (metrics, debugging)
│ └── api/ # Plugin API implementation
│
├── include/
│ ├── ts/ # Public plugin API (ts.h)
│ ├── tscpp/ # C++ plugin API
│ └── iocore/ # Internal headers
│
├── plugins/ # Stable plugins
│ ├── header_rewrite/ # Header manipulation (see HRW.instructions.md)
│ └── experimental/ # Experimental plugins
│
└── tools/
└── hrw4u/ # Header Rewrite DSL compiler
include/iocore/eventsystem/Continuation.h - Core async patternsrc/proxy/http/HttpSM.cc - HTTP state machine (most important)src/iocore/cache/Cache.cc - Cache implementationinclude/ts/ts.h - Plugin API (most stable interface)include/tscore/ink_memory.h - Memory allocation functionsBefore writing new code, look for similar existing code:
example/plugins/ for simple patternsplugins/ for production patternsplugins/experimental/ for newer approachesPattern discovery:
include/ts/ts.h for plugin API patternstests/gold_tests/ for usage examplesTypical file structure for a plugin:
plugins/my_plugin/ ├── my_plugin.cc # Main plugin logic ├── handler.cc # Request/response handlers ├── handler.h # Handler interface ├── config.cc # Configuration parsing └── CMakeLists.txt # Build configuration
Typical class structure:
Continuation for async operationshandle_event() for event processingGeneral structure for async operations:
Action*)handle_event()EVENT_DONE when completeAlways async, never blocking:
schedule_in() or schedule_at()Recoverable errors:
Unrecoverable errors:
ink_release_assert() for conditions that should never happenWhen adding new functionality:
tests/gold_tests/ (autest)Test.ATSReplayTest() with replay.yaml format (Proxy Verifier). If ATSReplayTest doesn't fit, prefer organizing the test around a test class with separate functions for configuring the servers, ATS, client, etc.StillRunningAfter and StillRunningBefore are assertions about whether a process is running at a test-run boundary. They do not keep processes alive, extend their lifetime, or control teardown.tr.StillRunningAfter does not cause AuTest to stop it. Do not request adding a process to this assertion on every run as a way to preserve it for later runs.Test scope and started with StartBefore can serve multiple test runs without appearing in each run's StillRunningAfter. Check the process scope, startup dependencies, and actual shutdown behavior before reporting a lifetime bug; a missing assertion alone is not evidence.src/records/RecordsConfig.cc:{RECT_CONFIG, "proxy.config.my_feature.enabled", RECD_INT, "0", RECU_RESTART_TS, RR_NULL, RECC_INT, nullptr, RECA_NULL}
int enabled = 0; REC_ReadConfigInteger(enabled, "proxy.config.my_feature.enabled");
❌ Blocking in event threads:
// WRONG - blocks event thread sleep(5); blocking_network_call();
✅ Use async operations:
// CORRECT - schedules continuation eventProcessor.schedule_in(this, HRTIME_SECONDS(5));
❌ Manual memory management:
// WRONG auto *obj = new MyObject(); // ... might leak if exception thrown delete obj;
✅ Use RAII:
// CORRECT auto obj = std::make_unique<MyObject>(); // Automatically cleaned up
❌ Creating threads:
// WRONG std::thread t([](){ do_work(); });
✅ Use event system:
// CORRECT eventProcessor.schedule_imm(continuation, ET_CALL);
include/ts/ts.hinclude/iocore/eventsystem/src/proxy/http/HttpSM.ccdoc/developer-guide/