## otplike – Erlang/OTP processes and behaviours for Clojure ### Alexey Aristov, __SUPREMATIC__
## EuroClojure 2017 ## Berlin, Germany
### _core.async_ recap * Foundation for async programs in Clojure and ClojureScript * Go Blocks (lightweight processes) * Channels * Useful primitives (i.e. alt, mult/tap/untap, pub/sub/unsub) * Similiar for Clojure and ClojureScript Great library, but too low level
core.async
pitfalls
Producer ```clojure (go-loop (>! chan {:some-data (prepare-data)}) (recur)) ```
Consumer ```clojure (go-loop (when-let [data (<! chan)] (process data) (recur)) ```
* high chance of lock if errors are not handled. * Who 'owns' the channel? * What if there are several consumers?
### Erlang/OTP recap * Functional language developed by Ericsson in 1986. * Lightweight processes as a building block. * Proven design patterns. * Built-in distribution. * Arguably weird syntax.
factorial_server() -> receive {From, N} -> From ! fact(N), factorial_server() end.
### otplike * Simple pure Clojure library. * Built on top of core.async and core.match. * Emulates a small subset Erlang/OTP features. * In no way replacement for Erlang/OTP.
### otplike / process - Introduction * Go block + inbox + control structures. * You can send messages to process. * Process can receive and handle messages. * Process definition looks like a function definition. * Start process with the otplike/spawn function. * Started process is identified by it's PID. * Possible to register a process under a global name.
### otplike / process - Example ```clojure (ns hello-process (:require [otplike.process :as process :refer [! proc-defn receive!]])) (proc-defn hello-world [] (receive! msg (println msg))) (let [pid (process/spawn hello-world)] (! pid "Hello, world")) ```
### otplike / process - Receive ```clojure (proc-defn hello-world [] (receive! [:print message] (do (println message) (recur)) :exit nil (after 500 (println "receive timeout") (recur)))) ``` * Pattern matching with core.match. * Crash if no matching pattern available. * Timeout handling with receive.
### otplike / process - Linking * Special connection between processes. * When one of two processes exits, another also exits. ![](img/link-exit.png) * Bi-directional and atomic. * Very important for error recovery and supervision. * Crash all related processes and restart whole group.
### otplike / process - Linking ```clojure (proc-defn process2 [] (receive! [:message message] (println message)) (recur)) (proc-defn process1 [] (let [p2 (process/spawn-link process2)] (receive! message (! p2 message)) (recur))) (let [p1 (process/spawn process1)] (! p1 :unsupported-message)) ``` * Use spawn-link instead of spawn to start a linked process * link and unlink functions are also available.
### otplike / process - Trap Exit ```clojure (proc-defn process1 [] (process/flag :trap-exit true) (loop [p2 (process/spawn-link process2)] (receive! [:EXIT p2 reason] (recur (process/spawn-link process2)) m (do (! p2 m) (recur p2))))) (let [p1 (process/spawn process1)] (! p1 :unsupported-message)) ``` * trap-exit process flag converts exit to regular messages. * without it, any single crash would destroy a whole system. * It is the way to stop crash propagation.
### otplike / _gen-server_ introduction * Interact synchroniously or asynchroniously. * Synchronized access to state. * State consitency guarantee. * Discard state on crash. * Internally it's just a process. * can be linked to other processes. * can trap exits. * Can be seen as a microservice.
### otplike / _gen-server_ behaviour ```clojure ; provide initial state (required!) (defn init [some args] [:ok {:p1 some :p2 args}]) ; handle sync requests (defn handle-call [message from state] [:reply :ok state]) ; handle async requests (defn handle-cast [message state] [:noreply state]) ``` * is a protocol implementation (IGenServer). * alternatively a namespace with functions (well-known names).
### otplike / _gen-server_ example ```clojure (require '[otplike.gen-server :as gs]) ; provide initial state (required!) (defn init [] [:ok {}]) ; handle sync requests (defn handle-call [message from state] (match message [::add a1 a2] [:reply (+ a1 a2) state])) (defn add [server a1 a2] (gs/call server [::add a1 a2])) (let [[_ server] (gs/start-ns [])] (println "2 + 3 = " (add server 2 3))) ```
### otplike / _gen-server_ ```clojure (defn init [some args] [:ok {::count 0}]) (defn handle-call [message from state] (match message [::add a1 a2] [:reply (+ a1 a2) (update-in state [::count] inc)] ::count [:reply (::count state) state])) (defn add [a1 a2] (gs/call ::my-service [::add a1 a2])) (defn get-count [a1 a2] (gs/call ::my-service ::count)) (gs/start-ns ::my-service [] {}) (println "2 + 3 = " (add 2 3) " / execution count: " (get-count)) ```
### otplike / Supervisors * responsible for starting gen-servers. * re-starts them in case they will crash. * supervisor is a special gen-server. * has start specifications (id, start function and arguments). * initially starts its children (gen-servers or other supervisors). * re-starts them if they crash. * re-start strategies: one-for-one, one-for-all, one-for-rest.
### otplike / Supervision tree ![](img/sup-tree.png) * application is designed as tree of _gen-servers_ and _supervisors_
### otplike / Supervision tree - Example ```clojure (defn- sup-fn [config] [:ok [{:strategy :one-for-all} [{:id :config :start [config-server/start-link [config]]} {:id :http-server :start [http/start-link [(:http-port config) routes/app]]} {:id :db :start [db/start-link []]} {:id :session-sup :start [ws/start-link-sup []]} {:id :repo-base :start [repo-base/start-link [(:repo-base config)]]} {:id :opfile-sup :start [opfiles-sup [config]]} {:id :rsync :start [rsync/start-link [config]]}]]]) (process/proc-defn p-boot [config] (process/flag :trap-exit true) (match (supervisor/start-link sup-fn [config]) [:ok sup-pid] (process/receive! [:EXIT sup-pid reason] (log/debugf "root supervisor terminated with reason=%s" reason)) [:error reason] (log/errorf "cannot start root supervisor: %s" reason))) ```
### Conclusion * we see _otplike_ as a very useful extension to our toolbox. * currently _otplike_ is used in two major projects: * automotive / engineering; * banking. * next steps: * get it running in browser (ClojureScript); * application support; * some distribution capabilities; * more advanced process registry (something like _gproc_).
### Links and Information * otplike - [https://github.com/suprematic/otplike](https://github.com/suprematic/otplike) * Erlang/OTP - [http://learnyousomeerlang.com/content](http://learnyousomeerlang.com/content) * pictures used in this presentation are borrowed from it * Twitter - [@suprematic_aav](http://www.twitter.com/suprematic_aav) * Our Company - [www.suprematic.net](http://www.suprematic.net)
# Q&A
### otplike / timer functions ```clojure (proc-defn [] (interface/light-off!) (timer/send-interval 10000 :light-on) (loop [] (receive! :light-on (do (interface/light-on!) (timer/send-after 1000 :light-off)) :light-off (interface/light-off!)) (recur))) ``` * send message after delay * send messages with regular interval