Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/msolli/proletarian/llms.txt

Use this file to discover all available pages before exploring further.

Proletarian is designed to shut down gracefully. When worker/stop! is called, it signals all worker threads to stop and waits for any in-flight jobs to finish before tearing down the thread pool. No jobs are abandoned mid-execution, and the queue is left in a consistent state.

Stopping the worker manually

Call worker/stop! when you are ready to bring down the system:
(require '[proletarian.worker :as worker])

;; Start
(def q (worker/create-queue-worker ds handler))
(worker/start! q)

;; ... later, on shutdown ...
(worker/stop! q)
stop! blocks until the thread pool has terminated (or the termination timeout is reached — see below).

JVM shutdown hook

Setting :proletarian/install-jvm-shutdown-hook? to true tells Proletarian to register a JVM shutdown hook via java.lang.Runtime.addShutdownHook. The hook calls stop! automatically when the JVM receives SIGTERM or when the process is interrupted with Ctrl-C:
(worker/create-queue-worker ds handler
  {:proletarian/install-jvm-shutdown-hook? true})
The default value is false. This is intentional: when using a component library (Component, Integrant, Mount), the library itself manages lifecycle, and you should let it call stop! rather than installing a competing shutdown hook. See the Component Integration guide for details.

Await-termination timeout

The :proletarian/await-termination-timeout-ms option controls how long stop! waits for running jobs to finish before considering the shutdown complete. The default is 10000 (10 seconds):
(worker/create-queue-worker ds handler
  {:proletarian/install-jvm-shutdown-hook?    true
   :proletarian/await-termination-timeout-ms  30000})  ;; wait up to 30 s
Increase this value if your jobs can legitimately take longer than 10 seconds to finish, and you want to avoid premature timeouts during shutdown.

Running code after shutdown

Use :proletarian/on-shutdown to register a zero-argument callback that Proletarian invokes once the thread pool has fully terminated. The function’s return value is discarded:
(worker/create-queue-worker ds handler
  {:proletarian/on-shutdown (fn []
                              (println "Worker stopped — releasing resources")
                              (close-connection-pool! ds))})
on-shutdown is called after shutdown-executor completes, so you can safely assume that no job code is still running when the callback fires. The default is a no-op.

Handling interrupts in job handlers

When stop! is called, Proletarian sends a JVM thread interrupt to every worker thread. What happens next depends on what the thread is doing at that moment:
  • Thread is polling — the thread sees the interrupt and stops immediately, without picking up any new job.
  • Thread is running a job — the interrupt is delivered to the job’s code. Whether the job can respond to it depends on the nature of the work.

Blocking operations (automatic interrupt handling)

Operations that block the thread — such as Thread/sleep, blocking I/O, JDBC calls, and core.async/<!! — respond to JVM interrupts by throwing java.lang.InterruptedException. Proletarian catches this exception and:
  1. Logs the ::worker/job-interrupted event.
  2. Leaves the job in the queue so it will be picked up and re-run when the worker restarts.
For most real-world jobs (HTTP calls, database writes, sending email), this means shutdown is fast and clean with no special handling required in your handler code. If you want to perform cleanup when interrupted, you can catch InterruptedException yourself:
(defmethod handle-job! ::long-running-job [_ payload]
  (try
    (Thread/sleep 30000)
    (catch InterruptedException _
      ;; Interrupted during sleep — the job will be re-run on next startup.
      ;; Add any cleanup logic here if needed.
      nil)))
Interrupted jobs are not marked as failed. They remain in the queue with their original process-at time and will be picked up and re-executed the next time a worker starts. This is why your job handlers should be idempotent — the same job may run more than once.

CPU-bound jobs (no automatic interrupt handling)

Tight loops and other CPU-intensive work do not make blocking calls, so they never receive an InterruptedException. The JVM interrupt flag is set on the thread, but the code keeps running until it checks the flag manually using Thread/isInterrupted():
(defmethod handle-job! ::cpu-intensive-job [_ {:keys [items]}]
  (loop [remaining items]
    (when (seq remaining)
      ;; Check the interrupt flag between units of work
      (if (.isInterrupted (Thread/currentThread))
        ;; Interrupted — stop early; job will be re-run
        nil
        (do
          (process-item! (first remaining))
          (recur (rest remaining)))))))
A CPU-bound job that does not check Thread/isInterrupted() cannot be interrupted. When shutdown is requested, Proletarian will wait for the job to finish on its own before the thread pool can terminate. If the job takes longer than :proletarian/await-termination-timeout-ms, the shutdown will time out. Design long-running CPU-bound jobs to check the interrupt flag at regular intervals.

Summary of shutdown behaviour

Job state at shutdownWhat happens
Polling for jobsStops immediately; no job is picked up
Running a blocking jobInterruptedException thrown; job left in queue for reprocessing
Running a CPU-bound jobJob finishes naturally; no new jobs are picked up afterward
The simplest strategy for fast, reliable shutdown: keep individual jobs short (a few seconds at most) and make them idempotent. Short jobs finish before the termination timeout, and idempotent jobs are safe to re-run in the rare case that a job is interrupted and re-queued.

Build docs developers (and LLMs) love