Hello @eager hinge
Lettuce is designed as a multiplexing driver, meaning - multiple application threads can execute commands simultaneously through a single Lettuce connection. However there are a few scenarios when this is not true.
- Blocking Commands (BLPOP, BRPOP, BLMOVE, etc.) - all commands after the blocking one are stuck waiting
Thread 1: BLPOP mylist 30 ──────────────────────────────→ (waiting for data...) Thread 2: GET key ──→ ??? STUCK waiting Thread 3: SET foo ──→ ??? STUCK waiting ↑ Commands are QUEUED behind the blocking command
- Transactions (MULTI/EXEC) - as the first example causes some performance degradation, transactions are worse, because they can break the connection state.
Thread 1: MULTI ← Connection enters "transaction mode" Thread 1: SET key1 val1 → QUEUED Thread 2: GET foo → QUEUED (oops! now part of Thread 1's transaction!) Thread 1: SET key2 val2 → QUEUED Thread 1: EXEC ← Executes ALL queued commands including Thread 2's!
Solution - in those cases you should use dedicated connection.Example:
CompletableFuture.supplyAsync(() -> { try (var conn = pool.borrowObject()) { // Dedicated connection var sync = conn.sync(); sync.multi(); sync.set("key1", "v1"); sync.set("key2", "v2"); return sync.exec(); } }, virtualThreadExecutor);