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: CamelCaseHttpSM, NetVConnection, CacheProcessor
  • Functions/variables: snake_casehandle_request(), server_port, cache_key
  • Constants/macros: UPPER_CASEHTTP_STATUS_OK, MAX_BUFFER_SIZE
  • Member variables: snake_case with no prefix → connection_count, buffer_size

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

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)
  4. Test both success and error paths

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/