Class LazyScheduler

java.lang.Object
ai.attackframework.tools.burp.utils.concurrent.LazyScheduler

public final class LazyScheduler extends Object
Lazily-started single-thread daemon ScheduledExecutorService owned by one class.

Centralizes the "volatile field + synchronized ensure-started + Workers shutdown" pattern that every reporter and the orphan-flush path previously duplicated. Each owner declares a static final LazyScheduler whose executor is created on first getOrStart() call and torn down by stop() during UI stop or extension unload; a subsequent getOrStart() transparently recreates a fresh executor, preserving the existing lazy-restart contract.

Instances are thread-safe: start and stop serialize on the instance monitor, the backing reference is volatile so peek() and isStarted() observe the most recent write without locking, and stop() delegates to Workers so shutdown semantics match every other extension-owned worker.

This helper does not clear reporter-specific state such as pushed-key sets or once-logged flags; owners remain responsible for resetting their own session state in their own stop() method after calling stop().

  • Constructor Details

    • LazyScheduler

      public LazyScheduler(String threadName)
      Creates a new holder with the default shutdown budget.

      Preferred for owners that want the standard lazy-start/deterministic-stop behavior without specifying a custom teardown window.

      Parameters:
      threadName - daemon thread name assigned to the scheduler's single worker thread
    • LazyScheduler

      public LazyScheduler(String threadName, long shutdownTimeoutMs)
      Creates a new holder configured with the daemon thread name and shutdown budget to use.
      Parameters:
      threadName - daemon thread name assigned to the scheduler's single worker thread
      shutdownTimeoutMs - maximum milliseconds stop() waits for termination before returning; forwarded to Workers.awaitExecutorShutdown(java.util.concurrent.ExecutorService, long)
  • Method Details

    • getOrStart

      public ScheduledExecutorService getOrStart()
      Returns the backing scheduler, creating the daemon executor on first call.

      Synchronized so the executor and its daemon thread are instantiated at most once per lazy-start cycle even under concurrent callers. Callers may schedule or submit work against the returned reference directly.

      Returns:
      the active scheduler owned by this holder
    • startRecurring

      public boolean startRecurring(Runnable task, long initialDelay, long period, TimeUnit unit)
      Schedules a recurring task on the backing scheduler if one has not been registered yet.

      Folds the "volatile check + synchronized double-check + getOrStart().scheduleAtFixedRate(...)" idiom that every recurring reporter previously inlined. Safe to call from any thread and safe to call more than once: on the second and subsequent calls the method returns false without touching the executor, so callers can treat it as idempotent.

      The start check uses isStarted() rather than tracking the individual schedule, so callers that need to register multiple recurring tasks against the same holder must obtain the executor directly via getOrStart() instead.

      Parameters:
      task - recurring work; scheduled via ScheduledExecutorService.scheduleAtFixedRate(java.lang.Runnable, long, long, java.util.concurrent.TimeUnit)
      initialDelay - delay before the first run, expressed in unit
      period - fixed-rate period between successive runs, expressed in unit
      unit - time unit for initialDelay and period
      Returns:
      true when this call started the executor and registered the task; false when the holder was already started and the call was a no-op
    • isStarted

      public boolean isStarted()
      Returns true when an executor is currently held.

      Intended for idempotent "already started?" guards that want to skip re-registering a recurring task. The check is lock-free because executor is volatile.

      Returns:
      true when getOrStart() has been called since the last stop(), otherwise false
    • peek

      public ScheduledExecutorService peek()
      Returns the current scheduler or null when not started.

      Primarily intended for tests and for owners like ExporterIndexStatsReporter that need to compare executor identity across recreation.

      Returns:
      the active scheduler, or null when the holder has not been started (or has been stopped)
    • stop

      public void stop()
      Null-swaps the backing reference under the holder's lock and hands the old executor to Workers.awaitExecutorShutdown(java.util.concurrent.ExecutorService, long).

      Safe to call from any thread and safe to call more than once. A subsequent getOrStart() creates a fresh executor. If shutdown does not complete within the configured timeout, the current thread's interrupt flag is restored; otherwise returns without throwing.