-
Notifications
You must be signed in to change notification settings - Fork 2
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
Implement Meta Arithmetic functionality #71
Conversation
861efde
to
2f77099
Compare
pub fn parse_meta_arithmetic_response( | ||
buf: &[u8], | ||
) -> Result<Option<(usize, MetaResponse)>, ErrorKind> { | ||
let total_bytes = buf.len(); | ||
let result = parse_meta_arithmetic_data_value(buf); | ||
|
||
match result { | ||
Ok((remaining_bytes, response)) => { | ||
let read_bytes = total_bytes - remaining_bytes.len(); | ||
Ok(Some((read_bytes, response))) | ||
} | ||
Err(nom::Err::Incomplete(_)) => Ok(None), | ||
Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => { | ||
Err(ErrorKind::Protocol(Some(e.code.description().to_string()))) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These parse_meta_*_response
methods can probably be refactored into a generic parse_meta_response
closure that takes the appropriate value parser for each operation.
src/parser/meta_parser.rs
Outdated
let (input, data) = take_until_size(input, size)?; // parses the data from the input | ||
let (input, mut data) = take_until_size(input, size)?; // parses the data from the input | ||
|
||
data = data.trim_ascii_end(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needed to add this in to compensate for memcached behaviour when decrementing a counter from a number with n
digits to a number with n-1
digits. When this happens, the memcached server still holds the same size
parameter for the value and fills the extra number of bytes with whitespace when the value is fetched.
e.g. decrementing from 10 -> 9 and then performing a meta_get
with v
flag on that key will give a value of 9_
(underscore representing whitespace) instead of the expected 9
.
90bcd09
to
447f4bb
Compare
4caf277
to
c836405
Compare
value(MetaResponse::Status(Status::NotFound), tag(b"NF")), | ||
value(MetaResponse::Status(Status::NotStored), tag(b"NS")), | ||
value(MetaResponse::Status(Status::Exists), tag(b"EX")), | ||
value(MetaResponse::Status(Status::NoOp), tag(b"MN\r\n")), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do all of the above response codes not contain \r\n
and the no-op one does?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you request the key or an opaque in return then they will be part of the response from the server and the code won't be followed by \r\n
. MN
is always followed by \r\n
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does Tag always look at the first two characters? I'm not exactly sure how this is parsing things, was wondering about what happens if the key has HD in it for example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nom
's tag parser will look for the first instance of the specified text exactly, and this is the first parsing step being called. All responses from the server begin with a two character status code so there should never be an opportunity to find an errant HD
etc. elsewhere in the response through this step.
src/parser/meta_parser.rs
Outdated
MetaResponse::Status(s) => process_meta_response_without_data_payload(input, s), | ||
_ => Err(nom::Err::Error(nom::error::Error::new( | ||
input, | ||
nom::error::ErrorKind::Eof, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why Eof
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good callout, I think this was a holdover from the prototype code. EoF
should correspond to a buffer issue during parsing which doesn't fit here so I'm going to replace with Fail
, which is the most generic nom
error code available. The IResult
in the return value requires that the error type be par of nom::error::ErrorKind::
which is a bit annoying.
src/proto/meta_protocol.rs
Outdated
// Mode switching between increment and decrement is done with the M flag. | ||
// The default behaviour is to increment with a delta of 1. | ||
// | ||
// Using the "q" flag for quiet mode will append a no-op command to the request ("mn\r\n") so that the client |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so that the client can proceed properly in the event of a cache miss.
Using a q
flag just means that the client doesn't wait for the response so what does that have to do with a cache miss?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Errors are still returned from the memcached server when the q
flag is used. For Meta Get, EN
responses (cache miss) are suppressed, for Meta Set, HD
responses (success) are suppressed etc. We still have to use drive_recieve
to pull responses off the buffer and if they're left unmanaged then the following command can parse incorrect information, unless we clear the buffer before writing every command.
The errors do have meaning and would impact client logic down the line in the case of something like a CAS operation, so I don't really think that they can just be safely ignored.
src/proto/meta_protocol.rs
Outdated
fn write_non_set_command( | ||
&mut self, | ||
op: &str, | ||
kr: &[u8], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kr: &[u8], | |
fn write_non_set_command<K: AsRef<[u8]>>( | |
&mut self, | |
op: &str, | |
key: K, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also not a huge fan of this abstraction. I'd prefer just to explicitly keep the logic, even though I recognize it's duplicated, in each of the respective methods
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair, it's possible that there might be different optimizations to be made for each different op. I'll walk this back.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yah, also write_non_set
command feels a bit wrong to name it like this. I think having seperate instances is probably correct to start.
src/proto/meta_protocol.rs
Outdated
@@ -200,4 +212,52 @@ impl MetaProtocol for Client { | |||
.transpose(), | |||
} | |||
} | |||
|
|||
async fn meta_arithmetic<K: AsRef<[u8]>>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we make this more convenient for the end-user by adding another "value" arg? And maybe providing an explicitly increment/decrement instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have been pushing down the route of just taking a set of flags so far but I do think that this might be a more compelling argument for explicitly taking params.
Could make this change to take a delta
(with default of 1) and decrement as a boolean (default of false
, as per the proto). Other flags that should maybe be explicit params are q (so we don't have to search for it) and opaque.
I have been thinking about this in the context of multi-ops as well where you would want to force the use of either an opaque or the k flag so that we can get away from this "trusting that responses are returned in order" issue that we have with that ASCII proto. Having an opaque as a param makes this more straightforward.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We talked about this IRL and decided that a good path would be exposing explicit meta_{increment|decrement}
methods that handle adding the respective flags for each method. These can just call meta_arithmetic
(private method) under the hood with the correct flags.
value(MetaResponse::Status(Status::NotFound), tag(b"NF")), | ||
value(MetaResponse::Status(Status::NotStored), tag(b"NS")), | ||
value(MetaResponse::Status(Status::Exists), tag(b"EX")), | ||
value(MetaResponse::Status(Status::NoOp), tag(b"MN\r\n")), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does Tag always look at the first two characters? I'm not exactly sure how this is parsing things, was wondering about what happens if the key has HD in it for example.
src/proto/meta_protocol.rs
Outdated
fn write_non_set_command( | ||
&mut self, | ||
op: &str, | ||
kr: &[u8], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yah, also write_non_set
command feels a bit wrong to name it like this. I think having seperate instances is probably correct to start.
src/proto/meta_protocol.rs
Outdated
} | ||
} | ||
|
||
async fn write_non_set_command( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there something that groups this that isn't a negative? Like all retrieval
commands? I think that is how memcached organizes them https://github.com/memcached/memcached/blob/master/doc/protocol.txt#L62
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, non_set
is just awkward verbiage, and retrieval
feels slightly better but it still isn't quite right in this context since it's doing more than just a fetch. The real differentiator is just the lack of a data block / value payload being written, which only happens in the case of meta_set
.
write_meta_command_without_payload
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, nevermind. Irrelevant if I'm walking that bit of code back anyway 😅 .
fa63d5e
to
841220f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approving but pls take a look at some of the comments/questions
&mut self, | ||
key: K, | ||
is_quiet: bool, | ||
opaque: Option<&[u8]>, | ||
delta: Option<u64>, | ||
meta_flags: Option<&[&str]>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we validate that a MI
isn't passed in because I'm assuming Memcached will throw up if you pass in duplicate flags?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, memcached has a bad time if there are duplicate flags.
When checking the flags, options around this are to either:
- Raise an exception when a flag with an explicit param is used (which would lead to duplication)
- Ignore flags that should be passed in as an explicit param (could lead to unexpected behaviour on the user's end)
- Prefer explicit params over raw meta flags (i.e. use param and reject flag if both are mistakenly provided), but use raw meta flags if provided in lieu of using the optional params
Last option is easy enough to implement and is the most flexible / accomodating, and doesn't just nuke a command. All options require iteration over the &[&str]
flags provided, so I don't think there is much of a perf diff either way.
b08ffa1
to
9d4b21b
Compare
…r problematic meta flag duplication
a6be0a6
to
ead4f76
Compare
TL;DR - What are you trying to accomplish?
This PR introduces
meta_arithmetic
functionality and does some further refactoring to simplify code after patterns and common code between different methods became more clear.Details - How are you making this change? What are the effects of this change?
The Meta Arithmetic functionality introduced here covers associated mode switching for incrementing & decrementing, CAS semantics, non-standard deltas and autovivification on nonexistent keys.