Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions eternalcode-commons-shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ plugins {
`commons-java-unit-test`
}

dependencies {
implementation("com.github.ben-manes.caffeine:caffeine:3.2.3")
}

tasks.test {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.eternalcode.commons.delay;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;

import java.time.Duration;
import java.time.Instant;
import java.util.function.Supplier;

public class Delay<T> {

private final Cache<T, Instant> cache;
private final Supplier<Duration> defaultDelay;

private Delay(Supplier<Duration> defaultDelay) {
if (defaultDelay == null) {
throw new IllegalArgumentException("defaultDelay cannot be null");
}

this.defaultDelay = defaultDelay;
this.cache = Caffeine.newBuilder()
.expireAfter(new InstantExpiry<T>())
.build();
Comment on lines +23 to +25
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The Caffeine cache is initialized without a maximum size, which can lead to memory exhaustion if a large number of unique keys are added. This is especially risky if the keys are derived from untrusted user input (e.g., unique user IDs or IP addresses). Adding a limit prevents the cache from growing indefinitely.

        this.cache = Caffeine.newBuilder()
            .expireAfter(new InstantExpiry<T>())
            .maximumSize(1000) // TODO: Adjust this value based on expected usage
            .build();

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this will affect our use-cases

}

public static <T> Delay<T> withDefault(Supplier<Duration> defaultDelay) {
return new Delay<>(defaultDelay);
}

public void markDelay(T key, Duration delay) {
if (delay.isZero() || delay.isNegative()) {
this.cache.invalidate(key);
}

this.cache.put(key, Instant.now().plus(delay));
}

public void markDelay(T key) {
this.markDelay(key, this.defaultDelay.get());
}

public void unmarkDelay(T key) {
this.cache.invalidate(key);
}

public boolean hasDelay(T key) {
Instant delayExpireMoment = this.getExpireAt(key);
return Instant.now().isBefore(delayExpireMoment);
}

public Duration getRemaining(T key) {
return Duration.between(Instant.now(), this.getExpireAt(key));
}

private Instant getExpireAt(T key) {
return this.cache.asMap().getOrDefault(key, Instant.MIN);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.eternalcode.commons.delay;

import com.github.benmanes.caffeine.cache.Expiry;

import java.time.Duration;
import java.time.Instant;

public class InstantExpiry<T> implements Expiry<T, Instant> {

private static long timeToExpire(Instant expireTime) {
Duration toExpire = Duration.between(Instant.now(), expireTime);
if (toExpire.isNegative()) {
return 0;
}

long nanos = toExpire.toNanos();
if (nanos == 0) {
return 1;
}

return nanos;
}

@Override
public long expireAfterCreate(T key, Instant expireTime, long currentTime) {
return timeToExpire(expireTime);
}

@Override
public long expireAfterUpdate(T key, Instant newExpireTime, long currentTime, long currentDuration) {
return timeToExpire(newExpireTime);
}

@Override
public long expireAfterRead(T key, Instant value, long currentTime, long currentDuration) {
return currentDuration;
}

}