Skip to content

ByteUtils.getBytes(ByteBuffer) does not respect buffer position for heap buffers #2204

@hban

Description

@hban

Noticed after upgrading from 2.5.5 to 2.6.0. If RedisElementWriter returns ByteBuffer that is not completely filled (limit() < capacity()), Spring Data will still use the entire buffer (including trailing junk bytes), instead of using only the filled part. This happens for ZSet values at least, I haven't checked other scenarios.

Reproducer:

import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.serializer.RedisElementReader;
import org.springframework.data.redis.serializer.RedisElementWriter;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import java.nio.ByteBuffer;

public class Foo {
    public static void main(String[] args) {
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(configuration);
        connectionFactory.afterPropertiesSet();

        RedisElementReader<Integer> reader = buffer -> { throw new UnsupportedOperationException(); };
        RedisElementWriter<Integer> writer = element -> {
            // Buffer is intentionally larger than necessary.
            ByteBuffer buffer = ByteBuffer.allocate(16);
            buffer.putInt(element);
            buffer.flip();
            System.out.printf("Serialized value (%d) has %d bytes%n", element, buffer.remaining());
            return buffer;
        };

        RedisSerializationContext<Integer, Integer> serializationContext = RedisSerializationContext
                .<Integer, Integer>newSerializationContext()
                .key(reader, writer)
                .value(reader, writer)
                .hashKey(reader, writer)
                .hashValue(reader, writer)
                .build();

        ReactiveRedisTemplate<Integer, Integer> intTemplate =
                new ReactiveRedisTemplate<>(connectionFactory, serializationContext);
        intTemplate.opsForZSet().add(20, 21, 22.0).block();

        ReactiveRedisTemplate<byte[], byte[]> byteTemplate =
                new ReactiveRedisTemplate<>(connectionFactory, RedisSerializationContext.byteArray());
        byteTemplate.opsForZSet()
                .scan(new byte[] { 0, 0, 0, 20 })
                .doOnNext(tuple -> {
                    // This prints "4 bytes" on 2.5.5, but "16 bytes" on 2.6.0.
                    System.out.printf("Deserialized value has %d bytes%n", tuple.getValue().length);
                })
                .then()
                .block();
    }
}

Metadata

Metadata

Assignees

Labels

type: regressionA regression from a previous release

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions