-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Caching inconsistencies #189
Comments
What would you think the desired fix could be? |
That is a good question and I have no idea. |
Also is it just guild roles/permissions affected, or are channels, members, etc also affected too? |
Well, it can affect anything. It always depends how you query for things. |
I guess what could be done is that it can also invalidate the results from the methods as well when:
|
That would be a possibility. I have thought about possible fixes, but none are quite ideal: Option 1: Option 2: |
I thought that fetching IGuild meant that it won't include the roles, members and channels by default, so basically I was doing queries for the roles / channels when I could have only fetched the guild by id? |
So, generally, this is by design since data objects in the cache are immutable - this is for a multitude of reasons, but mainly thread safety. Discord itself operates on "eventual consistency" (which, to be honest, doesn't really mean much), so this cache behaviour is more or less expected. Discord also usually exposes routes to either get something like roles individually or by batch on their own, instead of going through another object which may or may not contain the values you're looking for. The primary reason I have a short cache duration by default is so this issue is minimized. You can request a non-caching implementation if you're in a critical path that must always know the latest information, but this does come with the usual cacheless caveats. I don't have a good fix either, but directly mutating values in the cache is out of the question. Creating new ones (fusing old information with new information) and replacing values is definitely something that could be done, and I have considered it in the past, but it means adding a lot of fragile code that needs to be maintained and updated. For the moment, I would strongly recommend not relying on retrieving nested objects from related ones ( |
Yeah I mostly agree.
@AraHaan
@Nihlus Agreed, though then the implementation of |
Yeah - in that particular case, it's weighing a small time window of potential permission bypass from someone who did have the right permissions before against a large increase in API hits for bigger bots. I'm honestly not sure what the best way is right now :( |
If it is only a permission update on a role that can cause such a permission bypass, I think best option would be either to evict the However I don't know the code good enough yet to know whether there are other cases where a critical case like a permission bypass can happen, other than this role update. For other more unimportant cases like a channel delete and it still being in
Or we do update the |
I have been thinking of making a custom way to cache the thing without using MemoryCache by making a special collection type that will hold each type of item from the events then on the Create events store them, the update events invalidate what came from the Create events, then on the delete events evict them from the collection completely in an individual item basis (which for my bot could mean I could significantly reduce requests to Discord per second which would reduce the chance of getting temp api banned from hitting ratelimits constantly). |
I believe that is how other libraries handle it as well, and how I've gone ahead to cache/track |
This is exactly what Remora's caching does. The issue is that Discord doesn't always, for example, send a guild update immediately when you change a role, and then the cache is out of sync. The only way to get around this is by performing data fusing or mutation, or not caching compound objects (which I definitely think is out of the question).
The main reason Remora doesn't cache voice states and presences by default is because they come in at astounding rates for large bots, and is generally not used by most bots. As for Redis, it works OOTB as it is at the moment. |
Not quite, because Remora doesn't fuse/update its cached compound objects (way better wording than how I tried to describe it).
Yes, I think that is a good thing and was definitely not criticizing that. It was just an anecdote to @AraHaan's custom cache.
Yeah, I was wondering how @AraHaan's caching solution would work with Redis. |
Description
In
Remora.Discord.Caching
there are instances where when a gateway event occurs or an API call is made, that a 'child' entity is updated in the cache, however a 'parent' entity that holds information about this child doesn't get updated.This creates inconsistencies in the cache, where the supposed same entities may be different depending on where you're looking.
This causes a variety of issues downstream in the library, an example of which I will outline below, but I think there are various similar issues.
Steps to Reproduce
An example: The permissions for a role is updated.
The responder below updates a specific guild role in the cache
Guild:...:Role:...
, however doesn't updateGuild:...:Roles
(or perhaps evenGuild:...
).https://github.com/Nihlus/Remora.Discord/blob/a117ef6c9b9b69ad058302b93f1a5b410a0c5eae/Backend/Remora.Discord.Caching/Responders/EarlyCacheResponder.cs#L228-L234
This in turn causes code that only checks for
Guild:...:Roles
to still have outdated and incorrect information:The
CachingDiscordRestGuildAPI
inGetGuildRolesAsync()
returns outdated information about the changed role and still gives us the old permissions.https://github.com/Nihlus/Remora.Discord/blob/a117ef6c9b9b69ad058302b93f1a5b410a0c5eae/Backend/Remora.Discord.Caching/API/CachingDiscordRestGuildAPI.cs#L584-L614
This again may be used downstream for example in
RequireDiscordPermissionCondition
where a check for a permission goes through even though the permission was removed from the role.https://github.com/Nihlus/Remora.Discord/blob/a117ef6c9b9b69ad058302b93f1a5b410a0c5eae/Remora.Discord.Commands/Conditions/RequireDiscordPermissionCondition.cs#L164-L192
Expected Behavior
In this example, when requiring a certain permission for a command and then removing it from a role without restarting the bot instance, the command will error due to missing permissions.
Current Behavior
In this example, when requiring a certain permission for a command and then removing it from a role without restarting the bot instance, the command will still execute despite the user/their role missing the required permissions.
The text was updated successfully, but these errors were encountered: