-
Notifications
You must be signed in to change notification settings - Fork 15
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
Mutate API #87
base: main
Are you sure you want to change the base?
Mutate API #87
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files
|
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.
Haven't gotten all the way through this, but racked up a few comments so figured I'd leave them with you!
src/lib.rs
Outdated
pub use crate::proto::{ | ||
Client, Connection, DataSnapshot, FaktoryState, Job, JobBuilder, JobId, Reconnect, | ||
ServerSnapshot, WorkerId, | ||
Client, Connection, DataSnapshot, FaktoryState, Job, JobBuilder, JobId, MutationFilter, | ||
MutationFilterBuilder, MutationTarget, Reconnect, ServerSnapshot, WorkerId, | ||
}; |
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.
This is starting to be a lot of top-level exports. How do you feel about starting to group some of them the way we've done with the pub mod ent
below? Like pub mod mutate
for example? Then we can also rename them to avoid the Mutation
name prefix.
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.
added namespace and - indeed - prefix no longer needed in the public api
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.
though after some refactoring there are only two members left in that namespace :)
Ok(w.write_all(b"INFO\r\n").await?) | ||
} | ||
} | ||
self_to_cmd!(Info); |
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.
Nice, I like the macro expansion here.
src/proto/single/cmd.rs
Outdated
pub(crate) struct MutationAction<'a> { | ||
pub(crate) cmd: MutationType, | ||
pub(crate) target: MutationTarget, | ||
#[serde(skip_serializing_if = "filter_is_empty")] |
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 an empty-but-set mutation filter actually == to an unset filter? In some systems, the two have different semantics (usually, unset means "use the default").
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.
A nil filter and an empty one are treated in the common manner - as if we were matching everything in the targeted queue (they are wildcards effectively). Added a comment.
I see what you mean, like what if the default behavior changes? well, as one put it, where possible, your interface should be intuitive enough that if the user has to guess, they usually guess correctly. Looking at the filter fields (jids, regexp, jobtypes) I am guessing that leaving those empty should be a considered a wildcard or should cause an error encouraging me to be specific. In the same manner, omitting the filter - I am guessing - should be either a wildcard or (yet again) should error 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.
P.S.: to give an example of a broken law of least astonishment: in case of an unset filter, we system would be targeting the jobs whose jobtype is a string literal "general" 😢
src/proto/client/mutation.rs
Outdated
/// This method will immediately move the jobs from the targeted set (see [`MutationTarget`]) | ||
/// to their queues. This will apply to the jobs satisfying the [`filter`](crate::MutationFilter). |
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 having a hard time following what this operation actually does. For example, with MutationTarget::Scheduled
, what does it mean for the jobs to "immediately be moved to their queues"? They're already in queues?
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.
The scheduled set is not a queue, it’s time-sorted data structure in Redis. The job is moved from that structure into its queue for a worker to fetch.
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 they are not in queues, rather in dedicated sets where they are "pending" basically.
Updated the docs and also added an example scenario
P.S. Left the draft reply open in the browser tab and did not see Mike's comment when publishing. "Multiversion concurrency control" made my comment look stupid 😓
src/proto/single/mutation.rs
Outdated
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] | ||
#[serde(rename_all = "lowercase")] | ||
#[non_exhaustive] | ||
pub enum MutationTarget { |
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 this actually specific to mutations? Feels like a general purpose job set specifier to me.
It's also weird to me that the "mutation target" isn't just another filter, given that seems to be the function it has.
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.
-
the job sets are only used in mutation API context and also declared in the
mutate
module in the Go bindings. As per another thread, we decided to remove theMutation
prefix from the constucts in themutation
namespace, so it's now simplyTarget
. But I see what you mean this could be in theory simplyJobSet
orJobSetKind
. -
well, this where I was hesitant for a while, but decided to follow the Go bindings way. open to revisiting this place though
src/proto/client/mutation.rs
Outdated
/// client.requeue(MutationTarget::Retries, &filter).await.unwrap(); | ||
/// # }); | ||
/// ``` | ||
pub async fn requeue<'a, F>(&mut self, target: MutationTarget, filter: F) -> Result<(), Error> |
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.
The argument name target
makes me think that the jobs will be moved to this, but I don't think that's the case. Isn't it more like source
or candidate_job_set
or something?
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.
At the time of writing I was in the Mutate API flow and my idea was that we were mutating those dedicated sets and they were the mutation target, but when I now revisit this bit I see that it's confusing indeed.
I love source
as parameter name, but then I will need to rename the Target
to JobSet
, otherwise we will get source: Target
😃 in the signature.
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.
On the second thought, candidate_job_set
looks more universal since we also have such methods as Client::discard
and Client::kill
and Client::clear
src/proto/single/mutation.rs
Outdated
pub kind: Option<&'a str>, | ||
|
||
/// Ids of jobs to target. | ||
#[serde(skip_serializing_if = "Option::is_none")] |
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 it be send if empty?
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.
No need to. Even though is does not change the outcome, we should be consistent. So I added a utility trait called Empty
(only for usage within the crate) so that we can do:
#[serde(skip_serializing_if = "Empty::is_empty")]
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.
Also added a few unit tests for Filter
and MutationAction
for visualization purposes solely
src/proto/single/mutation.rs
Outdated
/// As of Faktory version 1.9.2, if [`MutationFilter::pattern`] and/or [`MutationFilter::kind`] | ||
/// is specified, the values in [`MutationFilter::jids`] will not be taken into account by the | ||
/// server. If you want to filter by `jids`, make sure to leave other fields of the filter empty | ||
/// or use dedicated methods like [`Client::requeue_by_ids`]. |
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 that's the case, I wonder if we shouldn't derive builder and instead just have specific constructors (for now) for the combinations that do work. What do you think?
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 think, this is the right call. If I do not read the docs (which I definitely can) and specify both kind
and jids
on the builder (thinking I am helping the server find jobs in question faster), I am getting into a situation where all the jobs of that kind in the targeted set are affected. If the server ignored pattern
and kind
when jids
are there (which is a trivial fix as Mike points out here, there would be no harm actually.
So I think the constructors will be:
Filter::by_ids;
Filter::by_kind;
Filter::by_pattern;
Filter::by_kind_and_pattern;
And the fields on that struct should be made private apparently.
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.
went with Filter::from_<attr>
rather than Filter::by_<attr>
. the latter is better for a verb I think, like Client::filter_by_ids
.
Co-authored-by: Jon Gjengset <[email protected]>
Co-authored-by: Jon Gjengset <[email protected]>
* Make Failure public * Update changelog * Get more info from panicking * Check different ways a job can fail * Add test clean up * Account for re-queued jobs * Check job id * Collect all kinds of handler failure * Add Job::failure_message. Check all error messages * Add to CHANGELOG.md. Fix clippy warning * Update tests/real/community.rs Co-authored-by: Jon Gjengset <[email protected]> * Update src/proto/single/mod.rs Co-authored-by: Jon Gjengset <[email protected]> * Fix docs to Failure * Nuke Job::failure_message * Nuke Job::failure_message * Restore comment in test_panic_and_errors_in_handler test * Add to the tests docs * Do not make Failure::kind pub --------- Co-authored-by: Jon Gjengset <[email protected]>
Sorry it took me a while to iterate on this PR - got some things to do in the Please have a look at what we got. |
addresses #61
this should also "unblock" #88
This change is