4

In our web application, we have the following snippet of code which is invoked with a button:

if (!isResponsibleSet(request))
{
    edit(request, [("responsibleUser"):(user)];
}

request is the object we're editing

user is the user which is about to become responsible for the request

The problem is that sometimes there more that just one user clicks the same button at the same time.

Unfortunately, in the scenario described above the following happens:

  1. User 1 clicks the button
  2. User 2 clicks the button
  3. The if statement evalutes true for User 1
  4. The if statement evalutes true for User 2
  5. User 1 edits the request and become the responsible
  6. User 2 edits the request and become the responsible

The if statement evaluates true for User 2 just because editing takes so long so responsible user isn't set in the database yet

We need to eliminate that possibility, and make just one and only one user responsible for the request, not allowing anybody else edit it (just add one more condition to the if block). Preferably throw an exception that the request is currently under editing already

How to implement such behaviour in the code?

2
  • which framework are you using?
    – injecteer
    Commented Nov 2, 2020 at 12:03
  • ... or which database. this question is totally open ended and basically asks how to do optimistic or pessimistic locking for whatever.
    – cfrick
    Commented Nov 2, 2020 at 17:15

1 Answer 1

2

One way to solve this is with a java util concurrent lock (also worth reading, the Lock interface api docs):

import java.util.concurrent.locks.ReentrantLock

def lock = new ReentrantLock()

lock.lock()
try { 
  if (!isResponsibleSet(request)) {
      edit(request, [("responsibleUser"):(user)];
  }
} finally { 
  lock.unlock()
} 

this ensures that one and only one thread at a time is within the lock / unlock block which in this case is the block of code within the try. In other words, whichever user (thread) gets the lock first gets to run the entire thing and complete. The second user (thread) is made to wait until the first user (thread) has completed the entire block.

If you want to throw an exception for the second thread which comes in and tries to lock you could do something like this:

import java.util.concurrent.locks.ReentrantLock

def lock = new ReentrantLock()

if (!lock.tryLock()) {
    throw new RuntimeException("lock is already being held by another thread")
}
try { 
  if (!isResponsibleSet(request)) {
      edit(request, [("responsibleUser"):(user)];
  } else { 
      throw new RuntimeException("request is already owned by another user")
  }
} finally { 
  lock.unlock()
} 


4
  • Hello! Many thanks for your answer. We've just tried the second option but sadly with no luck :( I have a question whether if we stick with the first one, we'll be able to evaluate false for the second user in the if condition?
    – Joe D
    Commented Nov 2, 2020 at 10:31
  • 2
    This doesn't work if you have an application that runs on multiple nodes. Commented Nov 2, 2020 at 14:30
  • So, I slightly modified the code (made lock static for all objects) and your solution worked! Thank you very much, it seems to be really good since it doesn't look the database itself but just an object in memory!
    – Joe D
    Commented Nov 2, 2020 at 18:07
  • yes, everybody needs to be looking at the same lock. That was perhaps not obvious from my example. Glad you got it to work. Commented Nov 2, 2020 at 20:32

Not the answer you're looking for? Browse other questions tagged or ask your own question.