├── deps.edn ├── .gitignore ├── test └── signal │ └── handler_test.clj ├── LICENSE ├── .circleci └── config.yml ├── project.clj ├── src └── signal │ └── handler.clj └── README.md /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {org.clojure/clojure {:mvn/version "1.10.1"}} 2 | :paths ["src"]} 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | -------------------------------------------------------------------------------- /test/signal/handler_test.clj: -------------------------------------------------------------------------------- 1 | (ns signal.handler-test 2 | (:require [clojure.test :refer :all] 3 | [signal.handler :refer :all])) 4 | 5 | (defn send-signal-to-myself 6 | [sig] 7 | (let [pid (.pid (java.lang.ProcessHandle/current)) 8 | runtime (Runtime/getRuntime) 9 | cmdline (format "kill -%s %s" sig pid)] 10 | (.exec runtime cmdline))) 11 | 12 | (deftest sighandler-test 13 | (testing "sending USR1" 14 | (let [calls (atom 0)] 15 | (with-handler :usr1 (swap! calls inc)) 16 | (send-signal-to-myself "USR1") 17 | (is (= 1 @calls)) 18 | (with-handler :usr1) 19 | (send-signal-to-myself "USR1") 20 | (is (= 1 @calls))))) 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 Pierre-Yves Ritschard 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | shared: &shared 3 | steps: 4 | - checkout 5 | - restore_cache: # restores saved cache if checksum hasn't changed since the last run 6 | key: riemann-{{ checksum "project.clj" }} 7 | - run: lein deps 8 | - save_cache: # generate and store cache in the .m2 directory using a key template 9 | paths: 10 | - ~/.m2 11 | - ~/.lein 12 | key: riemann-{{ checksum "project.clj" }} 13 | - run: lein junit 14 | - store_test_results: 15 | path: target/junit 16 | 17 | jobs: 18 | jdk11: 19 | docker: # run the steps with Docker 20 | - image: circleci/clojure:openjdk-11-lein-2.9.1 21 | <<: *shared 22 | 23 | workflows: 24 | version: 2 25 | test: 26 | jobs: 27 | - jdk11 28 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (let [cfg (clojure.edn/read-string (slurp "deps.edn")) 2 | deps (for [[k {:keys [mvn/version exclusions]}] (:deps cfg)] 3 | [k version :exclusions exclusions]) 4 | paths (:paths cfg)] 5 | 6 | (defproject spootnik/signal "0.2.6-SNAPSHOT" 7 | :description "system signal handler for clojure." 8 | :url "https://github.com/pyr/signal" 9 | :license {:name "MIT/ISC License"} 10 | :jvm-opts ["-Dclojure.compiler.direct-linking=true"] 11 | :pedantic? :abort 12 | :aliases {"kaocha" ["with-profile" "+dev" "run" "-m" "kaocha.runner"] 13 | "junit" ["with-profile" "+dev" "run" "-m" "kaocha.runner" 14 | "--plugin" "kaocha.plugin/junit-xml" "--junit-xml-file" 15 | "target/junit/results.xml"]} 16 | :deploy-repositories [["snapshots" :clojars] ["releases" :clojars]] 17 | :profiles {:dev {:dependencies [[lambdaisland/kaocha "0.0-554"] 18 | [lambdaisland/kaocha-junit-xml "0.0-70"]] 19 | :pedantic? :warn}} 20 | :dependencies ~deps 21 | :source-paths ~paths)) 22 | -------------------------------------------------------------------------------- /src/signal/handler.clj: -------------------------------------------------------------------------------- 1 | (ns signal.handler 2 | "Signal handling functions" 3 | (:import clojure.lang.Compiler 4 | clojure.lang.DynamicClassLoader)) 5 | 6 | (defn- set-compiler-class-loader 7 | "Ensures that the class loader in which a handler will 8 | execute runs in a context which can load clojure classes." 9 | [] 10 | (let [thread (Thread/currentThread)] 11 | (when-not (instance? DynamicClassLoader (.getContextClassLoader thread)) 12 | (.setContextClassLoader thread (DynamicClassLoader. 13 | (.getClassLoader Compiler)))))) 14 | 15 | (defn ^sun.misc.Signal ->signal 16 | "Convert a keyword to an appropriate Signal instance." 17 | [signal] 18 | (sun.misc.Signal. (-> signal name .toUpperCase))) 19 | 20 | (defn ^Long signal->number 21 | "Find out a signal's number" 22 | [signal] 23 | (-> signal ->signal .getNumber)) 24 | 25 | (defn ^clojure.lang.Keyword signal->kw 26 | "Translate a signal to a keyword" 27 | [^sun.misc.Signal s] 28 | (-> s .getName .toLowerCase keyword)) 29 | 30 | (defn ^sun.misc.SignalHandler ->handler 31 | "Convert class to signal handler." 32 | [handler] 33 | (proxy [sun.misc.SignalHandler] [] 34 | (handle [sig] 35 | (set-compiler-class-loader) 36 | (handler (signal->kw sig))))) 37 | 38 | (defn on-signal 39 | "Execute handler when signal is caught" 40 | [signal handler] 41 | (sun.misc.Signal/handle (->signal signal) (->handler handler))) 42 | 43 | (defmacro with-handler 44 | "Install a signal handler which will execute a function 45 | body when a UNIX signal is caught" 46 | [signal & body] 47 | `(on-signal ~signal (fn [_#] ~@body))) 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | signal: UNIX signal handlers in clojure 2 | ======================================= 3 | 4 | This is just a bit of code I end up copying through-out my projects. 5 | This projects bring one macro and one function of interest. 6 | 7 | [![CircleCI](https://circleci.com/gh/pyr/signal.svg?style=svg)](https://circleci.com/gh/pyr/signal) [![Clojars Project](https://img.shields.io/clojars/v/spootnik/signal.svg)](https://clojars.org/spootnik/signal) 8 | 9 | ## Usage 10 | 11 | Pull-in the following dependency: 12 | 13 | ```clojure 14 | [spootnik/signal "0.2.5"] 15 | ``` 16 | 17 | The main signatures: 18 | 19 | - `(on-signal signal handler)`: Execute handler (a function of one argument, the signal keyword). 20 | - `(with-handler signal & body)`: Handle signal by calling the body of forms supplied. 21 | 22 | A few additional signatures may come in handy: 23 | 24 | - `(->signal signal)`: Convert a signal keyword or string to a `sun.misc.Signal` instance. 25 | - `(signal->number signal)`: Show the number for a signal. 26 | - `(signal->kw signal)`: Convert a `sun.misc.Signal` instance to a keyword. 27 | - `(->handler f)`: Convert a function of one argument to a `sun.misc.SignalHandler` instance. 28 | 29 | ## Using with component 30 | 31 | Here's one way of hooking this up with a component system: 32 | 33 | ```clojure 34 | (with-handler :term 35 | (info "caught SIGTERM, quitting.") 36 | (alter-var-root #'system component/stop-system) 37 | (System/exit 0)) 38 | 39 | (with-handler :hup 40 | (info "caught SIGHUP, reloading.") 41 | (alter-var-root #'system (comp component/start-system component/stop-system))) 42 | ``` 43 | 44 | 45 | ## License 46 | 47 | Copyright © 2016 Pierre-Yves Ritschard 48 | 49 | --------------------------------------------------------------------------------