在Clojure中确保只有一个服务实例正在运行/启动/停止的典型方法?

时间:2013-04-21 13:06:48

标签: multithreading clojure parallel-processing singleton stm

我正在用Neo4j支持的Clojure编写一个有状态服务器,它可以提供套接字请求,比如HTTP。当然,这意味着我需要能够从此服务器中启动和停止套接字服务器。在设计方面,我希望能够在此服务器中声明“服务”并启动和停止它。

我想在Clojure中思考的是如何确保启动和停止这些服务是线程安全的。我正在编写的这个服务器将嵌入NREPL并以并行方式处理传入的请求。其中一些请求将是管理的:启动服务X,停止服务Y.这开启了两个启动请求同时进入的可能性。

  1. 启动时应同步检查“正在运行”标志和“启动”标志,如果设置了任何一个则失败。在同一事务中,应设置“starting”标志。
  2. 设置“starting”标志后,事务关闭。这使得“起始”标志对其他交易可见。
  3. 然后(开始)功能实际启动服务。
  4. 如果(开始)成功,则同步设置“running”和“starting”标志。
  5. 如果(start)失败,则设置“starting”标志并返回异常。
  6. 停止需要同样的事情,检查“运行”标志并检查并设置它自己的“停止”标志。

    我试图通过(开始)和(停止)的所有可能组合来推理。

    我错过了什么吗?

    这个库已经有了吗?如果没有,这样的图书馆应该是什么样的?我将开源并将其放在Github上。

    编辑:

    这是我到目前为止所拥有的。我可以看到一个洞。我错过了什么?

    (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异常),则(:停止db-)设置为false,这将取消阻塞并且正常。 .shutdown是来自Neo4j的void函数,所以我不必评估返回值。如果失败,(:停止db-)设置为false,那样也没关系。然后我将(:ref db-)设置为nil。怎么会失败? (:停止db-)设置为false,但(:ref db-)保持挂起状态。所以这是一个漏洞。第二次日志调用的情况相同。我错过了什么?

    如果我只使用Clojure的锁定原语而不是参考舞会,这会更好吗?

2 个答案:

答案 0 :(得分:3)

这实际上非常适合简单的锁定:

(locking x
  (do-stuff))

此处x是要同步的对象。

详细说明:启动和停止服务是一种副作用;不应从事务内部启动副作用,除非可能作为代理操作。虽然锁是设计所要求的。请注意,在Clojure中使用它们并不是什么问题,因为它们非常适合手头的问题,实际上我会说locking是这里的规范解决方案。 (参见 Programming Clojure (第1版)中介绍的Stuart Halloway' s Lancet,了解一个使用锁的Clojure库的例子,这些锁被广泛使用,主要在Leiningen 。)

更新:添加快速失败行为:

这仍然非常适合锁定,即java.util.concurrent.locks.ReentrantLock(跟随Javadoc的链接):

(import java.util.concurrent.locks.ReentrantLock)

(def lock (ReentrantLock.))

(defn start []
  (if (.tryLock lock)
    (try
      (do-stuff)
      (finally (.unlock lock)))
    (do-other-stuff)))
如果锁定获取成功,将执行

(do-stuff);否则,(do-other-stuff)将会发生。在任何一种情况下,当前线程都不会阻塞。

答案 1 :(得分:1)

这听起来像代理的一个很好的用例,它们允许您将更改序列化为一个可变状态,Clojure Agents documentation有一个很好的概述。 您可以使用错误处理程序和代理错误方法来处理异常,而不必担心锁定或竞争条件。

(def service (agent {:status :stopped}))

(defn start-service [{:keys [status] :as curr}]
  (if (= :stopped status)
    (do 
      (println "starting service")
      {:status :started})
    (do 
      (println "service already running")
      curr)))

 ;; start the service like this
 (send-off service start-service)

 ;; gets the current status of the service
 @service