Fixes race condition in QuoteBoardForwarder#1525
Open
Zabuzard wants to merge 1 commit into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
QuoteBoardForwarderhas a race condition that causes a message to be forwarded multiple times to#quotesif multiple users reacted to a message within short time. (This was already attempted with #1523 previously)This PR fixes the problem by slightly redesigning the logic-flow in a way that the race condition cannot occur anymore by design.
Details
Previously, the
onMessageReactionAddhandler decided whether a message will be forwarded or not. But this method is called concurrently by JDA on each reaction. So, by design, this is rather unsafe.The change introduced in this PR is conceptionally simple.
onMessageReactionAddnow only adds the message to aSet<ReactedMessage>, aka "messages to process" (like aQueue, but no duplicates).From there, it is then picked up by a
Routineonce a minute and the decision and handling is now done there instead. The routine is set toScheduleMode.FIXED_DELAYinstead ofFIXED_RATE, so by design it can only run after the previous routine run is done, not concurrently anymore. This fixes the underlying problem by design.The rest of the changes are just cosmetic and code-style improvements without any logic changes.
Remarks
Checks
Originally I wanted to do more of the early-outs already in
onMessageReactionAdd, so the messages ending up in the task-queue (Set<ReactedMessage>) are only messages that actually need to be forwarded to#quotes.Like, ideally
onMessageReactionAddwould already check:But sadly, due to JDA shenanigans, that isnt really possible. Well, it is possible, but it wouldnt yield any performance boost. The problem is that the
MessageReactionAddEvent eventdoes not provide a handle to the message yet. Accessing the message (to check if it exists, if it was already forwarded, or to compute its emoji scores) requires anevent.retrieveMessage()there already, i.e. a Rest-Api call.So doing these checks in the reaction-handler already would not save us anything and would actually lead to us having to do two API calls per message - not good.
Because of that those checks are all moved to the
Routineand the reaction-handler itself only does the checks it can do right away without any API calls.Lock
I would love to have avoided that
private final Object lockaround the set. Unfortunately, there is no better way to do an atomic "fetch and remove".Like, we either need to do
getAll-and-clear(drain) orgetOne-and-remove(poll).Setdoesnt offer this natively.Queuedoes, but that allows duplicates and we really dont want duplicates in this (since reactions usually come in waves).There are of course also other workarounds, but I ultimately decided to add a small lock instead.