Copilot commented on code in PR #13146:
URL: https://github.com/apache/trafficserver/pull/13146#discussion_r3302724393


##########
src/api/InkAPI.cc:
##########
@@ -3299,6 +3302,347 @@ TSMgmtUpdateRegister(TSCont contp, const char 
*plugin_name, const char *plugin_f
   global_config_cbs->insert(reinterpret_cast<INKContInternal *>(contp), 
plugin_name, plugin_file_name);
 }
 
+////////////////////////////////////////////////////////////////////
+//
+// Config Registry - plugin config reload registration
+//
+////////////////////////////////////////////////////////////////////
+
+namespace
+{
+// Handle behind the opaque TSCfgLoadCtx. The wrapper lambda new's one per
+// handler invocation; TSCfgLoadCtx{Complete,Fail} delete it. Caches the
+// strings/nodes the getters need to return as stable pointers.
+struct PluginConfigContext {
+  ConfigContext     ctx;
+  std::string       filename;
+  std::string       reload_token;
+  YAML::Node        supplied_yaml;
+  YAML::Node        reload_directives;
+  std::atomic<bool> consumed{false};
+};
+
+DbgCtl dbg_ctl_plugin_config{"config.reload"};
+
+/// Verify we're inside TSPluginInit() after a successful TSPluginRegister().
+/// Returns the active plugin name on success, nullptr on failure (after 
logging).
+const char *
+validate_plugin_init(const char *api_fn)
+{
+  if (!plugin_reg_current) {
+    Error("[unknown-plugin] %s must be called from TSPluginInit()", api_fn);
+    return nullptr;
+  }
+  if (!plugin_reg_current->plugin_registered || 
plugin_reg_current->plugin_name == nullptr) {
+    Error("[%s] %s must be called after TSPluginRegister() with a non-null 
plugin_name",
+          plugin_reg_current->plugin_path ? plugin_reg_current->plugin_path : 
"?", api_fn);
+    return nullptr;
+  }
+  return plugin_reg_current->plugin_name;
+}
+} // anonymous namespace
+
+TSReturnCode
+TSCfgRegister(const TSCfgRegistrationInfo *info)
+{
+  const char *plugin_name = validate_plugin_init("TSCfgRegister");
+  if (!plugin_name) {
+    return TS_ERROR;
+  }
+
+  if (info == nullptr) {
+    Error("[%s] TSCfgRegister: info is null", plugin_name);
+    return TS_ERROR;
+  }
+  if (info->key.empty() || info->config_path.empty() || info->handler == 
nullptr) {
+    Error("[%s] TSCfgRegister: missing required fields 
(key/config_path/handler)", plugin_name);
+    return TS_ERROR;
+  }
+
+  std::string key_str{info->key};
+  std::string config_path_str{info->config_path};
+  std::string filename_record_str{info->filename_record};
+
+  config::ConfigSource cfg_source =
+    (info->source == TS_CFG_SOURCE_FILE_AND_RPC) ? 
config::ConfigSource::FileAndRpc : config::ConfigSource::FileOnly;
+
+  auto cb    = info->handler;
+  auto udata = info->data;
+
+  // Lambda captures only the function pointer + opaque user data. Plugin name
+  // and key live on the Entry; the wrapper looks them up via the ctx that the
+  // framework attaches them to (set_plugin_name in ConfigRegistry).
+  config::ConfigReloadHandler wrapper = [cb, udata](ConfigContext ctx) {
+    auto *handle              = new PluginConfigContext{};
+    handle->reload_token      = ctx.get_reload_token();
+    handle->supplied_yaml     = ctx.supplied_yaml();
+    handle->reload_directives = ctx.reload_directives();
+    auto const &key           = ctx.get_description();
+    if (auto const *entry = config::ConfigRegistry::Get_Instance().find(key); 
entry != nullptr) {
+      handle->filename = entry->resolve_filename();
+    }
+    handle->ctx = std::move(ctx);
+
+    Dbg(dbg_ctl_plugin_config, "Invoking plugin config handler for '%s'", 
key.c_str());
+    cb(reinterpret_cast<TSCfgLoadCtx>(handle), udata);
+    // Past this point @p handle may be deleted (synchronous completion). 
Deferred
+    // completion is detected upstream via ctx.is_terminal(). If the plugin 
never
+    // completes, the handle leaks but core's progress timeout still unblocks.
+  };
+
+  // Duplicate-key handling lives in ConfigRegistry::do_register and emits a
+  // Warning identifying both owners; callers can probe with TSCfgIsRegistered.
+  config::ConfigRegistry::Get_Instance().register_plugin_config(key_str, 
plugin_name, config_path_str, filename_record_str,
+                                                                
std::move(wrapper), cfg_source, {}, info->is_required);
+
+  Dbg(dbg_ctl_plugin_config, "[%s] TSCfgRegister: registered '%s' (file: %s)", 
plugin_name, key_str.c_str(),
+      config_path_str.c_str());
+  return TS_SUCCESS;
+}
+
+bool
+TSCfgIsRegistered(std::string_view key)
+{
+  if (key.empty()) {
+    return false;
+  }
+  return config::ConfigRegistry::Get_Instance().contains(std::string{key});
+}
+
+TSReturnCode
+TSCfgAttachReloadTrigger(std::string_view key, std::string_view record_name)
+{
+  const char *plugin_name = validate_plugin_init("TSCfgAttachReloadTrigger");
+  if (!plugin_name) {
+    return TS_ERROR;
+  }
+  if (key.empty() || record_name.empty()) {
+    Error("[%s] TSCfgAttachReloadTrigger: key and record_name required", 
plugin_name);
+    return TS_ERROR;
+  }
+
+  std::string key_str{key};
+  std::string record_str{record_name};
+  if (config::ConfigRegistry::Get_Instance().attach(key_str, 
record_str.c_str()) != 0) {
+    Dbg(dbg_ctl_plugin_config, "[%s] TSCfgAttachReloadTrigger: key '%s' not 
found", plugin_name, key_str.c_str());
+    return TS_ERROR;
+  }
+
+  Dbg(dbg_ctl_plugin_config, "[%s] TSCfgAttachReloadTrigger: attached record 
'%s' to config '%s'", plugin_name, record_str.c_str(),
+      key_str.c_str());
+  return TS_SUCCESS;
+}
+
+TSReturnCode
+TSCfgAddFileDependency(const TSCfgFileDependencyInfo *info)
+{
+  const char *plugin_name = validate_plugin_init("TSCfgAddFileDependency");
+  if (!plugin_name) {
+    return TS_ERROR;
+  }
+  if (info == nullptr) {
+    Error("[%s] TSCfgAddFileDependency: info is null", plugin_name);
+    return TS_ERROR;
+  }
+  if (info->key.empty() || info->config_path.empty()) {
+    Error("[%s] TSCfgAddFileDependency: missing required fields 
(key/config_path)", plugin_name);
+    return TS_ERROR;
+  }
+
+  auto       &registry            = config::ConfigRegistry::Get_Instance();
+  std::string key_str             = std::string{info->key};
+  std::string path_str            = std::string{info->config_path};
+  std::string filename_record_str = std::string{info->filename_record};
+  std::string dep_key_str         = std::string{info->dep_key};
+  bool const  has_dep_key         = !dep_key_str.empty();
+
+  // filename_record_str may be empty; add_file_dependency / 
resolve_config_filename treat empty as "no record".
+  const char *filename_record_arg = filename_record_str.empty() ? nullptr : 
filename_record_str.c_str();
+
+  int rc = has_dep_key ?
+             registry.add_file_and_node_dependency(key_str, dep_key_str, 
filename_record_arg, path_str.c_str(), info->is_required) :
+             registry.add_file_dependency(key_str, filename_record_arg, 
path_str.c_str(), info->is_required);
+
+  if (rc != 0) {
+    Dbg(dbg_ctl_plugin_config, "[%s] TSCfgAddFileDependency: key '%s' not 
found", plugin_name, key_str.c_str());

Review Comment:
   TSCfgAddFileDependency logs "key '%s' not found" whenever the registry call 
returns non-zero, but add_file_dependency/add_file_and_node_dependency can also 
fail for reasons other than an unknown key (e.g., dep_key collisions, 
record-callback wiring failures). Please adjust the diagnostic so it doesn’t 
misreport the failure cause (include key/dep_key/filename_record and avoid 
claiming the key is missing unless verified).
   



##########
src/mgmt/config/ConfigRegistry.cc:
##########
@@ -333,40 +377,54 @@ ConfigRegistry::add_file_dependency(const std::string 
&key, const char *filename
     config_key = it->second.key;
   }
 
-  auto resolved = resolve_config_filename(filename_record, default_filename);
+  bool has_record = (filename_record != nullptr && filename_record[0] != '\0');
+  auto resolved   = resolve_config_filename(has_record ? filename_record : 
nullptr, default_filename);
 
-  Dbg(dbg_ctl, "Adding file dependency '%s' (resolved: %s) to config '%s'", 
filename_record, resolved.c_str(), key.c_str());
+  Dbg(dbg_ctl, "Adding file dependency '%s' (resolved: %s) to config '%s'", 
has_record ? filename_record : "<none>",
+      resolved.c_str(), key.c_str());
 
   // Register with FileManager for mtime-based change detection.
-  // When rereadConfig() detects the file changed, it calls 
RecSetSyncRequired()
-  // on the filename_record, which triggers on_record_change below.
-  FileManager::instance().addFile(resolved.c_str(), filename_record, false, 
is_required);
+  // When filename_record is empty (e.g. plugin configs), pass the config key
+  // as the configName so process_config_update routes mtime changes back to
+  // ConfigRegistry::schedule_reload().
+  const char *config_name = has_record ? filename_record : config_key.c_str();
+  FileManager::instance().addFile(resolved.c_str(), config_name, false, 
is_required);
+
+  if (has_record) {
+    return wire_record_callback(filename_record, config_key);
+  }

Review Comment:
   ConfigRegistry::add_file_dependency registers the file with FileManager 
before attempting wire_record_callback() when filename_record is set. If 
callback wiring fails, FileManager will still watch the file even though 
changes won’t trigger the registry handler. Consider wiring the record callback 
first (or rolling back the FileManager registration on failure) to avoid 
leaving partial state after an error.



##########
src/api/InkAPI.cc:
##########
@@ -3299,6 +3302,347 @@ TSMgmtUpdateRegister(TSCont contp, const char 
*plugin_name, const char *plugin_f
   global_config_cbs->insert(reinterpret_cast<INKContInternal *>(contp), 
plugin_name, plugin_file_name);
 }
 
+////////////////////////////////////////////////////////////////////
+//
+// Config Registry - plugin config reload registration
+//
+////////////////////////////////////////////////////////////////////
+
+namespace
+{
+// Handle behind the opaque TSCfgLoadCtx. The wrapper lambda new's one per
+// handler invocation; TSCfgLoadCtx{Complete,Fail} delete it. Caches the
+// strings/nodes the getters need to return as stable pointers.
+struct PluginConfigContext {
+  ConfigContext     ctx;
+  std::string       filename;
+  std::string       reload_token;
+  YAML::Node        supplied_yaml;
+  YAML::Node        reload_directives;
+  std::atomic<bool> consumed{false};
+};
+
+DbgCtl dbg_ctl_plugin_config{"config.reload"};
+
+/// Verify we're inside TSPluginInit() after a successful TSPluginRegister().
+/// Returns the active plugin name on success, nullptr on failure (after 
logging).
+const char *
+validate_plugin_init(const char *api_fn)
+{
+  if (!plugin_reg_current) {
+    Error("[unknown-plugin] %s must be called from TSPluginInit()", api_fn);
+    return nullptr;
+  }
+  if (!plugin_reg_current->plugin_registered || 
plugin_reg_current->plugin_name == nullptr) {
+    Error("[%s] %s must be called after TSPluginRegister() with a non-null 
plugin_name",
+          plugin_reg_current->plugin_path ? plugin_reg_current->plugin_path : 
"?", api_fn);
+    return nullptr;
+  }
+  return plugin_reg_current->plugin_name;
+}
+} // anonymous namespace
+
+TSReturnCode
+TSCfgRegister(const TSCfgRegistrationInfo *info)
+{
+  const char *plugin_name = validate_plugin_init("TSCfgRegister");
+  if (!plugin_name) {
+    return TS_ERROR;
+  }
+
+  if (info == nullptr) {
+    Error("[%s] TSCfgRegister: info is null", plugin_name);
+    return TS_ERROR;
+  }
+  if (info->key.empty() || info->config_path.empty() || info->handler == 
nullptr) {
+    Error("[%s] TSCfgRegister: missing required fields 
(key/config_path/handler)", plugin_name);
+    return TS_ERROR;
+  }
+
+  std::string key_str{info->key};
+  std::string config_path_str{info->config_path};
+  std::string filename_record_str{info->filename_record};
+
+  config::ConfigSource cfg_source =
+    (info->source == TS_CFG_SOURCE_FILE_AND_RPC) ? 
config::ConfigSource::FileAndRpc : config::ConfigSource::FileOnly;
+
+  auto cb    = info->handler;
+  auto udata = info->data;
+
+  // Lambda captures only the function pointer + opaque user data. Plugin name
+  // and key live on the Entry; the wrapper looks them up via the ctx that the
+  // framework attaches them to (set_plugin_name in ConfigRegistry).
+  config::ConfigReloadHandler wrapper = [cb, udata](ConfigContext ctx) {
+    auto *handle              = new PluginConfigContext{};
+    handle->reload_token      = ctx.get_reload_token();
+    handle->supplied_yaml     = ctx.supplied_yaml();
+    handle->reload_directives = ctx.reload_directives();
+    auto const &key           = ctx.get_description();
+    if (auto const *entry = config::ConfigRegistry::Get_Instance().find(key); 
entry != nullptr) {
+      handle->filename = entry->resolve_filename();
+    }
+    handle->ctx = std::move(ctx);
+
+    Dbg(dbg_ctl_plugin_config, "Invoking plugin config handler for '%s'", 
key.c_str());
+    cb(reinterpret_cast<TSCfgLoadCtx>(handle), udata);
+    // Past this point @p handle may be deleted (synchronous completion). 
Deferred
+    // completion is detected upstream via ctx.is_terminal(). If the plugin 
never
+    // completes, the handle leaks but core's progress timeout still unblocks.
+  };
+
+  // Duplicate-key handling lives in ConfigRegistry::do_register and emits a
+  // Warning identifying both owners; callers can probe with TSCfgIsRegistered.
+  config::ConfigRegistry::Get_Instance().register_plugin_config(key_str, 
plugin_name, config_path_str, filename_record_str,
+                                                                
std::move(wrapper), cfg_source, {}, info->is_required);
+
+  Dbg(dbg_ctl_plugin_config, "[%s] TSCfgRegister: registered '%s' (file: %s)", 
plugin_name, key_str.c_str(),
+      config_path_str.c_str());
+  return TS_SUCCESS;
+}
+
+bool
+TSCfgIsRegistered(std::string_view key)
+{
+  if (key.empty()) {
+    return false;
+  }
+  return config::ConfigRegistry::Get_Instance().contains(std::string{key});
+}
+
+TSReturnCode
+TSCfgAttachReloadTrigger(std::string_view key, std::string_view record_name)
+{
+  const char *plugin_name = validate_plugin_init("TSCfgAttachReloadTrigger");
+  if (!plugin_name) {
+    return TS_ERROR;
+  }
+  if (key.empty() || record_name.empty()) {
+    Error("[%s] TSCfgAttachReloadTrigger: key and record_name required", 
plugin_name);
+    return TS_ERROR;
+  }
+
+  std::string key_str{key};
+  std::string record_str{record_name};
+  if (config::ConfigRegistry::Get_Instance().attach(key_str, 
record_str.c_str()) != 0) {
+    Dbg(dbg_ctl_plugin_config, "[%s] TSCfgAttachReloadTrigger: key '%s' not 
found", plugin_name, key_str.c_str());

Review Comment:
   TSCfgAttachReloadTrigger logs "key '%s' not found" for any non-zero return 
from ConfigRegistry::attach(), but attach() can also fail when 
RecRegisterConfigUpdateCb wiring fails for the record. Consider logging a more 
accurate message (e.g., include both key and record_name, and avoid implying 
the key is missing unless that was the cause).
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to