skip to Main Content

I have a kotlin spring data application with redis caching. I want to clean a Redis database and use redisConnectionFactory.connection.serverCommands().flushAll() command for it. When I run unit tests individually, everything works without any problems. But when I try to run all the tests in the application (there are about 200 of them) or even tests from any class, then after passing 8 tests everything freezes on the command redisConnectionFactory.connection.serverCommands().flushAll() (I added logs for debugging, I understood this from it). It’s no use waiting, I have to stop the tests. There is no dependency on tests; this can be reproduced for different classes with different tests.

Perhaps someone has encountered this and can at least give me a hint? Or if someone can give me advice on how to solve my problem of clearing the cache in a different way, I will also be very grateful.

@SpringBootTest
@ExtendWith(SpringExtension::class)
@Import(TestChannelBinderConfiguration::class)
@ContextConfiguration(initializers = [RedisInitializer::class])
@RecordApplicationEvents
abstract class AbstractIntegrationTest {
    @Autowired
    protected lateinit var redisConnectionFactory: RedisConnectionFactory


    @AfterEach
    fun clean() {
        redisConnectionFactory.connection.serverCommands().flushAll()
    }
}

RedisInitializer:

class RedisInitializer : ApplicationContextInitializer<ConfigurableApplicationContext> {

    override fun initialize(context: ConfigurableApplicationContext) {
        TestPropertyValues.of(
            "spring.redis.host=" + REDIS_CONTAINER.host,
            "spring.redis.port=" + REDIS_CONTAINER.getMappedPort(REDIS_PORT)
        ).applyTo(context.environment)
    }

    companion object {
        private const val REDIS_PORT = 6379
        private val IMAGE_NAME = DockerImageName.parse("redis:6.2-alpine")
        var REDIS_CONTAINER: GenericContainer<*> = GenericContainer<Nothing>(IMAGE_NAME)
            .withExposedPorts(REDIS_PORT)

        init {
            REDIS_CONTAINER.start()
        }
    }
}

RedisConfig:

@Configuration
@EnableCaching
class RedisConfig(
    val redisProperties: RedisProperties
) {
    @Bean
    fun redisTemplate(connectionFactory: RedisConnectionFactory): RedisTemplate<String, Any> =
        RedisTemplate<String, Any>().apply {
            setConnectionFactory(connectionFactory)
            setDefaultSerializer(StringRedisSerializer())
            setValueSerializer(redisSerializer())
        }

    @Bean
    fun redisSerializer(): RedisSerializer<Any> {
        val objectMapper = ObjectMapper().apply {
            setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
        }
        return Jackson2JsonRedisSerializer(Any::class.java)
            .apply {
                setObjectMapper(objectMapper)
            }
    }

    @Bean
    fun jedisConnectionFactory(): RedisConnectionFactory {
        val jedisConnectionFactory = if (redisProperties.sentinel != null) {
            JedisConnectionFactory(getSentinelConfiguration())
        } else {
            JedisConnectionFactory(getStandaloneConfiguration())
        }
        jedisConnectionFactory.afterPropertiesSet()
        return jedisConnectionFactory
    }

    private fun getSentinelConfiguration(): RedisSentinelConfiguration {
        val sentinelConfiguration = RedisSentinelConfiguration()
        sentinelConfiguration.setMaster(redisProperties.sentinel.master)
        sentinelConfiguration.password = RedisPassword.of(redisProperties.password)
        sentinelConfiguration.sentinelPassword = RedisPassword.of(redisProperties.sentinel.password)
        sentinelConfiguration.setSentinels(
            redisProperties.sentinel.nodes.stream().map { node: String ->
                val nodeConf = node.split(":").toTypedArray()
                RedisNode(nodeConf[0], nodeConf[1].toInt())
            }.collect(Collectors.toList())
        )
        return sentinelConfiguration
    }

    /**
     * Standalone is used only in integration tests 
     * when raising the application context and connecting 
     * to the Redis docker image from the test container.
     */
    private fun getStandaloneConfiguration(): RedisStandaloneConfiguration {
        val configuration = RedisStandaloneConfiguration()
        configuration.hostName = redisProperties.host
        configuration.port = redisProperties.port
        configuration.password = RedisPassword.of(redisProperties.password)
        return configuration
    }
}

2

Answers


  1. Chosen as BEST ANSWER

    I replaced RedisConnectionFactory with RedisTemplate and it helped me:

    @SpringBootTest
    @ExtendWith(SpringExtension::class)
    @Import(TestChannelBinderConfiguration::class)
    @ContextConfiguration(initializers = [RedisInitializer::class])
    @RecordApplicationEvents
    abstract class AbstractIntegrationTest {
        @Autowired
        protected lateinit var redisTemplate: RedisTemplate<String, Any>
    
    
        @AfterEach
        fun clean() {
            redisTemplate.execute { connection: RedisConnection ->
                connection.serverCommands().flushDb()
                null
            }
        }
    }
    
    

  2. This command redisConnectionFactory.connection.serverCommands().flushAll() should exhaust connection pool. That’s why you have successfully run only 8 times (apparently pool config have at max 8 connections)

    How about that:

    try(var connection = redisConnectionFactory.getConnection()) {
        connection.serverCommands().flushDb()
    } 
    

    It guaranties connections will be released.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search