Apache Traffic Server - GitHub Copilot Instructions

Repository Overview

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:

  • ~500K lines of C++20 code (strict - no C++23)
  • Event-driven architecture using Continuation callbacks
  • Handles HTTP/1.1, HTTP/2, HTTP/3 (QUIC)
  • Supports TLS termination and caching
  • Extensible via C/C++ plugins

Code Style Requirements

C++ Style Guidelines

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:

  • Line length: 132 characters maximum
  • Indentation: 2 spaces (never tabs)
  • Braces: Linux kernel style (opening brace on same line)
if (condition) {
  // code here
}
  • Pointer/reference alignment: Right side
Type *ptr;        // Correct
Type &ref;        // Correct
Type* ptr;        // Wrong
  • Empty line after declarations:
void function() {
  int x = 5;
  std::string name = "test";

  // Empty line before first code statement
  process_data(x, name);
}
  • Always use braces for if/while/for (no naked conditions):
if (x > 0) {    // Correct
  foo();
}

if (x > 0)      // Wrong - missing braces
  foo();
  • Keep variable declarations together:
// 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:

  • Classes: CamelCase → HttpSM, NetVConnection, CacheProcessor
  • Functions/variables: snake_case → handle_request(), server_port, cache_key
  • Constants/macros: UPPER_CASE → HTTP_STATUS_OK, MAX_BUFFER_SIZE
  • Private member variables use both _ 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.
  • Boolean variables and functions returning boolean values are named as predicates: use 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:

  • Use RAII and smart pointers (std::unique_ptr, std::shared_ptr)
  • Prefer smart pointers over raw new/delete when possible
  • Use ats_malloc()/ats_free() for large allocations (not malloc)
  • Use IOBuffer for network data (zero-copy design)
  • Note: Some subsystems legitimately use explicit deletes / delete this (e.g., continuation-based code)

What NOT to Use:

  • ❌ C++23 features (code must compile with C++20)
  • ❌ malloc/free for large allocations (use ats_malloc), or prefer heaps or stack allocations
  • ❌ Blocking operations in event threads
  • ❌ Creating threads manually (use async event system)

Comments and Documentation

Minimal comments philosophy:

  • Only add comments where code isn't self-explanatory
  • Don't describe what the code does (the code already shows that)
  • Do explain why something is done if not obvious
  • Avoid stating the obvious
// 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:

  • Non-obvious algorithms or math
  • Workarounds for bugs in dependencies
  • Performance optimizations that reduce clarity
  • Security-critical sections
  • Complex state machine transitions

When NOT to add comments:

  • Self-documenting code
  • Obvious operations
  • Function/variable names that explain themselves

Python Style

  • Python 3.11+ with type hints
  • 4-space indentation (never tabs)
  • Type annotations on all function signatures
  • Use f'...' strings rather than .format() strings for new Python code.

License Headers

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.
 */

Architecture Patterns

Event-Driven Model (CRITICAL)

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:

  • ⚠️ NEVER block in event threads - use async I/O or thread pools
  • All async operations use Continuation callbacks
  • Return EVENT_DONE, EVENT_CONT, or EVENT_ERROR from handlers
  • Use EThread::schedule() for deferred work

HTTP State Machine

The 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 request
  • TS_HTTP_SEND_REQUEST_HDR_HOOK - Before sending to origin
  • TS_HTTP_READ_RESPONSE_HDR_HOOK - After reading origin response
  • TS_HTTP_SEND_RESPONSE_HDR_HOOK - Before sending to client

Threading Model

  • Event threads: Handle most async work (never block here)
  • DNS threads: Dedicated DNS resolution pool
  • Disk I/O threads: Cache disk operations
  • Network threads: Actually event threads handling network I/O

Rule: Don't create threads. Use the event system or existing thread pools.

Debug Logging Pattern

// 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).

Project Structure (Key Paths)

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

Key Files to Understand

  • include/iocore/eventsystem/Continuation.h - Core async pattern
  • src/proxy/http/HttpSM.cc - HTTP state machine (most important)
  • src/iocore/cache/Cache.cc - Cache implementation
  • include/ts/ts.h - Plugin API (most stable interface)
  • include/tscore/ink_memory.h - Memory allocation functions

Common Patterns

Finding Examples

Before writing new code, look for similar existing code:

  • Plugin examples: example/plugins/ for simple patterns
  • Stable plugins: plugins/ for production patterns
  • Experimental plugins: plugins/experimental/ for newer approaches

Pattern discovery:

  • Search for similar functionality in existing code
  • Check include/ts/ts.h for plugin API patterns
  • Look at tests in tests/gold_tests/ for usage examples

Code Organization

Typical 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:

  • Inherit from Continuation for async operations
  • Implement handle_event() for event processing
  • Store state in class members, not globals
  • Clean up resources in destructor (RAII)

Async Operation Pattern

General structure for async operations:

  1. Create continuation with callback
  2. Initiate async operation (returns Action*)
  3. Handle callback events in handle_event()
  4. Return EVENT_DONE when complete

Always async, never blocking:

  • Network I/O → Use VConnection
  • Cache operations → Use CacheProcessor
  • DNS lookups → Use DNSProcessor
  • Delayed work → Use schedule_in() or schedule_at()

Error Handling

Recoverable errors:

  • Return error codes
  • Log with appropriate severity
  • Clean up resources (RAII helps)

Unrecoverable errors:

  • Use ink_release_assert() for conditions that should never happen
  • Log detailed context before asserting

Testing Approach

When adding new functionality:

  1. Check if unit tests exist in same directory (Catch2)
  2. Add integration tests in tests/gold_tests/ (autest)
  3. Prefer 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.
  4. Test both success and error paths

AuTest Process Lifetime in Code Reviews

  • 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.
  • Omitting an origin, DNS server, or ATS process from 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.
  • A process created at 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.
  • Consult the AuTest TestRun API for the assertion and process-ordering semantics.

Configuration

Adding New Configuration Records

  1. Define in src/records/RecordsConfig.cc:
{RECT_CONFIG, "proxy.config.my_feature.enabled", RECD_INT, "0", RECU_RESTART_TS, RR_NULL, RECC_INT, nullptr, RECA_NULL}
  1. Read in code:
int enabled = 0;
REC_ReadConfigInteger(enabled, "proxy.config.my_feature.enabled");

What to Avoid

Common Mistakes

❌ 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);

Additional Resources

  • Plugin API: include/ts/ts.h
  • Event system: include/iocore/eventsystem/
  • HTTP state machine: src/proxy/http/HttpSM.cc
  • Documentation: doc/developer-guide/