{"id":"ff0543ab2c8c67f8","repo":"apache/kafka","slug":"this-pluginmetrics-instance-is-closed","errorCode":null,"errorMessage":"This PluginMetrics instance is closed","messagePattern":"This PluginMetrics instance is closed","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/common/metrics/internals/PluginMetricsImpl.java","lineNumber":49,"sourceCode":"\npublic class PluginMetricsImpl implements PluginMetrics, Closeable {\n\n    private static final String GROUP = \"plugins\";\n\n    private final Metrics metrics;\n    private final Map<String, String> tags;\n    private final Set<MetricName> metricNames = ConcurrentHashMap.newKeySet();\n    private final Set<String> sensors = ConcurrentHashMap.newKeySet();\n    private volatile boolean closing = false;\n\n    public PluginMetricsImpl(Metrics metrics, Map<String, String> tags) {\n        this.metrics = metrics;\n        this.tags = tags;\n    }\n\n    @Override\n    public MetricName metricName(String name, String description, LinkedHashMap<String, String> tags) {\n        if (closing) throw new IllegalStateException(\"This PluginMetrics instance is closed\");\n        for (String tagName : tags.keySet()) {\n            if (this.tags.containsKey(tagName)) {\n                throw new IllegalArgumentException(\"Cannot use \" + tagName + \" as a tag name\");\n            }\n        }\n        Map<String, String> metricsTags = new LinkedHashMap<>(this.tags);\n        metricsTags.putAll(tags);\n        return metrics.metricName(name, GROUP, description, metricsTags);\n    }\n\n    @Override\n    public void addMetric(MetricName metricName, MetricValueProvider<?> metricValueProvider) {\n        if (closing) throw new IllegalStateException(\"This PluginMetrics instance is closed\");\n        if (metricNames.contains(metricName)) {\n            throw new IllegalArgumentException(\"Metric \" + metricName + \" already exists\");\n        }\n        metrics.addMetric(metricName, metricValueProvider);\n        metricNames.add(metricName);","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/common/metrics/internals/PluginMetricsImpl.java#L31-L67","documentation":"Thrown by PluginMetricsImpl.metricName(...) after the instance has been closed (the close() method set closing=true). PluginMetrics is a scoped, closeable metrics container handed to plugins; once closed it must not be used because its backing metrics and sensors have been deregistered. The IllegalStateException signals use-after-close rather than a recoverable runtime condition.","triggerScenarios":"Calling pluginMetrics.metricName(name, description, tags) after pluginMetrics.close() has run. Happens when plugin code caches the PluginMetrics instance and uses it from an async callback, scheduled task, or shutdown hook that runs after the plugin's close() lifecycle method.","commonSituations":"Connector/agent plugin that records metrics from a background thread not joined before close(); a metrics reference leaked to a static field or cache that outlives the plugin; shutdown ordering bug where the runtime closes PluginMetrics before the plugin's own cleanup; tests that close the metrics instance then continue assertions.","solutions":["Stop all metric-producing threads/tasks (or null out their PluginMetrics reference) before pluginMetrics.close() returns.","Treat PluginMetrics as owned by the plugin lifecycle: do not store it in static fields; obtain it fresh per task/lifecycle.","Add a closing/active guard in the plugin's record path and skip metric updates once closed.","In tests, ensure close() is the last operation on the PluginMetrics instance, or use a fresh instance per test method."],"exampleFix":"// before\n// plugin close() runs first, then a background thread:\nmetrics.metricName(\"rx\", \"desc\", tags); // throws\n\n// after\n@Override public void close() {\n    backgroundThread.interrupt();\n    backgroundThread.join();\n    pluginMetrics.close();\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    MetricName name = pluginMetrics.metricName(n, description, tags);\n} catch (IllegalStateException e) {\n    // PluginMetrics instance has been closed by the framework; stop emitting from this plugin\n    log.debug(\"PluginMetrics closed, skipping metricName creation\", e);\n}","preventionTips":["Treat PluginMetrics as framework-owned; never call it after your plugin's close() returns.","Track an 'active' flag in your plugin and short-circuit all metric calls once inactive.","Create all metric names eagerly during plugin init(), not lazily at runtime."],"tags":["metrics","plugin","lifecycle","use-after-close"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}