Ensuring thread safety while preventing deadlock from synchronous callback

I have an API as shown below, where Baz

is the working version. This one Bar

needs to be thread safe and it gets tricky when interacting with Baz callbacks.

The current call to baz must be specified in a callback (which can be called on a worker thread or synchronously). The problem should show in the comments:

final class Bar {
  final Lock lock = new ReentrantLock();
  Baz baz; // Guarded by lock.

  void run() { // Called by any thread.
    lock.lock();
    if (baz.isRunning()) {
      lock.unlock();
      return;
    }
    baz = new Baz();
    // If it unlocks here, the next line may execute on the wrong Baz.
    // If it doesn't unlock here, there will be a deadlock when done() is called synchronously.
    // lock.unlock();
    baz.run(new Baz.Callback() { // May be called synchronously or by Baz worker thread.
      @Override
      public void done() {
        lock.lock();
        baz = new Baz();
        lock.unlock();
      }
    });
  }
}

      

Is there a good way to get this work done correctly and also not cause a dead end?

Edit: more succinctly:

final class Foo {
  final Lock lock = new ReentrantLock();

  void run() {
    lock.lock();
    worker.enqueue(new Callback() {
      @Override void complete() {
        lock.lock(); // Could cause deadlock.
      }
    });
    lock.unlock();
  }
}

      

+3


source to share


1 answer


Not sure if you will fully achieve what you are trying to achieve, but perhaps this is what you are looking for?



final class Bar {
    final Lock lock = new ReentrantLock();
    Baz baz = new Baz();

    void run() {
        if (!lock.tryLock()) {
            return;
        }
        try {
            CountdownLatch callbackFlag = new CountdownLatch(1);
            baz.run(new Baz.Callback() {
                @Override
                public void done() {
                    callbackFlag.countDown();
                }
            });
            try {
                callbackFlag.await(); // better use overloaded method with max timeout waiting. you don't probably want to wait forever
                baz = new Baz(); // do you really want to reinit Baz on each execution?
            } catch (InterruptedException e) {
                // decide what you want to happen here
            }
        } finally {
            lock.unlock();
        }
    }
}

      

0


source







All Articles