Is there a elegant way to make sure lock.release() called in nested callbacks?

145 views
Skip to first unread message

zaiyua lon

unread,
Feb 9, 2022, 2:09:56 AM2/9/22
to vert.x
I'm developping a cache wrapper for my service API.Following the idea of Singleton, I used SharedData.LocalLock to prevent repeated cache reload. Finally the code came out as a bunch of nested callbacks as below:
--------------------------------------------------------------------------------
// The cache wrapper
public Future<List<Client>> getClients() {
        Promise<List<Client>> promise = Promise.promise();
        cacheManager.getClients().onComplete(ar1 -> {
            if (ar1.failed()) {
                sharedData.getLocalLock(LOCK_NAME).onFailure(e -> {
                    promise.fail(e);
                }).onSuccess(lock -> { // make sure the lock is propertly released!!!!!
                    cacheManager.getClients().onComplete(ar2 -> {
                        if (ar2.failed()) {
                            clientService.getClients().onComplete(ar3 -> {
                                if (ar3.failed()) {
                                    promise.fail(ar3.cause());
                                    lock.release();
                                } else {
                                    cacheManager.updateClients(ar3.result()).onComplete(ar4 -> {
                                        promise.complete(ar3.result());
                                        lock.release();
                                    });
                                }
                            });
                        } else {
                            promise.complete(ar2.result());
                            lock.release();
                        }
                    });
                });
            } else {
                promise.complete(ar1.result());
            }
        });
        return promise.future();
    }
--------------------------------------------------------------------------------
I'm quite worried about the async lock used in the example above. If some APIs failed expectedly, it may result in a dead lock, right? the lock example from vertx doc was quite simple, I haven't found any guide on exception handling yet. is there any advice?

BTW, all my async APIs were implemented like below. Since not all exceptions get captured, the promise may remain uncompleted for ever. I think it's a big portential risk. what can I do? I hate to put try-catch everywhere.
---------------------------------------------------------------------------------
    Future<T> myAsyncAPI() {
        Promise<T> promise = Promise.promise();
        // here's the code to perform async tasks and finally completes the promise.
        // but what if exceptions occurs? Divide-By-Zero-Exception, Null-Pointer-Exception..
        return promise.future();
    }
---------------------------------------------------------------------------------


Thomas SEGISMONT

unread,
Feb 10, 2022, 3:44:39 AM2/10/22
to ve...@googlegroups.com
To reduce nesting you may add methods to your class invoked when an async method completes.
 

I'm quite worried about the async lock used in the example above. If some APIs failed expectedly, it may result in a dead lock, right?

It may result in a lock being held for a long time. When others try to acquire the lock, they can specify a timeout and avoid waiting forever.
Besides, when the lock is acquired, you can set a timer that will release the lock eventually.
 
the lock example from vertx doc was quite simple, I haven't found any guide on exception handling yet. is there any advice?

BTW, all my async APIs were implemented like below. Since not all exceptions get captured, the promise may remain uncompleted for ever. I think it's a big portential risk. what can I do? I hate to put try-catch everywhere.
---------------------------------------------------------------------------------
    Future<T> myAsyncAPI() {
        Promise<T> promise = Promise.promise();
        // here's the code to perform async tasks and finally completes the promise.
        // but what if exceptions occurs? Divide-By-Zero-Exception, Null-Pointer-Exception..
        return promise.future();
    }
---------------------------------------------------------------------------------



It is a risk but in practice these exceptions are most often caught when developing.
You should of course put safe guards where you do computations to avoid failures like divide by zero.
 

As an alternative to creating an async loading cache with shared data  + locks, you may use caffeine with an executor that runs loading tasks on the event loop.

--
You received this message because you are subscribed to the Google Groups "vert.x" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vertx+un...@googlegroups.com.
To view this discussion on the web, visit https://groups.google.com/d/msgid/vertx/05d46a93-38cd-41b7-8fe0-2386d8f8e426n%40googlegroups.com.

zaiyua lon

unread,
Feb 10, 2022, 9:44:54 PM2/10/22
to vert.x
Thanks for your advice. I'm going to try these:
- set up an auto release timer when lock acquired.
- set a shorter timeout when trying to acquire the lock.
it seems promising to overcome a dead lock. BTW, I also use caffeine as an alternative in other parts of my application. But In some cases, a standalone cache server like redis is a better choice.

I'm expecting a new try-catch clause that works in an async way, something like this. (not sure if it's possible in Java)
------------------------------------------------------------------------------------------------------------
promise.try({
      // can put any code that may produce any exceptions

      // success
      promise.complete();
      // failure
      promise.fail(e)
}).finally(ar->promise) // if a exception is not captured inside the 'try' wrapper ( including nested callbacks, composed futures.... )
------------------------------------------------------------------------------------------------------------

Thomas SEGISMONT

unread,
Feb 11, 2022, 10:19:30 AM2/11/22
to ve...@googlegroups.com
Le ven. 11 févr. 2022 à 03:44, zaiyua lon <lon.z...@gmail.com> a écrit :
Thanks for your advice. I'm going to try these:
- set up an auto release timer when lock acquired.
- set a shorter timeout when trying to acquire the lock.
it seems promising to overcome a dead lock. BTW, I also use caffeine as an alternative in other parts of my application. But In some cases, a standalone cache server like redis is a better choice.

I'm expecting a new try-catch clause that works in an async way, something like this. (not sure if it's possible in Java)

Not possible in Java (yet) but you can do try/finally in Kotlin with coroutines.
Also, if you use the RxJava or Mutiny bindings for Vert.x, any runtime exception thrown in your operations will be reported to the subscriber.
 
Reply all
Reply to author
Forward
0 new messages