Clojure / Jetty: Force url to be only once

I am working on a Clojure / Jetty web service. I have a special URL that I want to serve only one request at a time. If the URL was requested, and before returning it, the URL is requested again, I want to return immediately. So in more core.clj, where I defined my routes, I have something like this:

(def work-in-progress (ref false)) 

Then after a while

 (compojure.core/GET "/myapp/internal/do-work" [] (if @work-in-progress "Work in Progress please try again later" (do (dosync (ref-set work-in-progress true)) (do-the-work) (dosync (ref-set rebuild-in-progress false)) "Job completed Successfully"))) 

I tried this on the local Jetty server, but I seem to have hit the URL twice and doubled up. What is a good template / way to implement this in Clojure in a streaming web server environment?

+4
source share
3 answers

Provide the following condition for the race condition for the solution proposed in the question.

  • Thread A begins to execute the handler. @work-in-progress false , so it introduces a do statement. However, before he managed to set the value of work-in-progress to true ...
  • Thread B begins to execute the handler. @work-in-progress is false, so it introduces a do statement.

Now two threads do (do-the-work) at the same time. This is not what we want.

To prevent this problem, check and set the ref value in transaction dosync .

 (compojure.core/GET "/myapp/internal/do-work" [] (if (dosync (when-not @work-in-progress (ref-set work-in-progress true))) (try (do-the-work) "Job completed Successfully" (finally (dosync (ref-set work-in-progress false)))) "Work in Progress please try again later")) 

Another abstraction that may be useful in this scenario is atom and compare-and-set! .

 (def work-in-progress (atom false)) (compojure.core/GET "/myapp/internal/do-work" [] (if (compare-and-set! work-in-progress false true) (try (do-the-work) "Job completed Successfully" (finally (reset! work-in-progress false))) "Work in Progress please try again later")) 
+4
source

This is actually a natural precedent for blocking; in particular, java.util.concurrent.locks.ReentrantLock .

The same model came up in my answer to an earlier SO question, canonical way to make sure that only one instance of the service is started / started / stopped in Clojure? ; Here I repeat the corresponding piece of code:

 (import java.util.concurrent.locks.ReentrantLock) (def lock (ReentrantLock.)) (defn start [] (if (.tryLock lock) (try (do-stuff) (finally (.unlock lock))) (do-other-stuff))) 

The tryLock method tries to obtain a lock, returning true if it succeeds, and false otherwise, without blocking in any case.

+2
source

Consider the priority of accessing a resource - in addition to getting equivalent functionality compared to locks / flags, queues allow you to watch contention for resources, among other benefits.

+1
source

Source: https://habr.com/ru/post/1484705/


All Articles