From 545245abc274898ba86bbeeaeff06de7d90dca65 Mon Sep 17 00:00:00 2001 From: David Laban Date: Tue, 12 May 2020 22:58:06 +0100 Subject: [PATCH 1/3] have a go at making goose async It's a bit of a shitshow because of lifetimes, and the fact that async functions can't be used as function pointers (because the return value is not sized predictably in a dynamic context). This thread was really hepful to me: https://users.rust-lang.org/t/how-to-store-async-function-pointer/38343/4 All that's left to do is: * Fix the doctests * Actually try out the examples and see if they are still working/performant. * Go hunting for places where explicit threads are used which could be turned into tasks. --- Cargo.toml | 2 + examples/drupal_loadtest.rs | 41 +++++++----- examples/simple.rs | 14 +++- src/goose.rs | 130 ++++++++++++++++++++---------------- src/lib.rs | 35 +++++++++- 5 files changed, 145 insertions(+), 77 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8be52691..ef67ea9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ license = "Apache-2.0" ctrlc = "3.1" http = "0.2" log = "0.4" +macro_rules_attribute = "*" num_cpus = "1.0" num-format = "0.4" rand = "0.7" @@ -23,6 +24,7 @@ regex = "1" reqwest = { version = "0.10", features = ["blocking", "cookies", "json"] } structopt = "0.3" simplelog = "0.7" +tokio = "0.2.20" url = "2.1" [dev-dependencies] diff --git a/examples/drupal_loadtest.rs b/examples/drupal_loadtest.rs index a755d8f6..b3ab93f9 100644 --- a/examples/drupal_loadtest.rs +++ b/examples/drupal_loadtest.rs @@ -22,9 +22,13 @@ //! See the License for the specific language governing permissions and //! limitations under the License. +#[macro_use] +extern crate macro_rules_attribute; + use rand::Rng; use regex::Regex; +use goose::dyn_async; use goose::GooseState; use goose::goose::{GooseTaskSet, GooseClient, GooseTask}; @@ -72,13 +76,14 @@ fn main() { } /// View the front page. -fn drupal_loadtest_front_page(client: &mut GooseClient) { - let response = client.get("/"); +#[macro_rules_attribute(dyn_async!)] +async fn drupal_loadtest_front_page<'fut>(client: &'fut mut GooseClient) -> () { + let response = client.get("/").await; // Grab some static assets from the front page. match response { Ok(r) => { - match r.text() { + match r.text().await { Ok(t) => { let re = Regex::new(r#"src="(.*?)""#).unwrap(); for url in re.captures_iter(&t) { @@ -101,23 +106,26 @@ fn drupal_loadtest_front_page(client: &mut GooseClient) { } /// View a node from 1 to 10,000, created by preptest.sh. -fn drupal_loadtest_node_page(client: &mut GooseClient) { +#[macro_rules_attribute(dyn_async!)] +async fn drupal_loadtest_node_page<'fut>(client: &'fut mut GooseClient) -> () { let nid = rand::thread_rng().gen_range(1, 10_000); - let _response = client.get(format!("/node/{}", &nid).as_str()); + let _response = client.get(format!("/node/{}", &nid).as_str()).await; } /// View a profile from 2 to 5,001, created by preptest.sh. -fn drupal_loadtest_profile_page(client: &mut GooseClient) { +#[macro_rules_attribute(dyn_async!)] +async fn drupal_loadtest_profile_page<'fut>(client: &'fut mut GooseClient) -> () { let uid = rand::thread_rng().gen_range(2, 5_001); - let _response = client.get(format!("/user/{}", &uid).as_str()); + let _response = client.get(format!("/user/{}", &uid).as_str()).await; } /// Log in. -fn drupal_loadtest_login(client: &mut GooseClient) { - let response = client.get("/user"); +#[macro_rules_attribute(dyn_async!)] +async fn drupal_loadtest_login<'fut>(client: &'fut mut GooseClient) -> () { + let response = client.get("/user").await; match response { Ok(r) => { - match r.text() { + match r.text().await { Ok(html) => { let re = Regex::new( r#"name="form_build_id" value=['"](.*?)['"]"#).unwrap(); let form_build_id = match re.captures(&html) { @@ -155,12 +163,13 @@ fn drupal_loadtest_login(client: &mut GooseClient) { } /// Post a comment. -fn drupal_loadtest_post_comment(client: &mut GooseClient) { - let nid = rand::thread_rng().gen_range(1, 10_000); - let response = client.get(format!("/node/{}", &nid).as_str()); +#[macro_rules_attribute(dyn_async!)] +async fn drupal_loadtest_post_comment<'fut>(client: &'fut mut GooseClient) -> () { + let nid: i32 = rand::thread_rng().gen_range(1, 10_000); + let response = client.get(format!("/node/{}", &nid).as_str()).await; match response { Ok(r) => { - match r.text() { + match r.text().await { Ok(html) => { // Extract the form_build_id from the user login form. let re = Regex::new( r#"name="form_build_id" value=['"](.*?)['"]"#).unwrap(); @@ -205,10 +214,10 @@ fn drupal_loadtest_post_comment(client: &mut GooseClient) { ("op", "Save"), ]; let request_builder = client.goose_post(format!("/comment/reply/{}", &nid).as_str()); - let response = client.goose_send(request_builder.form(¶ms)); + let response = client.goose_send(request_builder.form(¶ms)).await; match response { Ok(r) => { - match r.text() { + match r.text().await { Ok(html) => { if !html.contains(&comment_body) { eprintln!("no comment showed up after posting to comment/reply/{}", &nid); diff --git a/examples/simple.rs b/examples/simple.rs index 2a7bd734..dfd51b61 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -17,6 +17,11 @@ //! See the License for the specific language governing permissions and //! limitations under the License. + +#[macro_use] +extern crate macro_rules_attribute; + +use goose::dyn_async; use goose::GooseState; use goose::goose::{GooseTaskSet, GooseClient, GooseTask}; @@ -38,7 +43,8 @@ fn main() { /// Demonstrates how to log in when a client starts. We flag this task as an /// on_start task when registering it above. This means it only runs one time /// per client, when the client thread first starts. -fn website_task_login(client: &mut GooseClient) { +#[macro_rules_attribute(dyn_async!)] +async fn website_task_login<'fut>(client: &'fut mut GooseClient) -> () { let request_builder = client.goose_post("/login"); // https://docs.rs/reqwest/*/reqwest/blocking/struct.RequestBuilder.html#method.form let params = [("username", "test_user"), ("password", "")]; @@ -46,11 +52,13 @@ fn website_task_login(client: &mut GooseClient) { } /// A very simple task that simply loads the front page. -fn website_task_index(client: &mut GooseClient) { +#[macro_rules_attribute(dyn_async!)] +async fn website_task_index<'fut>(client: &'fut mut GooseClient) -> () { let _response = client.get("/"); } /// A very simple task that simply loads the about page. -fn website_task_about(client: &mut GooseClient) { +#[macro_rules_attribute(dyn_async!)] +async fn website_task_about<'fut>(client: &'fut mut GooseClient) -> () { let _response = client.get("/about/"); } diff --git a/src/goose.rs b/src/goose.rs index 6a2239e9..3f252651 100644 --- a/src/goose.rs +++ b/src/goose.rs @@ -72,7 +72,7 @@ //! let mut a_task = GooseTask::new(task_function); //! //! /// A very simple task that simply loads the front page. -//! fn task_function(client: &mut GooseClient) { +//! fn task_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/"); //! } //! ``` @@ -88,7 +88,7 @@ //! let mut a_task = GooseTask::new(task_function).set_name("a"); //! //! /// A very simple task that simply loads the front page. -//! fn task_function(client: &mut GooseClient) { +//! fn task_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/"); //! } //! ``` @@ -106,12 +106,12 @@ //! let mut b_task = GooseTask::new(b_task_function).set_weight(3); //! //! /// A very simple task that simply loads the "a" page. -//! fn a_task_function(client: &mut GooseClient) { +//! fn a_task_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/a/"); //! } //! //! /// Another very simple task that simply loads the "b" page. -//! fn b_task_function(client: &mut GooseClient) { +//! fn b_task_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/b/"); //! } //! ``` @@ -134,17 +134,17 @@ //! let mut c_task = GooseTask::new(c_task_function); //! //! /// A very simple task that simply loads the "a" page. -//! fn a_task_function(client: &mut GooseClient) { +//! fn a_task_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/a/"); //! } //! //! /// Another very simple task that simply loads the "b" page. -//! fn b_task_function(client: &mut GooseClient) { +//! fn b_task_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/b/"); //! } //! //! /// Another very simple task that simply loads the "c" page. -//! fn c_task_function(client: &mut GooseClient) { +//! fn c_task_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/c/"); //! } //! ``` @@ -163,7 +163,7 @@ //! let mut a_task = GooseTask::new(a_task_function).set_sequence(1).set_on_start(); //! //! /// A very simple task that simply loads the "a" page. -//! fn a_task_function(client: &mut GooseClient) { +//! fn a_task_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/a/"); //! } //! ``` @@ -182,7 +182,7 @@ //! let mut b_task = GooseTask::new(b_task_function).set_sequence(2).set_on_stop(); //! //! /// Another very simple task that simply loads the "b" page. -//! fn b_task_function(client: &mut GooseClient) { +//! fn b_task_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/b/"); //! } //! ``` @@ -210,7 +210,7 @@ //! let mut task = GooseTask::new(get_function); //! //! /// A very simple task that makes a GET request. -//! fn get_function(client: &mut GooseClient) { +//! fn get_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.get("/path/to/foo/"); //! } //! ``` @@ -231,7 +231,7 @@ //! let mut task = GooseTask::new(post_function); //! //! /// A very simple task that makes a POST request. -//! fn post_function(client: &mut GooseClient) { +//! fn post_function<'fut>(client: &'fut mut GooseClient) { //! let _response = client.post("/path/to/foo/", "string value to post".to_string()); //! } //! ``` @@ -253,11 +253,11 @@ //! limitations under the License. use std::collections::{HashMap, BTreeMap}; -use std::time::Instant; +use std::{pin::Pin, time::Instant, future::Future}; use http::StatusCode; use http::method::Method; -use reqwest::blocking::{Client, Response, RequestBuilder}; +use reqwest::{Client, Response, RequestBuilder}; use reqwest::Error; use url::Url; @@ -327,7 +327,7 @@ impl GooseTaskSet { /// example_tasks.register_task(GooseTask::new(a_task_function)); /// /// /// A very simple task that simply loads the "a" page. - /// fn a_task_function(client: &mut GooseClient) { + /// fn a_task_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/a/"); /// } /// ``` @@ -647,7 +647,7 @@ impl GooseClient { /// let mut task = GooseTask::new(get_function); /// /// /// A very simple task that makes a GET request. - /// fn get_function(client: &mut GooseClient) { + /// fn get_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.set_request_name("foo").get("/path/to/foo"); /// } /// ``` @@ -660,7 +660,7 @@ impl GooseClient { /// let mut task = GooseTask::new(get_function); /// /// /// A very simple task that makes a GET request. - /// fn get_function(client: &mut GooseClient) { + /// fn get_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.set_request_name("foo").get("/path/to/foo"); /// let _response = client.get("/path/to/foo"); /// } @@ -754,13 +754,13 @@ impl GooseClient { /// let mut task = GooseTask::new(get_function); /// /// /// A very simple task that makes a GET request. - /// fn get_function(client: &mut GooseClient) { + /// fn get_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/path/to/foo/"); /// } /// ``` - pub fn get(&mut self, path: &str) -> Result { + pub async fn get(&mut self, path: &str) -> Result { let request_builder = self.goose_get(path); - let response = self.goose_send(request_builder); + let response = self.goose_send(request_builder).await; response } @@ -779,13 +779,13 @@ impl GooseClient { /// let mut task = GooseTask::new(post_function); /// /// /// A very simple task that makes a POST request. - /// fn post_function(client: &mut GooseClient) { + /// fn post_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.post("/path/to/foo/", "BODY BEING POSTED".to_string()); /// } /// ``` - pub fn post(&mut self, path: &str, body: String) -> Result { + pub async fn post(&mut self, path: &str, body: String) -> Result { let request_builder = self.goose_post(path).body(body); - let response = self.goose_send(request_builder); + let response = self.goose_send(request_builder).await; response } @@ -804,13 +804,13 @@ impl GooseClient { /// let mut task = GooseTask::new(head_function); /// /// /// A very simple task that makes a HEAD request. - /// fn head_function(client: &mut GooseClient) { + /// fn head_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.head("/path/to/foo/"); /// } /// ``` - pub fn head(&mut self, path: &str) -> Result { + pub async fn head(&mut self, path: &str) -> Result { let request_builder = self.goose_head(path); - let response = self.goose_send(request_builder); + let response = self.goose_send(request_builder).await; response } @@ -829,13 +829,13 @@ impl GooseClient { /// let mut task = GooseTask::new(delete_function); /// /// /// A very simple task that makes a DELETE request. - /// fn delete_function(client: &mut GooseClient) { + /// fn delete_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.delete("/path/to/foo/"); /// } /// ``` - pub fn delete(&mut self, path: &str) -> Result { + pub async fn delete(&mut self, path: &str) -> Result { let request_builder = self.goose_delete(path); - let response = self.goose_send(request_builder); + let response = self.goose_send(request_builder).await; response } @@ -853,7 +853,7 @@ impl GooseClient { /// /// /// A simple task that makes a GET request, exposing the Reqwest /// /// request builder. - /// fn get_function(client: &mut GooseClient) { + /// fn get_function<'fut>(client: &'fut mut GooseClient) { /// let request_builder = client.goose_get("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } @@ -877,7 +877,7 @@ impl GooseClient { /// /// /// A simple task that makes a POST request, exposing the Reqwest /// /// request builder. - /// fn post_function(client: &mut GooseClient) { + /// fn post_function<'fut>(client: &'fut mut GooseClient) { /// let request_builder = client.goose_post("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } @@ -901,7 +901,7 @@ impl GooseClient { /// /// /// A simple task that makes a HEAD request, exposing the Reqwest /// /// request builder. - /// fn head_function(client: &mut GooseClient) { + /// fn head_function<'fut>(client: &'fut mut GooseClient) { /// let request_builder = client.goose_head("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } @@ -925,7 +925,7 @@ impl GooseClient { /// /// /// A simple task that makes a PUT request, exposing the Reqwest /// /// request builder. - /// fn put_function(client: &mut GooseClient) { + /// fn put_function<'fut>(client: &'fut mut GooseClient) { /// let request_builder = client.goose_put("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } @@ -949,7 +949,7 @@ impl GooseClient { /// /// /// A simple task that makes a PUT request, exposing the Reqwest /// /// request builder. - /// fn patch_function(client: &mut GooseClient) { + /// fn patch_function<'fut>(client: &'fut mut GooseClient) { /// let request_builder = client.goose_patch("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } @@ -973,7 +973,7 @@ impl GooseClient { /// /// /// A simple task that makes a DELETE request, exposing the Reqwest /// /// request builder. - /// fn delete_function(client: &mut GooseClient) { + /// fn delete_function<'fut>(client: &'fut mut GooseClient) { /// let request_builder = client.goose_delete("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } @@ -1000,12 +1000,12 @@ impl GooseClient { /// /// /// A simple task that makes a GET request, exposing the Reqwest /// /// request builder. - /// fn get_function(client: &mut GooseClient) { + /// fn get_function<'fut>(client: &'fut mut GooseClient) { /// let request_builder = client.goose_get("/path/to/foo"); /// let response = client.goose_send(request_builder); /// } /// ``` - pub fn goose_send(&mut self, request_builder: RequestBuilder) -> Result { + pub async fn goose_send(&mut self, request_builder: RequestBuilder) -> Result { let started = Instant::now(); let request = match request_builder.build() { Ok(r) => r, @@ -1027,7 +1027,7 @@ impl GooseClient { self.previous_request_name = self.request_name.clone(); // Make the actual request. - let response = self.client.execute(request); + let response = self.client.execute(request).await; let elapsed = started.elapsed(); if self.config.print_stats { @@ -1111,7 +1111,7 @@ impl GooseClient { /// let mut task = GooseTask::new(get_function); /// /// /// A simple task that makes a GET request. - /// fn get_function(client: &mut GooseClient) { + /// fn get_function<'fut>(client: &'fut mut GooseClient) { /// let response = client.get("/404"); /// match &response { /// Ok(r) => { @@ -1147,7 +1147,7 @@ impl GooseClient { /// /// let mut task = GooseTask::new(loadtest_index_page); /// - /// fn loadtest_index_page(client: &mut GooseClient) { + /// fn loadtest_index_page<'fut>(client: &'fut mut GooseClient) { /// let response = client.set_request_name("index").get("/"); /// // Extract the response Result. /// match response { @@ -1185,6 +1185,20 @@ impl GooseClient { } } + +// TODO: https://users.rust-lang.org/t/how-to-store-async-function-pointer/38343/4 +// * make a macro to mark dyn async functions +// * mark all callbacks with this macro when defining them +// * pass them into GooseTask::new() as function pointers +type AsyncTaskCallbackFunction = + fn (&'_ mut GooseClient) -> + Pin // future API / pollable + + Send // required by non-single-threaded executors + + '_ // may capture `client`, which is only valid for the `'_` lifetime + >> +; + /// An individual task within a `GooseTaskSet`. #[derive(Clone)] pub struct GooseTask { @@ -1201,10 +1215,10 @@ pub struct GooseTask { /// A flag indicating that this task runs when the client stops. pub on_stop: bool, /// A required function that is executed each time this task runs. - pub function: fn(&mut GooseClient), + pub function: AsyncTaskCallbackFunction, } impl GooseTask { - pub fn new(function: fn(&mut GooseClient)) -> Self { + pub fn new(function: AsyncTaskCallbackFunction) -> Self { trace!("new task"); let task = GooseTask { tasks_index: usize::max_value(), @@ -1230,7 +1244,7 @@ impl GooseTask { /// /// GooseTask::new(my_task_function).set_name("foo"); /// - /// fn my_task_function(client: &mut GooseClient) { + /// fn my_task_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/"); /// } /// ``` @@ -1256,7 +1270,7 @@ impl GooseTask { /// /// GooseTask::new(my_on_start_function).set_on_start(); /// - /// fn my_on_start_function(client: &mut GooseClient) { + /// fn my_on_start_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/"); /// } /// ``` @@ -1282,7 +1296,7 @@ impl GooseTask { /// /// GooseTask::new(my_on_stop_function).set_on_stop(); /// - /// fn my_on_stop_function(client: &mut GooseClient) { + /// fn my_on_stop_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/"); /// } /// ``` @@ -1302,7 +1316,7 @@ impl GooseTask { /// /// GooseTask::new(task_function).set_weight(3); /// - /// fn task_function(client: &mut GooseClient) { + /// fn task_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/"); /// } /// ``` @@ -1335,15 +1349,15 @@ impl GooseTask { /// let runs_second = GooseTask::new(second_task_function).set_sequence(5835); /// let runs_last = GooseTask::new(third_task_function); /// - /// fn first_task_function(client: &mut GooseClient) { + /// fn first_task_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/1"); /// } /// - /// fn second_task_function(client: &mut GooseClient) { + /// fn second_task_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/2"); /// } /// - /// fn third_task_function(client: &mut GooseClient) { + /// fn third_task_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/3"); /// } /// ``` @@ -1359,15 +1373,15 @@ impl GooseTask { /// let runs_second = GooseTask::new(second_task_function_a).set_sequence(2); /// let also_runs_second = GooseTask::new(second_task_function_b).set_sequence(2).set_weight(2); /// - /// fn first_task_function(client: &mut GooseClient) { + /// fn first_task_function<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/1"); /// } /// - /// fn second_task_function_a(client: &mut GooseClient) { + /// fn second_task_function_a<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/2a"); /// } /// - /// fn second_task_function_b(client: &mut GooseClient) { + /// fn second_task_function_b<'fut>(client: &'fut mut GooseClient) { /// let _response = client.get("/2b"); /// } /// ``` @@ -1384,16 +1398,19 @@ impl GooseTask { #[cfg(test)] mod tests { use super::*; + use crate::dyn_async; #[test] fn goose_task_set() { // Simplistic test task functions. - fn test_task_function_a(client: &mut GooseClient) { - let _response = client.get("/a/"); + #[macro_rules_attribute(dyn_async!)] + async fn test_task_function_a<'fut>(client: &'fut mut GooseClient) -> () { + let _response = client.get("/a/").await; } - fn test_task_function_b(client: &mut GooseClient) { - let _response = client.get("/b/"); + #[macro_rules_attribute(dyn_async!)] + async fn test_task_function_b<'fut>(client: &'fut mut GooseClient) -> () { + let _response = client.get("/b/").await; } let mut task_set = GooseTaskSet::new("foo"); @@ -1485,7 +1502,8 @@ mod tests { #[test] fn goose_task() { // Simplistic test task functions. - fn test_task_function_a(client: &mut GooseClient) { + #[macro_rules_attribute(dyn_async!)] + async fn test_task_function_a <'fut>(client: &'fut mut GooseClient) -> () { let _response = client.get("/a/"); } diff --git a/src/lib.rs b/src/lib.rs index cc44b6d4..388b5c88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -274,6 +274,9 @@ #[macro_use] extern crate log; +#[cfg(test)] +#[macro_use] +extern crate macro_rules_attribute; //#[macro_use] //extern crate goose_codegen; @@ -302,6 +305,29 @@ use url::Url; use crate::goose::{GooseTaskSet, GooseTask, GooseClient, GooseClientMode, GooseClientCommand, GooseRequest}; +// FIXME: For some reason this borks if you don't specify -> () +#[macro_export] +macro_rules! dyn_async {( + $( #[$attr:meta] )* // includes doc strings + $pub:vis + async + fn $fname:ident<$lt:lifetime> ( $($args:tt)* ) $(-> $Ret:ty)? + { + $($body:tt)* + } +) => ( + $( #[$attr] )* + #[allow(unused_parens)] + $pub + fn $fname<$lt> ( $($args)* ) -> ::std::pin::Pin<::std::boxed::Box< + dyn ::std::future::Future + + ::std::marker::Send + $lt + >> + { + ::std::boxed::Box::pin(async move { $($body)* }) + } +)} + /// Internal global state for load test. #[derive(Clone)] pub struct GooseState { @@ -557,7 +583,12 @@ impl GooseState { /// let _response = client.get("/bar"); /// } /// ``` - pub fn execute(mut self) { + pub fn execute(self) { + let mut rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(self.execute_async()) + } + + async fn execute_async(mut self) { // At least one task set is required. if self.task_sets.len() <= 0 { error!("No task sets defined in goosefile."); @@ -1249,4 +1280,4 @@ mod test { assert_eq!(is_valid_host("foo://example.com"), true); assert_eq!(is_valid_host("file:///path/to/file"), true); } -} \ No newline at end of file +} From 6836ba7edc71d1db214c8b16342363ba3b9ad117 Mon Sep 17 00:00:00 2001 From: David Laban Date: Wed, 13 May 2020 06:53:30 +0100 Subject: [PATCH 2/3] make it actually do something --- Cargo.toml | 2 +- examples/simple.rs | 6 +++--- src/client.rs | 10 +++++----- src/lib.rs | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ef67ea9b..f96077e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ regex = "1" reqwest = { version = "0.10", features = ["blocking", "cookies", "json"] } structopt = "0.3" simplelog = "0.7" -tokio = "0.2.20" +tokio = { version = "0.2.20", features = ["rt-core", "time"] } url = "2.1" [dev-dependencies] diff --git a/examples/simple.rs b/examples/simple.rs index dfd51b61..44608ff1 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -48,17 +48,17 @@ async fn website_task_login<'fut>(client: &'fut mut GooseClient) -> () { let request_builder = client.goose_post("/login"); // https://docs.rs/reqwest/*/reqwest/blocking/struct.RequestBuilder.html#method.form let params = [("username", "test_user"), ("password", "")]; - let _response = client.goose_send(request_builder.form(¶ms)); + let _response = client.goose_send(request_builder.form(¶ms)).await; } /// A very simple task that simply loads the front page. #[macro_rules_attribute(dyn_async!)] async fn website_task_index<'fut>(client: &'fut mut GooseClient) -> () { - let _response = client.get("/"); + let _response = client.get("/").await; } /// A very simple task that simply loads the about page. #[macro_rules_attribute(dyn_async!)] async fn website_task_about<'fut>(client: &'fut mut GooseClient) -> () { - let _response = client.get("/about/"); + let _response = client.get("/about/").await; } diff --git a/src/client.rs b/src/client.rs index 40bc2623..674bc1b9 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,11 +4,11 @@ use std::sync::mpsc; use rand::thread_rng; use rand::seq::SliceRandom; use rand::Rng; -use std::{thread, time}; +use std::time; use crate::goose::{GooseTaskSet, GooseClient, GooseClientMode, GooseClientCommand}; -pub fn client_main( +pub async fn client_main( thread_number: usize, thread_task_set: GooseTaskSet, mut thread_client: GooseClient, @@ -66,7 +66,7 @@ pub fn client_main( thread_client.task_request_name = Some(thread_task_name.to_string()); } // Invoke the task function. - function(&mut thread_client); + function(&mut thread_client).await; // Prepare to sleep for a random value from min_wait to max_wait. let wait_time: usize; @@ -106,7 +106,7 @@ pub fn client_main( if thread_continue && thread_client.max_wait > 0 { let sleep_duration = time::Duration::from_secs(1); debug!("client {} from {} sleeping {:?} second...", thread_number, thread_task_set.name, sleep_duration); - thread::sleep(sleep_duration); + tokio::time::delay_for(sleep_duration).await; slept += 1; if slept > wait_time { in_sleep_loop = false; @@ -144,4 +144,4 @@ pub fn client_main( // Do our final sync before we exit. thread_sender.send(thread_client.clone()).unwrap(); info!("exiting client {} from {}...", thread_number, thread_task_set.name); -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 388b5c88..d124c22f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -704,9 +704,9 @@ impl GooseState { let thread_number = self.active_clients + 1; // Launch a new client. - let client = thread::spawn(move || { + let client = tokio::spawn( client::client_main(thread_number, thread_task_set, thread_client, thread_receiver, thread_sender) - }); + ); clients.push(client); self.active_clients += 1; @@ -846,7 +846,7 @@ impl GooseState { } info!("waiting for clients to exit"); for client in clients { - let _ = client.join(); + let _ = client.await; } debug!("all clients exited"); From 57f23e4c9c8c856b21e0c81af1b94f1f3acf855b Mon Sep 17 00:00:00 2001 From: David Laban Date: Wed, 13 May 2020 07:02:55 +0100 Subject: [PATCH 3/3] another missing .await --- examples/drupal_loadtest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/drupal_loadtest.rs b/examples/drupal_loadtest.rs index b3ab93f9..a23ff558 100644 --- a/examples/drupal_loadtest.rs +++ b/examples/drupal_loadtest.rs @@ -148,7 +148,7 @@ async fn drupal_loadtest_login<'fut>(client: &'fut mut GooseClient) -> () { ("op", "Log+in"), ]; let request_builder = client.goose_post("/user"); - let _response = client.goose_send(request_builder.form(¶ms)); + let _response = client.goose_send(request_builder.form(¶ms)).await; // @TODO: verify that we actually logged in. } Err(e) => {