lettuce icon indicating copy to clipboard operation
lettuce copied to clipboard

Docs improvement - what happens synchronously in async method

Open jacekgajek opened this issue 2 years ago • 2 comments

Maybe I'm not smart enough, but I think this part of docs https://github.com/lettuce-io/lettuce-core/wiki/Pipelining-and-command-flushing could be improved

A good example is the async API. Every invocation on the async API returns a Future (response handle) after the command is written to the netty pipeline. A write to the pipeline does not mean, the command is written to the underlying transport. Multiple commands can be written without awaiting the response. Invocations to the API (sync, async and starting with 4.0 also reactive API) can be performed by multiple threads.

Maybe it could specify what happens after the "Future". Since it mentions only pipeline, I thought that just calling methods does nothing synchronously, and I need to call thenAccept if I wanted a command be executed after the previous one (which usually is the case with async programming). Sure, examples show that this is how it should be done:

    futures.add(commands.set("key-" + i, "value-" + i));
 // apparently it saves commands to a queue synchronously, despite its an AsyncCommands
    futures.add(commands.expire("key-" + i, 3600));

 // executes all commands in order because they already are in the pipeline
boolean result = LettuceFutures.awaitAll(5, TimeUnit.SECONDS,
                   futures.toArray(new RedisFuture[futures.size()]));

Baeldung doesn't have a separate example for async, instead it points to Future documentation which again made me think I need to use thenAccept

https://www.baeldung.com/java-redis-lettuce

But coming from kotlin coroutines which also has await, it's a totally unexpected behaviour. Maybe mention it in javadocs of io.lettuce.core.api.async.RedisAsyncCommands<K, V> async() as well?

Another example:

RedisFuture f1 = conn.async().get(a);
RedisFuture f2 = conn.async().get(b);
RedisFuture f3 = conn.async().del(a);
RedisFuture f4 = conn.async().del(b);

@mp911de https://github.com/lettuce-io/lettuce-core/issues/1627

If this works, then something must be executed synchronously, otherwise it would be equivalent to

RedisFuture f3 = conn.async().del(a);
RedisFuture f4 = conn.async().del(b);
RedisFuture f1 = conn.async().get(a);
RedisFuture f2 = conn.async().get(b);

Proposal:

Every invocation on the async API writes a command to the netty pipeline synchronusly and returns a Future when the command is actually executed in Redis. A write to the pipeline does not mean, the command is written to the underlying transport.

Cheers, Jacek

jacekgajek avatar Mar 22 '22 11:03 jacekgajek

Asynchronous invocations retain the order in which commands are invoked. Synchronous is wrong in that context, because a write internally enqueues Runnable that performs the actual I/O write using a netty I/O thread.

after the command is written to the netty pipeline

This implies already that we retain the command order so the snippets you provided where get and delete order is mixed up lead to different behavior.

Maybe it could specify what happens after the "Future".

Not sure what you refer to. https://github.com/lettuce-io/lettuce-core/wiki/Command-execution-reliability covers a bit of behind-the-scenes. Lettuce's manual isn't a general introduction to asynchronous programming, instead it covers the Lettuce specifics.

mp911de avatar Mar 22 '22 13:03 mp911de

Now it makes a perfect sense and I understand everything :) I'm not a newbie, I've been using async libraries quite a lot.

However, I'm new to Lettuce, so you can find my first-sight impression valuable – it's a rare occasion as the most of posts here are written by people who already have been using Lettuce for some time and are familiar with the basics.

Greetings, Jacek

jacekgajek avatar Mar 22 '22 16:03 jacekgajek