在Clojure中确保只有一个服务实例正在运行/启动/停止的规范方法? [英] Canonical Way to Ensure Only One Instance of a Service Is Running / Starting / Stopping in Clojure?
问题描述
我在Clojure写一个状态服务器,由Neo4j支持,可以提供套接字请求,如HTTP。这意味着,当然,我需要能够从这个服务器内启动和停止套接字服务器。在设计方面,我想要能够在这个服务器中声明一个服务,并启动和停止它。
I'm writing a stateful server in Clojure backed by Neo4j that can serve socket requests, like HTTP. Which means, of course, that I need to be able to start and stop socket servers from within this server. Design-wise, I would want to be able to declare a "service" within this server and start and stop it.
我想包装我的心在Clojure是如何确保启动和停止这些服务是线程安全的。我编写的这个服务器将在其中嵌入NREPL,并以并行方式处理传入的请求。这些请求中的一些将是行政的:启动服务X,停止服务Y.这打开了两个开始请求同时进入的可能性。
What I'm trying to wrap my mind around in Clojure is how to ensure that starting and stopping these services is thread-safe. This server I'm writing will have NREPL embedded inside it and process incoming requests in a parallel way. Some of these requests will be administrative: start service X, stop service Y. Which opens up the possibility that two start requests come in at the same time.
- 开始应同步检查running标志和starting标志,如果设置了这两个标志就会失败。在同一事务中,应设置开始标志。
- 设置开始标志后,事务关闭。这使得开始标志对其他事务可见。
- 然后(start)函数实际启动服务。
- 如果(start)失败,则设置开始标志并返回异常。
- Starting should synchronously check a "running" flag and a "starting" flag and fail if either are set. In the same transaction, the "starting" flag should be set.
- After the "starting" flag is set, the transaction closes. That makes the "starting" flag visible to other transactions.
- Then the (start) function actually starts the service.
- If (start) succeeds, the "running" and "starting" flags are synchronously set.
- If (start) fails, the "starting" flag is set and the exception is returned.
停止需要相同的内容,检查正在运行标志,并检查并设置它自己的停止标志。
Stopping needs the same thing, checking a "running" flag and checking and setting it's own "stopping" flag.
我尝试通过(开始)和(停止)的所有可能组合。
I'm trying to reason through all possible combinations of (start) and (stop).
我错过了什么吗?
有没有这个库?如果不是,这样的图书馆应该是什么样子?
Is there a library for this already? If not, what should a library like this look like? I'll open source it and put it on Github.
编辑:
这是我所拥有的远。有一个洞我可以看到。我缺少什么?
This is what I have so far. There's a hole I can see though. 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)))))
在try块中,我记录,然后调用.shutdown,如果第一个日志失败(I / O异常可能发生),则(:stops db-)设置为false,这将解除阻塞它是正常的。 .shutdown是Neo4j的一个void函数,所以我不必评估一个返回值。如果失败,(:stops db-)设置为false,所以也很好。然后我设置(:ref db-)为nil。如果失败怎么办? (:stops db-)设置为false,但是(:ref db-)被挂起。这是一个洞。与第二个日志调用相同。
In the try block, I log, then call .shutdown, then log again. If the first log fails (I/O exceptions can happen), then (:stopping db-) is set to false, which unblocks it and is fine. .shutdown is a void function from Neo4j, so I don't have to evaluate a return value. If it fails, (:stopping db-) is set to false, so that's fine too. Then I set the (:ref db-) to nil. What if that fails? (:stopping db-) is set to false, but the (:ref db-) is left hanging. So that's a hole. Same case with the second log call. What am I missing?
如果我只是使用Clojure的锁定原语而不是跳舞,这会更好吗?
Would this be better if I just used Clojure's locking primitives instead of a ref dance?
推荐答案
这实际上是一个自然适合一个简单的锁:
This is actually a natural fit for a simple lock:
(locking x
(do-stuff))
这里 x
是要同步的对象。
详细说明:启动和停止服务是一个副作用;副作用不应该从事务内部启动,除非可能作为代理操作。这里虽然锁是设计要求的。注意,在Clojure中使用它们没有什么问题,当它们很适合手头的问题时,事实上我会说 locking
是这里的规范解决方案。 (请参阅 Programming Clojure (第1版)中介绍的Stuart Halloway的 Lancet ,对于使用已经被广泛使用的锁的Clojure库的示例,大多在Leiningen中。)
To elaborate: starting and stopping a service is a side effect; side effects should not be initiated from inside a transaction, except possibly as Agent actions. Here though locks are exactly what the design calls for. Note that there's nothing wrong in using them in Clojure when they are a good fit for the problem at hand, in fact I would say locking
is the canonical solution here. (See Stuart Halloway's Lancet, introduced in Programming Clojure (1st ed.), for an example of a Clojure library using locks which has seen some widespread use, mostly in Leiningen.)
更新:添加故障快速行为:
这仍然是一个很好的锁,即 java.util.concurrent.locks.ReentrantLock
(请点击Javadoc的链接):
This is still a good fit for a lock, namely a java.util.concurrent.locks.ReentrantLock
(follow link for Javadoc):
(import java.util.concurrent.locks.ReentrantLock)
(def lock (ReentrantLock.))
(defn start []
(if (.tryLock lock)
(try
(do-stuff)
(finally (.unlock lock)))
(do-other-stuff)))
<$如果锁获取成功,将执行c $ c>(do-stuff);否则,会发生(do-other-stuff)
。当前线程不会阻塞。
(do-stuff)
will be executed if lock acquisition succeeds; otherwise, (do-other-stuff)
will happen. Current thread will not block in either case.
这篇关于在Clojure中确保只有一个服务实例正在运行/启动/停止的规范方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!