0

I have a piece of code in clojure which should run on isolation. Lets say this function is

(defn isolate [string1])

Its easy to isolate the whole function on all inputs, calling it like so :

(def o (Object. ))

(locking o (isolate string1))

However this only allows one process/thread to access isolate simultaneously.

What I have now implemented is the following :

(def current-locks (ref {}))

(defn mergeReverse [x y] (merge y x))

(defn merge-with-current-locks [key val]
  (dosync (alter current-locks mergeReverse {key val})))

(defn remove-lock [key]
  (dosync (alter current-locks dissoc key)))

and finally the threads block calling this method

(defn block-until-free [key val]
  (let [_ (merge-with-current-locks key val)]
    (if (dosync (and (contains? current-locks key)
                     (not= (get current-locks key) val)))
      (do
        (Thread/sleep 10)
        (block-until-free key val)))))

As you can see in the solution I used keys and values here and although I only lock on the keys but being able to use maps instead of arrays was beneficial since I used the merge property that merges in a map only if map does not contain this value and since the current-locks is a ref I used alter and swapped the merge inputs to acquire the needed behaviour.

This hack works as far as I can tell (and I tested it). But my question is how can I do this in a correct clojure way? This solution seems complicated

Off course the remove-lock has to be called once the critical function is executed.

3
  • Can you add a motivation/use-case to this example? Since each thread has it's own copy of local variables, and since function inputs are immutable in Clojure, it seems that each thread is already fully isolated. If there is some shared global state you wish to mutate, it is not shown in your example. Commented Dec 11, 2018 at 17:09
  • For each added value under a specific tag in a table in my database, I need to call a service and update that value and each value in this tag. In the mean time no other process should try to update the tag. Thats why i want to block all other processes from accessing this critical function while my service is still doing its computation Commented Dec 11, 2018 at 17:10
  • This usually does not happen simultaneously, but when it does it needs to be blocked Commented Dec 11, 2018 at 17:16

1 Answer 1

1

You should use a database transaction for this. Here is an example of the Clojure code:

  ; Wraps all commands in a single transaction
  (jdbc/with-db-transaction
    [tx db-conn]
    (let [clj-id (grab :id (only (jdbc/query tx ["select id from langs where lang='Clojure'"])))]
      (jdbc/insert-multi! tx :releases
                          [{:desc "ancients" :langId clj-id}
                           {:desc "1.8" :langId clj-id}
                           {:desc "1.9" :langId clj-id}]))
    (let [java-id (grab :id (only (jdbc/query tx ["select id from langs where lang='Java'"])))]
      (jdbc/insert-multi! tx :releases
                          [{:desc "dusty" :langId java-id}
                           {:desc "8" :langId java-id}
                           {:desc "9" :langId java-id}
                           {:desc "10" :langId java-id}])))

Here we query a table langs for the id values of languages Clojure and Java. We then add rows to table releases with cols desc and foreign key langId. Because both insert-multi! statements are wrapped via (jdbc/with-db-transaction, the transaction will roll-back if any other thread updated the db before it completed.

The above code would need a retry loop to catch an exception if our transaction failed, then retry (perhaps with a random delay). You can find the entire sample code here.


Update

My example was for a SQL db such as Postgres. For Datomic, I believe you will want a function such as db.fn/cas. See the Datomic docs for full details. You can also ask on the Datomic mailing list or post a more specific Datomic question on StackOverflow.

For either Postgres or Datomic, the transaction will only abort if the specific row/entity you change is also changed by another thread. It does not lock the entire database.

Sign up to request clarification or add additional context in comments.

1 Comment

Great I understand your solution, but when you say "if any other thread updates the db", this makes me think if that is necessary. We use a Datomic db and a lot of services may update the db and I don't want to rollback the tx if that happened, since then it would be unnecessary. I only care to rollback the tx if some other process with the same tag came along. And what would be the difference of this solution and a using the locking mentioned above. In my understanding the locking would be better since it wont leave the option of rollback. Do I understand your approach correctly?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.