Jobs
Declaring a job
The simplest way to declare a job is to create a function, and then annotate it with Batch's job
procedural macro:
# #![allow(unused_variables)] #fn main() { extern crate batch; use batch::job; #[job(name = "batch-example.say-hello")] fn say_hello(name: String) { println!("Hello {}!", name); } #}
Return value of a job
As of today, the job
macro only supports two types of return values:
- either the function return a unit value (the
()
type); - either the function return a value that implements the
IntoFuture
trait (amongst others, it is implemented for bothFuture
andResult
)
The macro not being capable of determining whether the function complies with the above requirements means that if you made a mistake, the compiler will error and the message might not be the clearest, so keep that in mind if you encounter a compile error on one of your job.
Injecting external values
More often than not, your job will need values that can't or shouldn't be transfered in their message payload. For example, you might need a connection handle to your database, or a mail API client instance, or even the contents of a configuration file. To solve this problem, Batch allow you to mark arguments as "injected". This is done by using the inject
parameter of the job
attribute:
# #![allow(unused_variables)] #fn main() { extern crate batch; use batch::job; # # struct UserRepository; # struct Mailer; #[job(name = "batch-example.send-hello", inject = [ repository, mailer ])] fn send_hello(user_id: i32, repository: UserRepository, mailer: Mailer) { # drop(user_id); # drop(repository); # drop(mailer); // ... } #}
In the example above, Batch will only put the user_id
parameter in the underlying structure, and will fetch the values for the repository
and the mailer
values when performing the job. These values are registered on the executor of the job which more often than not will be Batch's own Worker
.
Configuring the job
Changing the number of retries
By default, a job will be tried 25 times before being declared failed and dropped into a dead-letter queue. This gives you plenty of time to fix your job's implementation. If you wish to change this number, you can do so by using the retries
parameter of the job procedural macro:
# #![allow(unused_variables)] #fn main() { extern crate batch; use batch::job; #[job(name = "batch-example.send-hello", retries = 10)] fn send_hello(to: String) { println!("Hello {}", to); } #[job(name = "batch-example.send-goodbye", retries = 50)] fn send_goodbye(to: String) { println!("Goodbye {}", to); } #}
Changing the timeout for the job's execution
By default, a job is given 30 minutes to complete. If you wish to change this number, you can do so by using the timeout
parameter of the job
procedural macro with the number of seconds the job should be allowed to run:
# #![allow(unused_variables)] #fn main() { extern crate batch; use batch::job; // This job will have 1 minute to complete. #[job(name = "batch-example.send-hello", timeout = "1minute")] fn send_hello(to: String) { println!("Hello {}", to); } // This job will have 3 hours and 30 minutes to complete. #[job(name = "batch-example.send-goodbye", timeout = "3hours 30mins")] fn send_goodbye(to: String) { println!("Goodbye {}", to); } #}
The timeout parser supports the given suffixes:
nsec
,ns
-- microsecondsusec
,us
-- microsecondsmsec
,ms
-- millisecondsseconds
,second
,sec
,s
minutes
,minute
,min
,m
hours
,hour
,hr
,h
days
,day
,d
weeks
,week
,w
months
,month
,M
-- defined as 30.44 daysyears
,year
,y
-- defined as 365.25 days
Changing the job priority
By default, a job is assigned the Normal
priority. If you wish to change this, you can use the priority
parameter of the job
procedural macro with one of trivial
, low
, normal
, high
, critical
:
# #![allow(unused_variables)] #fn main() { extern crate batch; use batch::job; // This job will be assigned the `low` priority. #[job(name = "batch-example.send-hello", priority = low)] fn send_hello(to: String) { println!("Hello {}", to); } // This job will be assigned the `critical` priority. #[job(name = "batch-example.send-goodbye", priority = critical)] fn send_goodbye(to: String) { println!("Goodbye {}", to); } #}
Under the hood
Annotating a function with the job
procedural macro will do two things:
- Create a new structure deriving Serde's
Serialize
&Deserialize
traits, that stores all of the non-provided (see below) function arguments. This implies that all of the arguments must also implement Serde'sSerialize
&Deserialize
traits. More importantly, this new structure also implements Batch'sJob
trait. - Change the annotated function to return an instance of the newly defined structure (see previous bullet point), allowing for easy scheduling of the job.
Get or set the underlying struct of a job
When invoked, job
procedural macro will generate a new structure declaration containing the arguments for the job, and implementing the Job
trait. The name of this structure is the same as the function is comes from. This is made possible by the fact that in Rust, structures and functions don't share the same namespace:
# #![allow(unused_variables)] #fn main() { extern crate batch; use batch::job; #[job(name = "batch-example.say-hello")] fn say_hello(name: String) { // ... } // Would generate code roughly equivalent to: # mod generated_do_not_copy_paste_this_please { struct say_hello { name: String } # } #}
Hopefully, this simple naming scheme should not conflict with already existing code. If you ever happen to be in this situation, Batch allows you to set the name of the structure that will be generated, by using the wrapper
parameter:
# #![allow(unused_variables)] #fn main() { extern crate batch; use batch::job; #[job(name = "batch-example.say-hello", wrapper = MySuperAwesomeJob)] fn say_hello(name: String) { // ... } // Would generate code roughly equivalent to: # mod generated_do_not_copy_paste_this_please { struct MySuperAwesomeJob { name: String } # } #}