diff --git a/Cargo.lock b/Cargo.lock index a596b51..989f16c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -305,6 +305,7 @@ dependencies = [ "log", "matchit", "once_cell", + "parking_lot", "pyo3", "pyo3-asyncio-0-21", "serde", diff --git a/Cargo.toml b/Cargo.toml index ba4e028..d8c93af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ once_cell = "1.19" # RFC 1738 / WHATWG: percent-decode and + as space in application/x-www-form-urlencoded query form_urlencoded = "1.2" log = "0.4" +parking_lot = "0.12" [features] default = [] diff --git a/src/dispatch.rs b/src/dispatch.rs index 218943f..11d769d 100644 --- a/src/dispatch.rs +++ b/src/dispatch.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::sync::Arc; -use std::sync::Mutex; + +use parking_lot::Mutex; use jsonwebtoken::errors::ErrorKind; use jsonwebtoken::{decode, DecodingKey, Validation}; @@ -73,11 +74,11 @@ pub async fn run_rsgi( let is_head = method == "HEAD"; if (method == "GET" || method == "HEAD") && path == "/openapi.json" { let (inc, doc) = { - let st = state.lock().map_err(crate::lock_err)?; + let st = state.lock(); if !st.include_openapi { (false, String::new()) } else { - let oa = st.openapi.lock().map_err(crate::lock_err)?; + let oa = st.openapi.lock(); (true, oa.to_string()) } }; @@ -121,7 +122,7 @@ pub async fn run_rsgi( )) })?; let route_out: Option<(usize, HashMap)> = (|| -> PyResult<_> { - let st = state.lock().map_err(crate::lock_err)?; + let st = state.lock(); let g = map_method_router(&st, &method) .ok_or_else(|| pyo3::exceptions::PyValueError::new_err("method"))?; Ok(g.at(&path).ok().map(|m| { @@ -136,7 +137,7 @@ pub async fn run_rsgi( Some(x) => x, None => { let m = { - let st = state.lock().map_err(crate::lock_err)?; + let st = state.lock(); methods_matching_path(&st, &path) }; if m.is_empty() { @@ -169,7 +170,7 @@ pub async fn run_rsgi( handler_param_names, handler_varkw, ) = { - let st = state.lock().map_err(crate::lock_err)?; + let st = state.lock(); let e = st .routes .get(route_idx) diff --git a/src/lib.rs b/src/lib.rs index bfc5115..8f83c97 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ use std::collections::HashSet; use std::sync::Arc; -use std::sync::Mutex; + +use parking_lot::Mutex; use pyo3::prelude::*; use pyo3::types::{PyDict, PyList, PyTuple}; @@ -18,10 +19,6 @@ use state::AppState; type ParsedDependencies = (Vec, Vec>, Vec, Vec); -pub(crate) fn lock_err(e: std::sync::PoisonError) -> PyErr { - pyo3::exceptions::PyRuntimeError::new_err(e.to_string()) -} - /// Parameter names the route handler accepts, plus whether it has `**kwargs`. fn handler_signature_kinds( py: Python<'_>, @@ -184,7 +181,7 @@ impl App { jwt_cookie: Option, body_schema_json: Option, ) -> PyResult<()> { - let st = self.state.lock().map_err(lock_err)?; + let st = self.state.lock(); if st.frozen { return Err(pyo3::exceptions::PyValueError::new_err( "app is frozen; no more add_route", @@ -231,7 +228,7 @@ impl App { .getattr(pyo3::intern!(py, "__name__"))? .extract()?; let (handler_param_names, handler_varkw) = handler_signature_kinds(py, handler.bind(py))?; - let mut st = self.state.lock().map_err(lock_err)?; + let mut st = self.state.lock(); let idx = st.routes.len(); st.routes.push(state::RouteEntry { handler, @@ -261,7 +258,7 @@ impl App { })?), }; { - let mut oa = st.openapi.lock().map_err(lock_err)?; + let mut oa = st.openapi.lock(); App::openapi_add_path(&mut oa, &method, &path, &op_id, request_schema); } { @@ -276,20 +273,20 @@ impl App { /// Lock route registration. Linear dependency list is a valid topological order (DAG of independent roots). fn freeze(&self) -> PyResult<()> { - let mut st = self.state.lock().map_err(lock_err)?; + let mut st = self.state.lock(); st.frozen = true; Ok(()) } fn set_openapi_served(&self, enabled: bool) -> PyResult<()> { - let mut st = self.state.lock().map_err(lock_err)?; + let mut st = self.state.lock(); st.include_openapi = enabled; Ok(()) } fn set_openapi_title(&self, title: &str) -> PyResult<()> { - let st = self.state.lock().map_err(lock_err)?; - let mut oa = st.openapi.lock().map_err(lock_err)?; + let st = self.state.lock(); + let mut oa = st.openapi.lock(); if let Some(info) = oa .as_object_mut() .and_then(|m| m.get_mut("info")) @@ -315,8 +312,8 @@ impl App { } fn openapi_json(&self) -> PyResult { - let st = self.state.lock().map_err(lock_err)?; - let oa = st.openapi.lock().map_err(lock_err)?; + let st = self.state.lock(); + let oa = st.openapi.lock(); Ok(oa.to_string()) } } diff --git a/src/state.rs b/src/state.rs index a4ffb39..981c936 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; -use std::sync::Mutex; use matchit::Router; +use parking_lot::Mutex; use pyo3::prelude::*; pub struct RouteEntry { @@ -72,15 +72,15 @@ impl AppState { pub fn map_method_router<'a>( state: &'a AppState, method: &str, -) -> Option>> { +) -> Option>> { match method { // RFC 9110: HEAD shares URI with GET; same handler, no body in the response. - "GET" | "HEAD" => state.get.lock().ok(), - "POST" => state.post.lock().ok(), - "PUT" => state.put.lock().ok(), - "PATCH" => state.patch.lock().ok(), - "DELETE" => state.delete.lock().ok(), - "OPTIONS" => state.options.lock().ok(), + "GET" | "HEAD" => Some(state.get.lock()), + "POST" => Some(state.post.lock()), + "PUT" => Some(state.put.lock()), + "PATCH" => Some(state.patch.lock()), + "DELETE" => Some(state.delete.lock()), + "OPTIONS" => Some(state.options.lock()), _ => None, } } @@ -92,33 +92,39 @@ pub fn map_method_router<'a>( pub fn methods_matching_path(state: &AppState, path: &str) -> Vec { const ORDER: [&str; 7] = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]; let mut have = [false; 7]; - if let Ok(g) = state.get.lock() { + { + let g = state.get.lock(); if g.at(path).is_ok() { have[0] = true; have[1] = true; } } - if let Ok(r) = state.post.lock() { + { + let r = state.post.lock(); if r.at(path).is_ok() { have[2] = true; } } - if let Ok(r) = state.put.lock() { + { + let r = state.put.lock(); if r.at(path).is_ok() { have[3] = true; } } - if let Ok(r) = state.patch.lock() { + { + let r = state.patch.lock(); if r.at(path).is_ok() { have[4] = true; } } - if let Ok(r) = state.delete.lock() { + { + let r = state.delete.lock(); if r.at(path).is_ok() { have[5] = true; } } - if let Ok(r) = state.options.lock() { + { + let r = state.options.lock(); if r.at(path).is_ok() { have[6] = true; }