Class KarateLifecycle

java.lang.Object
io.karatelabs.common.KarateLifecycle

public final class KarateLifecycle extends Object
The process-wide register of everything Karate has running — HTTP servers, websocket clients, thread pools, browser processes — and the one way to drain all of it.

Embedding applications get two options, and should pick exactly one:

  • call shutdownAll(Duration) at the point their own shutdown sequence says Karate should be gone (preferred — shutdown ordering stays under the application's control, and the returned summary is inspectable while logging still works);
  • or call installShutdownHook() once during startup and let the JVM trigger the drain — fine for CLIs and tests, less so inside a container where the shutdown hook competes with the framework's own.

Whichever is chosen, daemon threads (see ThreadUtils) remain the backstop: a component that is never stopped will not keep the JVM alive, it just skips the polite parts — close frames unsent, callbacks dropped, peers seeing abrupt resets.

Phases

The register moves KarateLifecycle.Phase.RUNNING → KarateLifecycle.Phase.SHUTTING_DOWN → KarateLifecycle.Phase.STOPPED and stays there; reset() is the only way back, for tests and for applications that deliberately restart Karate in-process. Once shutdown has begun, register(Stoppable) no longer adds to the register — the late arrival is stopped on the spot (logged at warn) rather than being quietly leaked past the drain.

Deadline

The timeout handed to shutdownAll(Duration) is a total deadline for the whole sequence and it is enforced, not merely hoped for: every stop() runs on an interruptible worker and is abandoned when its slice of the remaining time runs out. The slice is the remaining time divided by the number of components still to go, so a single component that hangs is interrupted after its own share and the ones behind it still get their turn — while components that stop promptly hand their unused time to the rest.

Shutdown runs in reverse registration order, on the theory that later arrivals tend to depend on earlier ones (a client connected to a server started before it). Every stop is individually guarded, so one component that throws or hangs cannot strand the rest; each outcome is logged and reported in the returned summary.

All methods are thread-safe. Registration is idempotent and identity-based, so a component that registers twice is listed once, and one whose equals collides with a sibling's is still tracked separately. A second shutdownAll(Duration) arriving while one is in flight does not start a second drain — it waits for the one already running and returns its summary.

  • Field Details

    • DEFAULT_TIMEOUT

      public static final Duration DEFAULT_TIMEOUT
      Used by installShutdownHook() and shutdownAll() — modest on purpose, a JVM shutdown hook that overstays gets killed by the OS anyway (and by container stop timeouts).
  • Method Details

    • register

      public static void register(Stoppable stoppable)
      Add a component to the register — call once it is actually running (bound, connected, spawned), not while it is still being constructed. Registering the same instance again is a no-op, and null is ignored.

      Once shutdown has begun this does not register anything: the component arrived too late to be drained in order, so it is stopped immediately (logged at warn), bounded by whatever is left of the in-progress deadline — or by DEFAULT_TIMEOUT when shutdown has already finished.

    • unregister

      public static void unregister(Stoppable stoppable)
      Remove a component from the register — call from the component's own stop path, so that something which stopped by itself (peer closed the socket, process exited) drops off too. Removing something not registered is a no-op, and null is ignored.
    • running

      public static List<Stoppable> running()
      Everything registered right now, in registration order. A snapshot — the returned list is immutable and does not track later registrations, so it is safe to iterate while components start and stop.
    • phase

      public static KarateLifecycle.Phase phase()
      Where the register currently is — see KarateLifecycle.Phase.
    • reset

      public static void reset()
      Return the register to KarateLifecycle.Phase.RUNNING so it accepts registrations again. Meant for tests, and for an application that deliberately restarts Karate inside a live JVM; a normal shutdown never needs it. Anything left over from the previous life is dropped, not stopped.
      Throws:
      IllegalStateException - if a shutdown is in flight
    • shutdownAll

      public static List<KarateLifecycle.StopResult> shutdownAll()
      Stop everything registered, in reverse registration order, bounded by DEFAULT_TIMEOUT.
    • shutdownAll

      public static List<KarateLifecycle.StopResult> shutdownAll(Duration timeout)
      Stop everything registered, in reverse registration order, and report what happened.

      timeout is the total deadline for the sequence. Each component is stopped on an interruptible worker, given the remaining time divided by the number of components left to stop; one that overruns is interrupted, recorded as KarateLifecycle.Outcome.TIMED_OUT and left to the daemon-thread backstop while the sequence moves on. One that throws is recorded as KarateLifecycle.Outcome.FAILED and logged at warn. The register is emptied and the phase becomes KarateLifecycle.Phase.STOPPED, so anything registering afterwards is stopped on arrival.

      Concurrency: if a shutdown is already in flight this waits for it and returns that summary rather than starting a second one. The exception is a call made from inside a stop() (or otherwise from the thread driving the shutdown) — waiting there would be waiting on itself, so it returns the results collected so far and the outer sequence carries on.

      Parameters:
      timeout - total deadline; null or non-positive means DEFAULT_TIMEOUT
      Returns:
      one KarateLifecycle.StopResult per component, in the order they were stopped
    • installShutdownHook

      public static void installShutdownHook()
      Register a JVM shutdown hook that runs shutdownAll(Duration) with DEFAULT_TIMEOUT and logs the summary. Opt-in and idempotent — repeat calls install nothing further. Applications that already own a shutdown sequence should call shutdownAll(Duration) from it instead, rather than racing their own hook against this one.
    • wrap

      public static Stoppable wrap(String name, String kind, ExecutorService executor)
      Adapt an ExecutorService to Stoppable, for a component whose only long-lived resource is its pool. The returned stop() calls shutdownNow() and waits (up to a few seconds) for the pool to terminate; it is idempotent, and the executor is exposed via Stoppable.lifecycleExecutor(). Register it like anything else: KarateLifecycle.register(KarateLifecycle.wrap("my-pool", "executor", pool)).