diff options
| author | eltrac <hi@eltr.ac> | 2026-10-06 17:40:03 +0800 |
|---|---|---|
| committer | eltrac <hi@eltr.ac> | 2026-10-06 17:41:15 +0800 |
| commit | 4030d4d921fed3e9abea28268fe499fbaedb5dfc (patch) | |
| tree | a35e393824af254e3926f605759749615a42a267 /src | |
| parent | 99ac7a49687ed353ee3919bd1d635429033a577f (diff) | |
core, processor: refactor startup; graceful shutdown
- implement a functional rewrite of core.clj, no more `def`, no more
states. callable from an REPL
- handle SIGINT/SIGTERM and perform graceful shutdown
- print clear logs when starting up and shutting down
Diffstat (limited to 'src')
| -rw-r--r-- | src/weepinbell/core.clj | 85 | ||||
| -rw-r--r-- | src/weepinbell/processor/starter.clj | 33 |
2 files changed, 78 insertions, 40 deletions
diff --git a/src/weepinbell/core.clj b/src/weepinbell/core.clj index 4c867ef..034bb96 100644 --- a/src/weepinbell/core.clj +++ b/src/weepinbell/core.clj @@ -7,38 +7,69 @@ [weepinbell.config :refer [load-config]] [weepinbell.webhook :as webhook] [weepinbell.storage.edn-storage :as edn] - [clojure.core.async :refer [chan]] + [clojure.core.async :refer [chan >!! <!!]] [taoensso.timbre :as timbre] [weepinbell.storage.spec]) + (:import (sun.misc Signal SignalHandler)) (:gen-class)) ;; Essential for "lein uberjar" -(def ^{:doc "Webmention channel. - Receiver puts webmention requests in. Processor takes them out."} - wmc (chan)) +; Handle startup; Callable from REPL +;; processor starter is in ns weepinbell.proceesor.starter +;; webserver starter is defined here, temporarily +(defn start-webserver + "Starts web server. Returns a function that stops the server." + [server-meta] + (let [{:keys [wmc store config]} server-meta + port (Integer/parseInt (or (:port config) (System/getenv "PORT") "3000"))] + (timbre/info "starting web server, listening on" port) + (run-server (app wmc store config) {:port port}))) -(def ^{:doc "Weepinbell configuration, - loaded from config.edn and merged with defaults."} - config (load-config)) - -(def ^{:doc "Storage middle layer, a map of data-manipulating functions. - Supports various storage as long as the map conforms storage.spec."} - store (s/conform :weepinbell.storage.spec/vtable - (edn/use-store (:data-path config)))) - -(def ^{:doc "Hooks, a map of callback functions that processor calls - on certain events. Can be used for webhooks and more."} - hooks (webhook/make-hooks config)) +;; start whole system +(defn start-system + "Start webmention processor and webserver in the background. + Returns a function that performs a graceful shutdown." + [] + (let [;; webmention channel + ;; Receiver puts webmention requests in. + ;; Processor takes them out. + wmc (chan) + ;; Weepinbell configuration, + ;; loaded from config.edn and merged with defaults. + config (load-config) + ;; Storage middle layer, a map of data-manipulating functions. + ;; Supports various storage as long as the map conforms storage.spec. + store (s/conform :weepinbell.storage.spec/vtable + (edn/use-store (:data-path config))) + ;; Hooks, a map of callback functions that processor calls + ;; on certain events. Can be used for webhooks and more. + hooks (webhook/make-hooks config) + ;; start processor and webservers and get stoppers + stop-processor (start-processor + {:wmc wmc + :callbacks hooks + :storage store + :config config}) + stop-webserver (start-webserver + {:wmc wmc + :store store + :config config})] + (fn [] (stop-processor) (stop-webserver)))) +; Entry point; Terminal user interface +;; start up system and wait for signals to perform graceful shutdown (defn -main [& _] - ;; Start webmention processor - (start-processor - {:wmc wmc - :callbacks hooks - :storage store - :config config}) - ;; Start receiver server - ;; Check for a PORT environment variable, default to 3000 - (let [port (Integer/parseInt (or (:port config) (System/getenv "PORT") "3000"))] - (run-server (app wmc store config) {:port port :join? true}) - (timbre/info "weepinbell started, listening on" port))) + (timbre/info "starting weepinbell...") + (let [done-chan (chan 1) + stop-system (start-system)] + ;; Register signal handlers for SIGINT and SIGTERM + (doseq [signal ["INT" "TERM"]] + (Signal/handle (Signal. signal) + (reify SignalHandler + (handle [_ sig] + (stop-system) + (timbre/info "signal" (.getName sig) "received." + "gracefully shut down weepinbell.") + (>!! done-chan true))))) + ;; block til signal received + (<!! done-chan))) diff --git a/src/weepinbell/processor/starter.clj b/src/weepinbell/processor/starter.clj index 384b4ac..ebf2f17 100644 --- a/src/weepinbell/processor/starter.clj +++ b/src/weepinbell/processor/starter.clj @@ -1,6 +1,6 @@ (ns weepinbell.processor.starter
(:require [clojure.spec.alpha :as s]
- [clojure.core.async :refer [go-loop <!]]
+ [clojure.core.async :refer [go-loop <! put!]]
[taoensso.timbre :as timbre]
[weepinbell.utils.digest :refer [digest]]
[weepinbell.storage.spec]
@@ -23,17 +23,24 @@ [metadata]
{:pre [(s/valid? ::processor-meta metadata)]}
(let [{:keys [wmc callbacks storage config]} metadata]
+ ;; start processing loop
(go-loop [wm (<! wmc)]
- (let [{:keys [s t]} wm
+ (let [{:keys [s t stop?]} wm
id (digest (str s t))]
- (timbre/info "webmention found in queue, processing.")
- ((:on-receive callbacks) {:webmention {:id id :source s :target t}})
- (-> {:id id
- :wm wm
- :ok? true}
- (check-storage storage)
- verify
- (clean storage callbacks)
- (wrap config)
- (write storage callbacks))
- (recur (<! wmc))))))
+ (if stop?
+ (timbre/warn "processor stopped on stop signal.")
+ ;; process found webmention
+ (do
+ (timbre/info "webmention found in queue, processing.")
+ ((:on-receive callbacks) {:webmention {:id id :source s :target t}})
+ (-> {:id id
+ :wm wm
+ :ok? true}
+ (check-storage storage)
+ verify
+ (clean storage callbacks)
+ (wrap config)
+ (write storage callbacks))
+ (recur (<! wmc))))))
+ ;; return a function that stops the loop
+ (fn [] (put! wmc {:stop? true}))))
|
