diff --git a/meilisearch/src/search_queue.rs b/meilisearch/src/search_queue.rs index b677f81a4..0dd2abf17 100644 --- a/meilisearch/src/search_queue.rs +++ b/meilisearch/src/search_queue.rs @@ -47,14 +47,21 @@ impl SearchQueue { loop { tokio::select! { search_request = receive_new_searches.recv() => { + // this unwrap is safe because we're sure the `SearchQueue` still lives somewhere in actix-web let search_request = search_request.unwrap(); if searches_running < usize::from(parallelism) && queue.is_empty() { searches_running += 1; // if the search requests die it's not a hard error on our side let _ = search_request.send(Permit { sender: sender.clone() }); continue; - } - if queue.len() >= capacity { + } else if capacity == 0 { + // in the very specific case where we have a capacity of zero + // we must refuse the request straight away without going through + // the queue stuff. + drop(search_request); + continue; + + } else if queue.len() >= capacity { let remove = rng.gen_range(0..queue.len()); let thing = queue.swap_remove(remove); // this will drop the channel and notify the search that it won't be processed drop(thing); diff --git a/meilisearch/tests/search/search_queue.rs b/meilisearch/tests/search/search_queue.rs index 8dca2ab31..6bb8d2169 100644 --- a/meilisearch/tests/search/search_queue.rs +++ b/meilisearch/tests/search/search_queue.rs @@ -131,3 +131,40 @@ async fn search_request_crashes_while_holding_permits() { .expect("I should get a permit straight away") .unwrap(); } + +#[actix_rt::test] +async fn works_with_capacity_of_zero() { + let queue = Arc::new(SearchQueue::new(0, NonZeroUsize::new(1).unwrap())); + + // First, use the whole capacity of the + let permit1 = tokio::time::timeout(Duration::from_secs(1), queue.try_get_search_permit()) + .await + .expect("I should get a permit straight away") + .unwrap(); + + // then we should get an error if we try to register a second search request. + let permit2 = tokio::time::timeout(Duration::from_secs(1), queue.try_get_search_permit()) + .await + .expect("I should get a result straight away"); + + let err = meilisearch_types::error::ResponseError::from(permit2.unwrap_err()); + let http_response = err.error_response(); + snapshot!(format!("{:?}", http_response.headers()), @r###"HeaderMap { inner: {"content-type": Value { inner: ["application/json"] }, "retry-after": Value { inner: ["10"] }} }"###); + + let err = serde_json::to_string_pretty(&err).unwrap(); + snapshot!(err, @r###" + { + "message": "Too many search requests running at the same time: 0. Retry after 10s.", + "code": "too_many_search_requests", + "type": "system", + "link": "https://docs.meilisearch.com/errors#too_many_search_requests" + } + "###); + + drop(permit1); + // After dropping the first permit we should be able to get a new permit + let _permit3 = tokio::time::timeout(Duration::from_secs(1), queue.try_get_search_permit()) + .await + .expect("I should get a permit straight away") + .unwrap(); +}