linagora / tmail-backend

GNU Affero General Public License v3.0
39 stars 22 forks source link

OutOfMemoryError when run EnforceRateLimitingPlanTest #761

Closed vttranlina closed 1 year ago

vttranlina commented 1 year ago

Description of the bug

Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "lettuce-eventExecutorLoop***

Reproduction Steps

Run repeat until failure EnforceRateLimitingPlanTest (with my PC, it will throw when tests passed repeat at 2xx)

Additional information

vttranlina commented 1 year ago

MyIDE image

vttranlina commented 1 year ago

I guess it relates to io.lettuce.core.resource.ClientResources, which has not been closed correctly So I tried to modify org.apache.james.rate.limiter.redis.RedisRateLimiterFactory is bellow but I can't get better

class RedisRateLimiterFactory @Inject()(redisConfiguration: RedisRateLimiterConfiguration) extends RateLimiterFactory {

  var resources: ClientResources = _
  val rateLimitjFactory: AbstractRequestRateLimiterFactory[RedisSlidingWindowRequestRateLimiter] =
    if (redisConfiguration.isCluster) {
      val resourceBuilder = ClientResources.builder()
        .threadFactoryProvider(poolName => NamedThreadFactory.withName(s"redis-driver-$poolName"))
      redisConfiguration.ioThreads.foreach(value => resourceBuilder.ioThreadPoolSize(value))
      redisConfiguration.workerThreads.foreach(value =>resourceBuilder.computationThreadPoolSize(value))
      new RedisClusterRateLimiterFactory(RedisClusterClient.create(resourceBuilder.build(),
        redisConfiguration.redisURI.value.asJava))
    } else {
      val resourceBuilder = ClientResources.builder()
        .threadFactoryProvider(poolName => NamedThreadFactory.withName(s"redis-driver-$poolName"))
      redisConfiguration.ioThreads.foreach(value => resourceBuilder.ioThreadPoolSize(value))
      redisConfiguration.workerThreads.foreach(value => resourceBuilder.computationThreadPoolSize(value))
      resources = resourceBuilder.build();

      new RedisSingleInstanceRateLimitjFactory(RedisClient.create(resources, redisConfiguration.redisURI.value.last))
    }

  def preDestroy(): Unit = {
    resources.shutdown()
  }
quantranhong1999 commented 1 year ago

May help to get some insights from the heap dump: https://heaphero.io/heap-report-wc.jsp?p=RThCZ0lOakJ3K2dCd3FiSEc4VEhscytQdnNlRUVoSExkWjdwdlY1T01IUzlVR1JsaGU4dFpzUGtZc1BFdE5vSnQ0SXNuNkNaMzhOdWM2N2hzdVBTZFE9PQ==

5,094 instances of "io.netty.buffer.PoolThreadCache", loaded by "jdk.internal.loader.ClassLoaders$AppClassLoader @ 0xc0b26618" occupy 705,688,952 (66.28%) bytes

My guess: We initiate multiple times the lettuce driver in tests, and in somehow the driver is not freed after each test.

@BeforeEach
redisRateLimiterFactory = new RedisRateLimiterFactory(RedisRateLimiterConfiguration.from(redis.redisURI().toString, isCluster = false))

Not a big deal in tests IMO. But in production RedisRateLimiterFactory should be in SINGLETON scope IMO.

vttranlina commented 1 year ago

local issue