Skip to content

Getex extra options to avoid duplicate options#10419

Closed
hwware wants to merge 2 commits into
redis:unstablefrom
hwware:getex_update
Closed

Getex extra options to avoid duplicate options#10419
hwware wants to merge 2 commits into
redis:unstablefrom
hwware:getex_update

Conversation

@hwware

@hwware hwware commented Mar 10, 2022

Copy link
Copy Markdown
Contributor

This PR fixes the bug in redis related to getex.
Currently there is no check if duplicate options are added. For example
getex key ex 100 ex 400 will set the expiry to 400 and ignore ex 100
image

getex key ex x ex 400 will set the expiry to 400 and ignore the wrong argument x for ex
image

After the change we get the following result which is the default error being used in redis
image

Comment thread src/t_string.c Outdated
!(*flags & OBJ_EX) && !(*flags & OBJ_EXAT) &&
!(*flags & OBJ_PX) && !(*flags & OBJ_PXAT) &&
!(*flags & OBJ_KEEPTTL))
!(*flags & OBJ_KEEPTTL) && !(*flags & OBJ_PERSIST))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

how about this (extra line without touching the line), but its not critical

Suggested change
!(*flags & OBJ_KEEPTTL) && !(*flags & OBJ_PERSIST))
!(*flags & OBJ_PERSIST) &&
!(*flags & OBJ_KEEPTTL))

@zuiderkwast zuiderkwast left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Good and simple solution.

Comment thread src/t_string.c Outdated
!(*flags & OBJ_KEEPTTL) && !(*flags & OBJ_PERSIST) &&
!(*flags & OBJ_EXAT) && !(*flags & OBJ_PX) &&
!(*flags & OBJ_PXAT) && next)
!(*flags & OBJ_PXAT) && next && !(*flags & OBJ_EX))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(Minor unimportant suggestion) Since all these options are mutually exclusive, they can be listed in the same order in every else-if branch. next is different so I think it can be on a separate line. The flags could be checked all at once if you want:

        } else if ((opt[0] == 'e' || opt[0] == 'E') &&
                   (opt[1] == 'x' || opt[1] == 'X') && opt[2] == '\0' &&
                   !(*flags & (OBJ_KEEPTTL | OBJ_PERSIST | OBJ_EX | OBJ_PX | OBJ_EXAT | OBJ_PXAT)) &&
                   next)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hello, thanks for the idea. I like this change as it will simplify the if statements. I have updated the code.
Thanks

@hwware hwware requested a review from oranagra March 11, 2022 21:27
@oranagra oranagra added the breaking-change This change can potentially break existing application label Mar 13, 2022
@oranagra

Copy link
Copy Markdown
Member

don't we have that problem in many many commands?
fixing it is a breaking change, so from one perspective, maybe we rather not fix it.
on the other hand, users that accidentally use it, may have a bug in their application and might be using the wrong value, in which case triggering an error will alert them on their bug (but might also silently break the app without anyone noticing too).
@yossigo what do you think? (considering this might be a wide spread issue)
i.e. the problem existed in set k v ex 123 ex 123 since forever.

@yossigo

yossigo commented Mar 13, 2022

Copy link
Copy Markdown
Collaborator

@oranagra I tend to agree with you. This is not necessarily a bug and if we're able to define how this uncommon case is expected to behave across all commands, that may be a better option vs. changing behavior.

@zuiderkwast

Copy link
Copy Markdown
Contributor

FWIW, I think it should be considered a bug and fixed. The documented syntax only allows one option. Nobody wants hidden bugs in their application.

Btw, the PR seems to affect all SET and GET related commands with expire options. It needs to be reflected in the title and top comment.

@oranagra

Copy link
Copy Markdown
Member

The problem is even deeper than just these two commands, if we wanna considering fixing it, we need to first compose a full list of the affected commands.

Note that even fixing it can possibly lead to bugs in some apps, the ones who log the error and keep running. with the above example, in the past these some set and expiration succeed, and now it would be empty.

@zuiderkwast

Copy link
Copy Markdown
Contributor

If we don't fix it, then I think we should document the behaviour (the last occurrence of the duplicated option wins) so that we can avoid future PRs fixing this again and again. (I think I have seen some similar fix before, with a similar discussion.)

