TSCfgRegister
Register a plugin’s configuration file with the Traffic Server reload framework and drive the per-reload context object delivered to the handler.
Synopsis
#include <ts/ts.h>
-
enum TSCfgSourceType
What content sources a plugin’s config handler supports.
-
enumerator TS_CFG_SOURCE_FILE_ONLY
Handler reloads only from a file on disk. RPC-supplied YAML is rejected.
-
enumerator TS_CFG_SOURCE_FILE_AND_RPC
Handler can also process YAML content supplied via RPC.
-
enumerator TS_CFG_SOURCE_FILE_ONLY
-
type TSCfgRegistrationInfo
Options struct passed to
TSCfgRegister(). Fields are set with C++20 designated initializers; new fields may be appended in future Traffic Server versions without breaking source compatibility for existing plugins.-
std::string_view key
Unique registry key, also used as the YAML node name when an operator reloads via
traffic_ctl config reload --data '{<key>: {...}}'. Must be unique across all registered configs (core and plugin).Convention. Use the plugin name (
my_plugin). Keep the key alphanumeric + underscore; avoid dots, sincetraffic_ctl --directive my_plugin.dry_run=trueparses the first.as the directive separator.Plugins with multiple files:
If the files share the same logical config and should reload together, register once and add the rest via
TSCfgAddFileDependency()(single task per reload).If the files are genuinely independent and reloadable on their own, register each separately with a disambiguating key (e.g.
my_plugin_main,my_plugin_aux). Each becomes its own task entry intraffic_ctl config status.
Required.
-
std::string_view config_path
Default config file path (absolute, or relative to
sysconfdir). Required.
-
std::string_view filename_record
Fully-qualified record name that holds the active config filename, or
{}(default) if the path is fixed. When set, the operator can change the active config file at runtime by editing this record;config_pathis used as the fallback when the record is empty or unset. Optional.
-
TSCfgLoadCb handler
Reload callback invoked when the file changes, when an attached trigger record changes, or when the operator triggers an RPC reload. Required.
-
void *data
Opaque pointer passed unmodified to
handleron every invocation. The plugin owns it; ATS does not copy or free it. Must outlive the registration - typically astaticor once-allocated heap object stashed inTSPluginInit:static PluginState state; state.config_path = ...; TSCfgRegistrationInfo info{}; info.handler = config_reload; info.data = &state; TSCfgRegister(&info);
-
TSCfgSourceType source
Whether
handlercan also process YAML content supplied via RPC. Defaults toTSCfgSourceType::TS_CFG_SOURCE_FILE_ONLY.
-
bool is_required
Hint propagated to FileManager: marks the file as “required” in catalog/inspection output and emits a
Dbgline if the file is missing at registration time. The framework does not enforce this flag at reload-time today: the handler is still invoked even when the file is missing or invalid, and the plugin chooses whether to fail the reload. Defaults tofalse.
-
std::string_view key
-
type TSCfgFileDependencyInfo
Options struct passed to
TSCfgAddFileDependency(). Onlykeyandconfig_pathare required.-
std::string_view key
Parent registry key as passed to
TSCfgRegister(). Required.
-
std::string_view config_path
Default companion file path (absolute, or relative to
sysconfdir). Required.
-
std::string_view filename_record
Fully-qualified record name that holds the filename, or
{}if the path is fixed. Optional.
-
std::string_view dep_key
Routing key for inline YAML supplied via JSONRPC. When non-empty, content delivered under this top-level node is routed to the parent entry’s handler, giving the plugin parity with core composite-config patterns. When empty (default), the dependency is file-change-only.
-
bool is_required
Hint propagated to FileManager (catalog/inspection only); see the equivalent field on
TSCfgRegistrationInfo. Defaults tofalse.
-
std::string_view key
-
type TSCfgLoadCtx
Opaque handle to a per-reload context. The context tracks the lifecycle of a single reload attempt for one registered config: it carries the reload token, supplied YAML (for RPC reloads), the filename (for file reloads), and the task state machine the framework uses to aggregate results.
-
typedef void (*TSCfgLoadCb)(TSCfgLoadCtx ctx, void *data)
Plugin reload callback signature.
datais the opaque pointer the plugin supplied viaTSCfgRegistrationInfo::data.
-
enum TSCfgLogLevel
Severity for log entries emitted via
TSCfgLoadCtxAddLog().-
enumerator TS_CFG_LOG_NOTE
Informational. Default level. Maps to
DL_Noteinternally.
-
enumerator TS_CFG_LOG_WARNING
Concerning; the reload may still complete. Maps to
DL_Warning.
-
enumerator TS_CFG_LOG_ERROR
Failure cause; pair with a subsequent
TSCfgLoadCtxFail(). Maps toDL_Error.
The framework deliberately does not expose the fatal-class diagnostics levels here: a reload handler should not be able to terminate the process. Plugins that truly need debug-only output should use
Dbg(ctl, ...)instead of this API.-
enumerator TS_CFG_LOG_NOTE
-
TSReturnCode TSCfgRegister(const TSCfgRegistrationInfo *info)
-
bool TSCfgIsRegistered(std::string_view key)
-
TSReturnCode TSCfgAttachReloadTrigger(std::string_view key, std::string_view record_name)
-
TSReturnCode TSCfgAddFileDependency(const TSCfgFileDependencyInfo *info)
-
void TSCfgLoadCtxInProgress(TSCfgLoadCtx ctx, std::string_view msg)
-
void TSCfgLoadCtxComplete(TSCfgLoadCtx ctx, std::string_view msg)
-
void TSCfgLoadCtxFail(TSCfgLoadCtx ctx, std::string_view msg)
-
void TSCfgLoadCtxAddLog(TSCfgLoadCtx ctx, TSCfgLogLevel level, std::string_view msg)
-
TSCfgLoadCtx TSCfgLoadCtxAddSubtask(TSCfgLoadCtx ctx, std::string_view description)
-
std::string_view TSCfgLoadCtxGetFilename(TSCfgLoadCtx ctx)
-
std::string_view TSCfgLoadCtxGetReloadToken(TSCfgLoadCtx ctx)
-
TSYaml TSCfgLoadCtxGetSuppliedYaml(TSCfgLoadCtx ctx)
-
TSYaml TSCfgLoadCtxGetReloadDirectives(TSCfgLoadCtx ctx)
Description
These functions are the plugin-facing entry points to the Traffic Server configuration reload framework. A plugin registers a config file together with a reload callback, and the framework drives the callback whenever the file changes, an attached trigger record changes, or an operator triggers a reload via JSONRPC.
The plugin name (set via TSPluginRegister()) is automatically
attached to every registered entry, so reload-trace logs and
traffic_ctl config status output identify which plugin owns
which entry as [plugin: <name>]. Plugins do not pass their name
explicitly.
Registration
TSCfgRegister()Registers a config file and reload handler.
Must be called from
TSPluginInit(), afterTSPluginRegister(). ReturnsTS_ERRORif called outsideTSPluginInit, beforeTSPluginRegister, or with a null or incompleteinfostruct.If another plugin (or core) has already registered the same key, the duplicate registration is rejected, a warning is logged identifying both owners, and
TS_ERRORis returned. The colliding registration is not installed, so the handler would never run; the plugin can detect this and log or bail out. Plugins that may be loaded more than once can probe first withTSCfgIsRegistered()to avoid the duplicate attempt entirely.TSCfgIsRegistered()Returns
trueifkeyis already registered (by any plugin or core),falseotherwise (including for an emptykey). Plugins that may be loaded more than once - e.g. by an admin retry, or as a remap plugin instance - can call this beforeTSCfgRegister()to skip a duplicate registration that would otherwise be rejected.TSCfgAttachReloadTrigger()Wires a record so that changing its value re-runs the reload handler registered for
key. Internally registers a record-change callback (RecRegisterConfigUpdateCb) and routes the event back into the same reload pipeline as file changes: the plugin’sTSCfgLoadCbis invoked with the resolved file path available viaTSCfgLoadCtxGetFilename(), exactly as it would be for an on-disk file change. May be called multiple times to attach more than one trigger record to the same entry.Core analog.
ConfigRegistry::register_config()accepts atrigger_recordsinitializer-list at registration time (e.g.ssl_multicertlists ~10 record names there). The post-registration form isConfigRegistry::attach(). This function is the plugin-facing wrapper for the latter; the initializer-list shape is intentionally not exposed onTSCfgRegistrationInfoso the option struct stays ABI-stable.It triggers a reload, nothing else. Specifically the plugin cannot:
Register a free-form record-change callback. There is no
TSRecordRegisterChangeCbtoday; the underlying primitive (RecRegisterConfigUpdateCb) is internal-only.Receive record-change details. The handler gets no record name, no old/new value, no event payload. Use
TSMgmt*Get()inside the handler if it needs the value.Subscribe a record without a registered config key. Calling with an unknown
keyreturnsTS_ERROR.Multiplex one record across two keys, or attach in any shape other than (one record, one config key, per call).
The reload is always treated as file-driven (no RPC payload):
TSCfgLoadCtxGetSuppliedYaml()andTSCfgLoadCtxGetReloadDirectives()returnnullptrfor record-triggered invocations. Standalone record changes (i.e.traffic_ctl config setoutside an active reload cycle) invoke the handler with an empty context that does not surface intraffic_ctl config status- same as core record-triggered reloads outside a reload cycle.TSCfgAddFileDependency()Adds a companion file to a previously registered key. When the companion file changes on disk - or when an operator submits inline YAML under
TSCfgFileDependencyInfo::dep_keyvia JSONRPC, when set - the plugin’s handler is invoked. Withdep_keyempty the dependency is file-change-only.
Per-reload context (TSCfgLoadCtx)
A TSCfgLoadCtx is created by the framework before invoking the
plugin’s TSCfgLoadCb. The handler must drive the context to a
terminal state - either TSCfgLoadCtxComplete() or
TSCfgLoadCtxFail() - so the reload tree can finish. See
Terminal State Rule for the complete contract.
State transitions:
TSCfgLoadCtxInProgress()Set the task to
IN_PROGRESSand emit an optional progress message. Calling it more than once on the same context is discouraged - useTSCfgLoadCtxAddLog()for periodic log output. Pass{}formsgif no message is needed.TSCfgLoadCtxComplete()Mark the task as successfully completed. Pass
{}formsgif no message is needed. This call finalizes and invalidatesctx: do not usectxafterwards. A later call on the same handle (a second Complete/Fail, or any accessor) is ignored and logged as misuse rather than acting on freed state, but plugins must not rely on that as normal control flow.TSCfgLoadCtxFail()Mark the task as failed. Pass
{}formsgif no message is needed. This call finalizes and invalidatesctxwith the same post-finalize semantics asTSCfgLoadCtxComplete().TSCfgLoadCtxAddLog()Append a log entry at
levelto the task. Visible intraffic_ctl config statusoutput and in the get_reload_config_status JSONRPC response. Does not change the task state.TSCfgLoadCtxAddSubtask()Create a child subtask. Returns a new context that the plugin must independently drive to a terminal state. Use this when a single config reload spawns several distinct units of work that should be tracked separately. The parent’s status aggregates from its children: any child failing causes the parent to be marked failed.
The subtask is born in
CREATEDand follows the same state machine as the parent. Two transition patterns are valid:CREATED -> IN_PROGRESS -> SUCCESS / FAILED(recommended for substantive work) - callTSCfgLoadCtxInProgress()once you start, thenTSCfgLoadCtxComplete()/TSCfgLoadCtxFail()when done. The “in progress” phase is visible intraffic_ctl config status.CREATED -> SUCCESS / FAILED(shortcut for trivial subtasks) - callTSCfgLoadCtxComplete()/TSCfgLoadCtxFail()directly on the returned handle.
Inputs:
TSCfgLoadCtxGetFilename()Returns the path the framework expects this handler to read. Two-step resolution:
If the registration’s
filename_recordwas set AND the record currently has a non-empty value, that value is returned (the operator can override the filename at runtime viatraffic_ctl config set <record>).Otherwise, returns
config_pathas-registered.
Most plugins don’t set
filename_recordand could equivalently use their own stashed copy of the registered path - this function is the canonical way to get the filename only whenfilename_recordis in play.Always populated for plugin handlers, including on RPC reloads. To detect RPC content, check
TSCfgLoadCtxGetSuppliedYaml()- not this function.Core analog.
ConfigReloadTask::get_filename(). For core handlers, the value is empty on RPC reloads; the plugin wrapper always populates it so plugins can useTSCfgLoadCtxGetSuppliedYaml()as the canonical RPC-detection signal.TSCfgLoadCtxGetReloadToken()Returns the reload-cycle correlation token (e.g.
rldtk-<timestamp>). Useful for plugin-side log lines that need to match the operator’straffic_ctl config status -t <token>view.TSCfgLoadCtxGetSuppliedYaml()Returns the YAML content supplied via the JSONRPC reload payload as an opaque
TSYamlhandle (aYAML::Node *). The framework-reserved_reloaddirectives are stripped before delivery. The returned node is undefined when the reload was driven by file change rather than RPC. Only meaningful when the entry was registered withTSCfgSourceType::TS_CFG_SOURCE_FILE_AND_RPC.TSCfgLoadCtxGetReloadDirectives()Returns the YAML map extracted from the
_reloadkey in the RPC-supplied content as an opaqueTSYamlhandle. Used by handlers that need scoped reload behavior (for example, reload only one entry, dry-run mode, or a version constraint). See Configuration Reload Framework for the directive convention.
The two std::string_view accessors return views whose backing
storage is owned by the framework and is valid for the lifetime of the
handler call. Copy the data if you need to retain it past the callback
return. The TSYaml accessors return handles whose lifetime is
also tied to the handler call.
Lifecycle and threading
The handler runs on an ET_TASK thread. It does not have to
complete synchronously: the plugin may stash ctx in its own state,
return from the callback, finish work on a different thread, and then
call TSCfgLoadCtxComplete() or TSCfgLoadCtxFail() from
there. This is the standard pattern for plugins that perform
asynchronous I/O during reload.
If the handler never reaches a terminal state, the per-reload progress
checker eventually marks the task as TIMEOUT and unblocks the
overall reload (see Terminal State Rule). The context
handle then leaks for the process lifetime, so reaching a terminal
state on every code path is mandatory.
Recommended pattern: parse first, apply second
The framework does not provide a separate validation phase. The handler is the single point where the plugin parses the new configuration and applies it. To avoid leaving live state half-mutated on a partial parse, plugins should follow a two-step pattern inside the same handler:
Parse the file (or supplied YAML) into a fresh, staging-side structure. Validate fully. Do not touch live state during this step. On parse failure, call
TSCfgLoadCtxFail()and return - live state remains untouched.Swap the staging structure into place atomically. This is typically a pointer swap into a
std::shared_ptr/std::atomicslot the request path reads from. After the swap succeeds, callTSCfgLoadCtxComplete().
This pattern matches what core configs already do (for example,
ip_allow, remap.config) and gives operators predictable
behavior on a malformed reload: the previous configuration stays in
effect until a fully valid one is loaded.
Restrictions
Not supported for remap plugins (
TSRemapInit()/TSRemapNewInstance()).TSCfgRegister(),TSCfgAttachReloadTrigger(), andTSCfgAddFileDependency()must all be called fromTSPluginInit(), afterTSPluginRegister().
Example - registration and synchronous handler
#include <ts/ts.h>
#include <string>
namespace
{
constexpr char PLUGIN_NAME[] = "my_plugin";
struct PluginState {
std::string config_path;
};
void
config_reload(TSCfgLoadCtx ctx, void *data)
{
auto *state = static_cast<PluginState *>(data);
std::string_view filename = TSCfgLoadCtxGetFilename(ctx);
if (filename.empty()) {
TSCfgLoadCtxFail(ctx, "no filename available");
return;
}
if (!parse_and_apply(state, std::string{filename})) {
TSCfgLoadCtxFail(ctx, "parse failed");
return;
}
TSCfgLoadCtxComplete(ctx, "Reloaded my_plugin");
}
} // anonymous namespace
void
TSPluginInit(int /* argc */, const char * /* argv */[])
{
TSPluginRegistrationInfo plugin{};
plugin.plugin_name = PLUGIN_NAME;
plugin.vendor_name = "Example Inc.";
plugin.support_email = "support@example.com";
if (TSPluginRegister(&plugin) != TS_SUCCESS) {
TSError("[%s] plugin registration failed", PLUGIN_NAME);
return;
}
static PluginState state;
state.config_path = std::string{TSConfigDirGet()} + "/my_plugin.yaml";
TSCfgRegistrationInfo info{};
info.key = PLUGIN_NAME;
info.config_path = state.config_path;
info.handler = config_reload;
info.data = &state;
info.source = TS_CFG_SOURCE_FILE_AND_RPC;
info.is_required = false;
if (TSCfgRegister(&info) != TS_SUCCESS) {
TSError("[%s] TSCfgRegister failed for '%s'", PLUGIN_NAME,
state.config_path.c_str());
return;
}
// Optional: attach a record so changing it fires the handler.
TSCfgAttachReloadTrigger(PLUGIN_NAME, "proxy.config.my_plugin.enabled");
}
Example - RPC content with directives
#include <yaml-cpp/yaml.h>
void
config_reload(TSCfgLoadCtx ctx, void * /* data */)
{
auto *yaml = static_cast<YAML::Node *>(TSCfgLoadCtxGetSuppliedYaml(ctx));
auto *dirs = static_cast<YAML::Node *>(TSCfgLoadCtxGetReloadDirectives(ctx));
if (dirs && dirs->IsDefined()) {
// operator passed --directive ...
}
if (yaml && yaml->IsDefined()) {
// RPC-supplied content; do not read the file.
} else {
// file-driven reload; read TSCfgLoadCtxGetFilename(ctx).
}
TSCfgLoadCtxComplete(ctx, {});
}
Example - deferred completion
A plugin that schedules background work returns from the callback
without calling Complete or Fail; it stashes ctx and finishes from
another thread:
struct Work { TSCfgLoadCtx ctx; PluginState *state; };
void
config_reload(TSCfgLoadCtx ctx, void *data)
{
auto *work = new Work{ctx, static_cast<PluginState *>(data)};
// schedule heavy_work(work) on a worker thread; it will eventually
// call TSCfgLoadCtxComplete(work->ctx, {}) (or Fail) and delete work.
schedule_async(work);
}
A runnable example of the deferred pattern lives in
tests/gold_tests/jsonrpc/plugins/cfg_plugin_deferred_test.cc.
See Also
Configuration Reload Framework is the complete framework guide,
covering the registry, terminal-state rule, RPC reload payload format,
[plugin: <name>] attribution in traffic_ctl config status,
diagnostic macros, and diags.log summaries.