diff --git a/abstracto-application/abstracto-modules/starboard/starboard-impl/src/main/java/dev/sheldan/abstracto/starboard/listener/StarAddedListener.java b/abstracto-application/abstracto-modules/starboard/starboard-impl/src/main/java/dev/sheldan/abstracto/starboard/listener/StarAddedListener.java index 1aae8f007..502b2553e 100644 --- a/abstracto-application/abstracto-modules/starboard/starboard-impl/src/main/java/dev/sheldan/abstracto/starboard/listener/StarAddedListener.java +++ b/abstracto-application/abstracto-modules/starboard/starboard-impl/src/main/java/dev/sheldan/abstracto/starboard/listener/StarAddedListener.java @@ -1,6 +1,7 @@ package dev.sheldan.abstracto.starboard.listener; import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException; import dev.sheldan.abstracto.core.listener.DefaultListenerResult; import dev.sheldan.abstracto.core.listener.async.jda.AsyncReactionAddedListener; import dev.sheldan.abstracto.core.metric.service.CounterMetric; @@ -42,7 +43,12 @@ public class StarAddedListener extends StarboardListener implements AsyncReactio log.info("User {} in server {} reacted with star to put a message {} from channel {} on starboard.", model.getUserReacting().getUserId(), model.getServerId(), model.getMessage().getMessageId(), model.getMessage().getChannelId()); Optional reactionOptional = emoteService.getReactionFromMessageByEmote(model.getMessage(), aEmote); - handleStarboardPostChange(model.getMessage(), reactionOptional.orElse(null), model.getUserReacting(), true); + try { + handleStarboardPostChange(model.getMessage(), reactionOptional.orElse(null), model.getUserReacting(), true); + } catch (InterruptedException e) { + log.error("Starboard post change failed in guild {} for message {} in channel {}", serverId, model.getMessage().getChannelId(), model.getMessage().getMessageId(), e); + throw new AbstractoRunTimeException(e); + } return DefaultListenerResult.PROCESSED; } else { return DefaultListenerResult.IGNORED; diff --git a/abstracto-application/abstracto-modules/starboard/starboard-impl/src/main/java/dev/sheldan/abstracto/starboard/listener/StarRemovedListener.java b/abstracto-application/abstracto-modules/starboard/starboard-impl/src/main/java/dev/sheldan/abstracto/starboard/listener/StarRemovedListener.java index c1282d9e4..73b008cef 100644 --- a/abstracto-application/abstracto-modules/starboard/starboard-impl/src/main/java/dev/sheldan/abstracto/starboard/listener/StarRemovedListener.java +++ b/abstracto-application/abstracto-modules/starboard/starboard-impl/src/main/java/dev/sheldan/abstracto/starboard/listener/StarRemovedListener.java @@ -1,6 +1,7 @@ package dev.sheldan.abstracto.starboard.listener; import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException; import dev.sheldan.abstracto.core.listener.DefaultListenerResult; import dev.sheldan.abstracto.core.listener.async.jda.AsyncReactionRemovedListener; import dev.sheldan.abstracto.core.metric.service.CounterMetric; @@ -43,7 +44,12 @@ public class StarRemovedListener extends StarboardListener implements AsyncReact log.info("User {} in server {} removed star reaction from message {} on starboard.", userRemoving.getUserId(), model.getServerId(), model.getMessage().getMessageId()); Optional reactionOptional = emoteService.getReactionFromMessageByEmote(model.getMessage(), aEmote); - handleStarboardPostChange(model.getMessage(), reactionOptional.orElse(null), userRemoving, false); + try { + handleStarboardPostChange(model.getMessage(), reactionOptional.orElse(null), userRemoving, false); + } catch (InterruptedException e) { + log.error("Starboard post change failed in guild {} for message {} in channel {}", guildId, model.getMessage().getChannelId(), model.getMessage().getMessageId(), e); + throw new AbstractoRunTimeException(e); + } return DefaultListenerResult.PROCESSED; } else { return DefaultListenerResult.IGNORED; diff --git a/abstracto-application/abstracto-modules/starboard/starboard-impl/src/main/java/dev/sheldan/abstracto/starboard/listener/StarboardListener.java b/abstracto-application/abstracto-modules/starboard/starboard-impl/src/main/java/dev/sheldan/abstracto/starboard/listener/StarboardListener.java index 4d4cb3095..d0a0e160e 100644 --- a/abstracto-application/abstracto-modules/starboard/starboard-impl/src/main/java/dev/sheldan/abstracto/starboard/listener/StarboardListener.java +++ b/abstracto-application/abstracto-modules/starboard/starboard-impl/src/main/java/dev/sheldan/abstracto/starboard/listener/StarboardListener.java @@ -9,6 +9,7 @@ import dev.sheldan.abstracto.core.models.cache.CachedReactions; import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.service.ConfigService; import dev.sheldan.abstracto.core.service.EmoteService; +import dev.sheldan.abstracto.core.service.LockService; import dev.sheldan.abstracto.core.service.management.ConfigManagementService; import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; import dev.sheldan.abstracto.starboard.config.StarboardFeatureConfig; @@ -27,6 +28,7 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.Semaphore; import java.util.stream.Collectors; @Component @@ -63,6 +65,8 @@ public abstract class StarboardListener { public static final String STARBOARD_POSTS = "starboard.posts"; public static final String STAR_ACTION = "action"; + public static final Semaphore STARBOARD_SEMAPHORE = new Semaphore(1, true); + protected static final CounterMetric STARBOARD_STARS_THRESHOLD_REACHED = CounterMetric .builder() .name(STARBOARD_POSTS) @@ -75,16 +79,16 @@ public abstract class StarboardListener { .tagList(Arrays.asList(MetricTag.getTag(STAR_ACTION, "threshold.below"))) .build(); - protected void handleStarboardPostChange(CachedMessage message, CachedReactions reaction, ServerUser userReacting, boolean adding) { + protected void handleStarboardPostChange(CachedMessage message, CachedReactions reaction, ServerUser userReacting, boolean adding) throws InterruptedException { Long starMaxDays = configService.getLongValueOrConfigDefault(StarboardFeatureConfig.STAR_MAX_DAYS_CONFIG_KEY, message.getServerId()); if(message.getTimeCreated().isBefore(Instant.now().minus(starMaxDays, ChronoUnit.DAYS))) { log.info("Post {} in channel {} in guild {} is beyond the configured max day amount of {} - ignoring.", message.getMessageId(), message.getChannelId(), message.getServerId(), starMaxDays); return; } - Optional starboardPostOptional = starboardPostManagementService.findByMessageId(message.getMessageId()); - boolean starboardPostExists = starboardPostOptional.isPresent(); - if(starboardPostExists && starboardPostOptional.get().isIgnored()) { - log.info("Starboard post {} for message {} in server {} is ignored. Doing nothing.", starboardPostOptional.get().getId(), message.getMessageId(), message.getServerId()); + Optional firstOptional = starboardPostManagementService.findByMessageId(message.getMessageId()); + boolean starboardPostExists = firstOptional.isPresent(); + if(starboardPostExists && firstOptional.get().isIgnored()) { + log.info("Starboard post {} for message {} in server {} is ignored. Doing nothing.", firstOptional.get().getId(), message.getMessageId(), message.getServerId()); return; } if(reaction != null) { @@ -93,29 +97,42 @@ public abstract class StarboardListener { Long starMinimum = getFromConfig(FIRST_LEVEL_THRESHOLD_KEY, message.getServerId()); AUserInAServer userAddingReaction = userInServerManagementService.loadOrCreateUser(userReacting); if (userExceptAuthor.size() >= starMinimum) { - log.info("Post reached starboard minimum. Message {} in channel {} in server {} will be starred/updated.", - message.getMessageId(), message.getChannelId(), message.getServerId()); - if(starboardPostExists) { - updateStarboardPost(message, userAddingReaction, adding, starboardPostOptional.get(), userExceptAuthor); - } else { - metricService.incrementCounter(STARBOARD_STARS_THRESHOLD_REACHED); - log.info("Creating starboard post for message {} in channel {} in server {}", message.getMessageId(), message.getChannelId(), message.getServerId()); - starboardService.createStarboardPost(message, userExceptAuthor, userAddingReaction, author).exceptionally(throwable -> { - log.error("Failed to persist starboard post for message {}.", message.getMessageId(), throwable); - return null; - }); + try { + STARBOARD_SEMAPHORE.acquire(); + Optional secondOptional = starboardPostManagementService.findByMessageId(message.getMessageId()); + log.info("Post reached starboard minimum. Message {} in channel {} in server {} will be starred/updated.", + message.getMessageId(), message.getChannelId(), message.getServerId()); + if(secondOptional.isPresent()) { + updateStarboardPost(message, userAddingReaction, adding, secondOptional.get(), userExceptAuthor); + STARBOARD_SEMAPHORE.release(); + } else { + metricService.incrementCounter(STARBOARD_STARS_THRESHOLD_REACHED); + log.info("Creating starboard post for message {} in channel {} in server {}", message.getMessageId(), message.getChannelId(), message.getServerId()); + starboardService.createStarboardPost(message, userExceptAuthor, userAddingReaction, author).exceptionally(throwable -> { + log.error("Failed to persist starboard post for message {}.", message.getMessageId(), throwable); + STARBOARD_SEMAPHORE.release(); + return null; + }).thenAccept(unused -> { + log.info("Releasing starboard post lock for message {}.", message.getMessageId()); + STARBOARD_SEMAPHORE.release(); + }); + } + } catch (Throwable throwable) { + log.error("Failed to create starboard post for message {}.", message.getMessageId(), throwable); + STARBOARD_SEMAPHORE.release(); + throw throwable; } } else { if(starboardPostExists) { metricService.incrementCounter(STARBOARD_STARS_THRESHOLD_FELL); log.info("Removing starboard post for message {} in channel {} in server {}. It fell under the threshold {}", message.getMessageId(), message.getChannelId(), message.getServerId(), starMinimum); - starboardPostOptional.ifPresent(starboardPost -> completelyRemoveStarboardPost(starboardPost, userReacting)); + firstOptional.ifPresent(starboardPost -> completelyRemoveStarboardPost(starboardPost, userReacting)); } } } else { if(starboardPostExists) { log.info("Removing starboard post for message {} in channel {} in server {}", message.getMessageId(), message.getChannelId(), message.getServerId()); - starboardPostOptional.ifPresent(starboardPost -> completelyRemoveStarboardPost(starboardPost, userReacting)); + firstOptional.ifPresent(starboardPost -> completelyRemoveStarboardPost(starboardPost, userReacting)); } } }