@daniel-house

Copy link
Copy Markdown

In either case new tests would be a good idea.

GETEX K V EX ttl1 EX tt2
SET K V PXAT ttl1 PXAT ttl2
etc.

The same tests for both cases, just different expected results.

@daniel-house

Copy link
Copy Markdown

@oranagra "we need to first compose a full list of the affected commands"

There are exactly 2 affected commands: GETEX and SET. Those are the only commands that call the code with the bug ... oops, I mean the undocumented feature :)

@oranagra

Copy link
Copy Markdown
Member

@daniel-house these are the two commands that call the code modified here. but there are a ton of other commands that probably do the same practice.
for instance, RESTORE IDLETIME.
there's no sense to start patching SET and GETEX if we don't consider the full picture.

@daniel-house

Copy link
Copy Markdown

@oranagra I'm not sure what you mean by "the same practice". I looked at the code for RESTORE and I might make a guess that you mean "any command that takes options". Could you please elaborate on your criteria for a command being affected?

@oranagra

Copy link
Copy Markdown
Member

yes, any command that takes argument, for instance
RESTORE <key> <ttl> <payload> idletime 10 idletime 20
COPY <key> <dst> DB 2 DB 3

@madolson

madolson commented Mar 16, 2022

Copy link
Copy Markdown
Contributor

My vote would be to go through all of the commands that have the issue and fix it for Redis 7. I think some specific use cases aren't a big deal, since adding the flag multiple times is idempotent (ZADD with XX for example), but the other cases where providing the same argument multiple times can cause different behavior can be a source of bugs.

@hwware

hwware commented Mar 16, 2022

Copy link
Copy Markdown
Contributor Author

My vote would be to go through all of the commands that have the issue and fix it for Redis 7. I think this specific use case isn't a big deal, since adding the flag multiple times is idempotent, but the other cases where providing the same argument multiple times can cause different behavior can be a source of bugs.

I could help community summarize the command list, and find a proper way to solve this issue.

@daniel-house

Copy link
Copy Markdown

@madolson The specific use case that I am thinking of is GETEX key EX ttl1 EX ttl2. In this case adding EX is only idempotent when ttl1 = ttl2. You mentioned a flag, so may be you are thinking of a different use case.

Note, my testing shows that GETEX key EX a-non-integer-string EX a-valid-ttl will be accepted, which is weird. This happens because the code does not attempt to parse a-non-integer-string.

@madolson

Copy link
Copy Markdown
Contributor

@daniel-house Yes, my brain was not working. The getex case is an example where it does matter. I was thinking about cases like ZADD with XX (Which my brain somehow made the jump too)

@daniel-house

Copy link
Copy Markdown

I agree that in some cases like ZADD with XX adding the same flag more than once does not change the behavior. However, if I do something like that it will be a mistake on my part. I find it difficult to imagine a use case where anyone would want to do that. My personal preference when I write queries is to get strict feedback about mistakes as early as possible, and for me a mistake like this could be a symptom of bigger problems.

@oranagra

Copy link
Copy Markdown
Member

we discussed this in a core-team meeting, and came to two important realizations:

  1. most apps use a client library and not form their own command arguments, so they're unlikely to hit this.
  2. fixing this could cause nasty data inconsistencies in upgrade. i.e. you upgrade the replica first, it'll then start rejecting commands that successfully executed on the master.

so if we wanna fix it safely, we may need to reject commands with repeated arguments, only when they didn't originate from the master or AOF client.

@oranagra

Copy link
Copy Markdown
Member

i went over other breaking changes we recently made, to check which of them could have caused issues of rejecting (or resulting with a different outcome) when executed on an upgraded replica, vs it's master.

a few examples that are not a problem:

the only one i could find that can cause such issues are:

This realization means that not only that we must always respect commands propagated from masters / AOF, we might also have to be aware which version of redis was the source of that command.
i.e. if a command can be executed in two different ways we need to know how the master executed it and be "bug" compatible with that specific version.

@redis/core-team FYI.

@oranagra

Copy link
Copy Markdown
Member

2 more interesting cases:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change This change can potentially break existing application

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants