I am writing a status server in Clojure supported by Neo4j that can handle socket requests like HTTP. This means, of course, that I need to be able to start and stop socket servers from this server. Of course, I would like to be able to declare a “service” on this server and start and stop it.
What I'm trying to include in Clojure is how to make these services start and stop thread-safe. This server, which I am writing, will have NREPL built inside it and process incoming requests in parallel. Some of these requests will be administrative: start service X, stop service Y. This opens up the possibility of simultaneously launching two start requests.
- The start should synchronously check the "running" flag and the "start" flag and fail, if set. In the same transaction, the "start" flag must be set.
- After the "start" flag is set, the transaction is completed. This makes the start flag visible to other transactions.
- Then the (start) function actually starts the service.
- If (start) succeeds, the “start” and “start” flags are set synchronously.
- If (start) fails, the "start" flag is set and an exception is returned.
Stopping requires the same by checking the "running" flag and checking and setting its own "stopping" flag.
I try to reason through all possible combinations of (start) and (stop).
Did I miss something?
Is there a library for this? If not, what should the library look like? I will open the source and put it on Github.
Edit:
This is what I still have. There is a hole that I see. What am I missing?
(ns extenium.db (:require [clojure.tools.logging :as log]) (:import org.neo4j.graphdb.factory.GraphDatabaseFactory)) (def ^:private db- (ref {:ref nil :running false :starting false :stopping false})) (defn stop [] (dosync (if (or (not (:running (ensure db-))) (:stopping (ensure db-))) (throw (IllegalStateException. "Database already stopped or stopping.")) (alter db- assoc :stopping true))) (try (log/info "Stopping database") (.shutdown (:ref db-)) (dosync (alter db- assoc :ref nil)) (log/info "Stopped database") (finally (dosync (alter db- assoc :stopping false)))))
In the try block, I register, then call .shutdown, and then register again. If the first log fails (there may be I / O exceptions), then (: stopdb-) is set to false, which unlocks it and excellent .. shutdown is the void function from Neo4j, so I do not need to evaluate the return value. If it fails, (: stopdb-) is set to false, which is also very good. Then I set (: ref db-) to nil. What if it fails? (: stopdb-) is set to false, but (: ref db-) remains hanging. So there is a hole. The same case with the second log call. What am I missing?
Would it be better if I just used Clojure blocking primitives instead of dancing ref?