{"record":{"id":"1dc5ec753526f6e7","repo":"apache/hadoop","slug":"shutdown-in-progress-cannot-remove-a-shutdownhook","errorCode":null,"errorMessage":"Shutdown in progress, cannot remove a shutdownHook","messagePattern":"Shutdown in progress, cannot remove a shutdownHook","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"warning","filePath":"hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/ShutdownHookManager.java","lineNumber":344,"sourceCode":"    if (shutdownInProgress.get()) {\n      throw new IllegalStateException(\"Shutdown in progress, cannot add a \" +\n          \"shutdownHook\");\n    }\n    hooks.add(new HookEntry(shutdownHook, priority, timeout, unit));\n  }\n\n  /**\n   * Removes a shutdownHook.\n   *\n   * @param shutdownHook shutdownHook to remove.\n   * @return TRUE if the shutdownHook was registered and removed,\n   * FALSE otherwise.\n   */\n  @InterfaceAudience.Public\n  @InterfaceStability.Stable\n  public boolean removeShutdownHook(Runnable shutdownHook) {\n    if (shutdownInProgress.get()) {\n      throw new IllegalStateException(\"Shutdown in progress, cannot remove a \" +\n          \"shutdownHook\");\n    }\n    // hooks are only == by runnable\n    return hooks.remove(new HookEntry(shutdownHook, 0, TIMEOUT_MINIMUM,\n      TIME_UNIT_DEFAULT));\n  }\n\n  /**\n   * Indicates if a shutdownHook is registered or not.\n   *\n   * @param shutdownHook shutdownHook to check if registered.\n   * @return TRUE/FALSE depending if the shutdownHook is is registered.\n   */\n  @InterfaceAudience.Public\n  @InterfaceStability.Stable\n  public boolean hasShutdownHook(Runnable shutdownHook) {\n    return hooks.contains(new HookEntry(shutdownHook, 0, TIMEOUT_MINIMUM,\n      TIME_UNIT_DEFAULT));","sourceCodeStart":326,"sourceCodeEnd":362,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/ShutdownHookManager.java#L326-L362","documentation":"ShutdownHookManager.removeShutdownHook throws IllegalStateException when called while the JVM shutdown sequence is running: the hook registry is being iterated and executed, so mutation is disallowed. Removal during shutdown is a lifecycle race in cleanup code, not a registry corruption.","triggerScenarios":"A service close()/destroy() that deregisters its hook being invoked from another shutdown hook or from a thread that notices SIGTERM after shutdown began — double teardown paths (container stop plus JVM hook).","commonSituations":"Idempotent close() called once by webapp undeploy and once by the shutdown hook; test frameworks deregistering hooks in @AfterClass while the JVM is exiting; watchdogs that trigger cleanup concurrently with exit.","solutions":["Wrap removal in try/catch(IllegalStateException) and treat it as a no-op — the JVM is exiting anyway","Guard with an AtomicBoolean closed flag so deregistration runs at most once, before shutdown starts","Never call removeShutdownHook from inside another shutdown hook","Pre-check with ShutdownHookManager.get().isShutdownInProgress() and skip removal when true"],"exampleFix":"// before\npublic void close() {\n  ShutdownHookManager.get().removeShutdownHook(hook); // throws if JVM exiting\n}\n\n// after\nprivate final AtomicBoolean closed = new AtomicBoolean(false);\npublic void close() {\n  if (!closed.compareAndSet(false, true)) return;\n  try {\n    ShutdownHookManager.get().removeShutdownHook(hook);\n  } catch (IllegalStateException e) {\n    // JVM shutdown in progress: nothing to deregister\n  }\n}","handlingStrategy":"try-catch","validationCode":"ShutdownHookManager mgr = ShutdownHookManager.get();\nif (!mgr.isShutdownInProgress()) {\n  mgr.removeShutdownHook(hook);\n}","typeGuard":null,"tryCatchPattern":"try {\n  ShutdownHookManager.get().removeShutdownHook(hook);\n} catch (IllegalStateException e) {\n  // JVM exiting: registry is being drained, removal is moot\n}","preventionTips":["Make close() idempotent with an AtomicBoolean so deregistration runs at most once, before shutdown","Never remove hooks from inside another shutdown hook","Pre-check isShutdownInProgress() before removal in teardown paths that can run during exit","Treat this exception as expected noise during JVM shutdown; log at debug"],"tags":["hadoop","shutdown-hook","lifecycle","race-condition","illegalstate"],"backgroundTag":"illegal-state-during-shutdown","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}