在Clojure中确保只有一个服务实例正在运行/启动/停止的规范方法? [英] Canonical Way to Ensure Only One Instance of a Service Is Running / Starting / Stopping in Clojure?

查看:153
本文介绍了在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.


  1. 开始应同步检查running标志和starting标志,如果设置了这两个标志就会失败。在同一事务中,应设置开始标志。

  2. 设置开始标志后,事务关闭。这使得开始标志对其他事务可见。

  3. 然后(start)函数实际启动服务。


  4. 如果(start)失败,则设置开始标志并返回异常。

  1. 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.
  2. After the "starting" flag is set, the transaction closes. That makes the "starting" flag visible to other transactions.
  3. Then the (start) function actually starts the service.
  4. If (start) succeeds, the "running" and "starting" flags are synchronously set.
  5. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