From 70486b6f58fcca28149d35888b61166eb655a6fc Mon Sep 17 00:00:00 2001 From: Mateusz Tworek Date: Thu, 8 Jan 2026 11:47:22 +0100 Subject: [PATCH] debezium/dbz#1488 Fix race condition in RedisOffsetBackingStore causing infinite NPE loop When Redis becomes temporarily unavailable, a race condition can occur: - Thread A catches RedisClientConnectionException and calls connect() - connect() calls closeClient() which sets client = null - Thread B concurrently tries to use client.hset() and gets NullPointerException - NPE is not a RedisClientConnectionException, so no reconnect is attempted - Thread B retries indefinitely with null client, causing infinite NPE loop Fix: - Make client field volatile for cross-thread visibility - Add null checks in load() and save() that throw RedisClientConnectionException when client is null, triggering the existing reconnect logic Signed-off-by: Mateusz Tworek --- .../storage/redis/offset/RedisOffsetBackingStore.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/debezium-storage/debezium-storage-redis/src/main/java/io/debezium/storage/redis/offset/RedisOffsetBackingStore.java b/debezium-storage/debezium-storage-redis/src/main/java/io/debezium/storage/redis/offset/RedisOffsetBackingStore.java index cecc39024a5..356e599733d 100644 --- a/debezium-storage/debezium-storage-redis/src/main/java/io/debezium/storage/redis/offset/RedisOffsetBackingStore.java +++ b/debezium-storage/debezium-storage-redis/src/main/java/io/debezium/storage/redis/offset/RedisOffsetBackingStore.java @@ -35,7 +35,7 @@ public class RedisOffsetBackingStore extends MemoryOffsetBackingStore { private RedisOffsetBackingStoreConfig config; - private RedisClient client; + private volatile RedisClient client; public RedisClient getRedisClient() { return client; @@ -99,6 +99,9 @@ public synchronized void stop() { void load() { // fetch the value from Redis Map offsets = Uni.createFrom().item(() -> { + if (client == null) { + throw new RedisClientConnectionException(new RuntimeException("Redis client is null")); + } return (Map) client.hgetAll(config.getRedisKeyName()); }) // handle failures and retry @@ -142,6 +145,9 @@ protected void save() { byte[] value = (mapEntry.getValue() != null) ? mapEntry.getValue().array() : null; // set the value in Redis Uni.createFrom().item(() -> { + if (client == null) { + throw new RedisClientConnectionException(new RuntimeException("Redis client is null")); + } return (Long) client.hset(config.getRedisKeyName().getBytes(), key, value); }) // handle failures and retry