{"record":{"id":"31aad64ce7e5da10","repo":"grpc/grpc-java","slug":"gcpobservability-already-closed","errorCode":null,"errorMessage":"GcpObservability already closed!","messagePattern":"GcpObservability already closed!","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"warning","filePath":"gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java","lineNumber":141,"sourceCode":"    }\n    return instance;\n  }\n\n  /** Un-initialize/shutdown grpc-observability. */\n  @Override\n  public void close() {\n    closeWithSleepTime(2 * METRICS_EXPORT_INTERVAL, TimeUnit.SECONDS);\n  }\n\n  /**\n   * Method to close along with sleep time explicitly.\n   *\n   * @param sleepTime sleepTime\n   */\n  void closeWithSleepTime(long sleepTime, TimeUnit timeUnit) {\n    synchronized (GcpObservability.class) {\n      if (instance == null) {\n        throw new IllegalStateException(\"GcpObservability already closed!\");\n      }\n      sink.close();\n      if (config.isEnableCloudMonitoring() || config.isEnableCloudTracing()) {\n        try {\n          // Sleeping before shutdown to ensure all metrics and traces are flushed\n          timeUnit.sleep(sleepTime);\n        } catch (InterruptedException e) {\n          Thread.currentThread().interrupt();\n          logger.log(Level.SEVERE, \"Caught exception during sleep\", e);\n        }\n      }\n      instance = null;\n    }\n  }\n\n  // TODO(dnvindhya): Remove <channel/server>InterceptorFactory and replace with respective\n  // interceptors\n  private void setProducer(","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java#L123-L159","documentation":"GcpObservability.closeWithSleepTime closes the observability sink but requires a live singleton instance; if GcpObservability was already closed (instance set to null) it throws IllegalStateException. This guards against double-close of the global observability registrar.","triggerScenarios":"Calling GcpObservability.close() (or closeWithSleepTime) a second time after the singleton was already closed.","commonSituations":"Shutdown hooks plus application teardown both calling close(); wrapping observability close in try/finally that runs twice; repeated Spring bean destruction.","solutions":["Track whether close was already called and only call it once (idempotent close wrapper).","Null-check via your own holder before calling close, or catch IllegalStateException and ignore.","Ensure only one component owns the GcpObservability lifecycle."],"exampleFix":"// before\nobservability.close();\ncleanupHook.run(); // calls observability.close() again -> IllegalStateException\n// after\nprivate final AtomicBoolean closed = new AtomicBoolean(false);\nif (closed.compareAndSet(false, true)) { observability.close(); }","handlingStrategy":"try-catch","validationCode":"private final AtomicBoolean closed = new AtomicBoolean(false); boolean shouldClose = closed.compareAndSet(false, true);","typeGuard":null,"tryCatchPattern":"try { observability.close(); } catch (IllegalStateException e) { /* already closed; ignore */ }","preventionTips":["Make close idempotent with a flag","Ensure only one lifecycle owner calls close","Avoid calling close from both shutdown hook and app teardown"],"tags":["grpc","gcp","observability","double-close"],"backgroundTag":"invalid-state-transition","analyzedSha":"64daddc1f3d1975670f769f3e97bde8b2ba32d25","analyzedAt":"2026-09-08T06:14:57.704Z","contentChangedAt":"2026-09-08T06:14:57.704Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}