Skip to content

Commit b804b38

Browse files
Merge pull request #176 from QueryaHub/perf-zero-alloc-params-140
perf(params): zero-allocation path parameter extraction from URL slices (#140)
2 parents 1417de8 + 90caf70 commit b804b38

3 files changed

Lines changed: 33 additions & 31 deletions

File tree

src/dispatch.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,7 @@ pub async fn run_rsgi(
638638
None => Err(pyo3::exceptions::PyValueError::new_err("method")),
639639
Some(m) => Ok(m),
640640
}?;
641-
let (route_idx, param_map) = match route_out {
641+
let (route_idx, params) = match route_out {
642642
Some(x) => x,
643643
None => {
644644
let m = methods_matching_path_compiled(&compiled, &path);
@@ -1088,7 +1088,7 @@ pub async fn run_rsgi(
10881088
let has_dep_kwargs = dependencies.iter().enumerate().any(|(i, dep)| {
10891089
dep_out.get(i).is_some() && (handler_varkw || handler_param_names.contains(&dep.name))
10901090
});
1091-
let should_use_kwargs = !param_map.is_empty()
1091+
let should_use_kwargs = !params.is_empty()
10921092
|| !query_map.is_empty()
10931093
|| has_dep_kwargs
10941094
|| claims_val.is_some()
@@ -1108,8 +1108,8 @@ pub async fn run_rsgi(
11081108
return Ok(RunHandlerResult::Ok((res, is_async)));
11091109
}
11101110
let kwargs = PyDict::new(py);
1111-
for (k, v) in param_map {
1112-
let vpy = value_for_path_param(py, &v);
1111+
for (k, v) in params.iter() {
1112+
let vpy = value_for_path_param(py, v);
11131113
kwargs.set_item(k, vpy)?;
11141114
}
11151115
if !query_map.is_empty() {
@@ -1771,7 +1771,7 @@ async fn run_rsgi_websocket(
17711771
};
17721772
let ws_routes = Arc::clone(&snapshot.websocket_routes);
17731773
let route_match = match_ws_route_compiled(&compiled, &path);
1774-
let Some((route_idx, param_map)) = route_match else {
1774+
let Some((route_idx, params)) = route_match else {
17751775
// No route → polite close. ``close`` is sync on RSGIWebsocketProtocol.
17761776
let _ = Python::with_gil(|py| -> PyResult<()> {
17771777
let p = protocol.bind(py);
@@ -1786,8 +1786,12 @@ async fn run_rsgi_websocket(
17861786
.ok_or_else(|| pyo3::exceptions::PyRuntimeError::new_err("ws route index"))?;
17871787
Ok((e.handler.clone(), e.is_async))
17881788
})?;
1789+
let path_params: Vec<(String, String)> = params
1790+
.iter()
1791+
.map(|(k, v)| (k.to_string(), v.to_string()))
1792+
.collect();
17891793
let call_result = Python::with_gil(|py| -> PyResult<(PyObject, bool)> {
1790-
let ws = WebSocket::new(protocol.clone_ref(py), scope.clone_ref(py), param_map);
1794+
let ws = WebSocket::new(protocol.clone_ref(py), scope.clone_ref(py), path_params);
17911795
let py_ws = Py::new(py, ws)?;
17921796
let res = handler.bind(py).call1((py_ws,))?.unbind();
17931797
Ok((res, is_async))

src/params.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ pub fn value_for_path_param(py: Python<'_>, s: &str) -> Py<PyAny> {
102102
return f.into_py_any(py).expect("f64 to Python");
103103
}
104104
}
105-
s.to_string().into_py_any(py).expect("str to Python")
105+
pyo3::types::PyString::new(py, s).into_any().unbind()
106106
}
107107

108108
pub fn header_get_lax(headers: &Bound<'_, PyAny>, name: &str) -> Option<String> {

src/state.rs

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -259,36 +259,27 @@ pub struct HotSnapshot {
259259
}
260260

261261
/// Lookup a WebSocket route in a precomputed [`CompiledRouters`] (lock-free).
262-
pub fn match_ws_route_compiled(
263-
compiled: &CompiledRouters,
264-
path: &str,
265-
) -> Option<(usize, Vec<(String, String)>)> {
266-
compiled.websocket.at(path).ok().map(|m| {
267-
let mut pmap = Vec::new();
268-
for (k, v) in m.params.iter() {
269-
pmap.push((k.to_string(), v.to_string()));
270-
}
271-
(*m.value, pmap)
272-
})
262+
pub fn match_ws_route_compiled<'a, 'b>(
263+
compiled: &'a CompiledRouters,
264+
path: &'b str,
265+
) -> Option<(usize, matchit::Params<'a, 'b>)> {
266+
compiled
267+
.websocket
268+
.at(path)
269+
.ok()
270+
.map(|m| (*m.value, m.params))
273271
}
274272

275273
/// Lookup an HTTP route in a precomputed [`CompiledRouters`] (lock-free).
276274
///
277275
/// Returns ``None`` for unsupported method, ``Some(None)`` for no match, ``Some(Some(...))`` on hit.
278-
#[allow(clippy::type_complexity)]
279-
pub fn match_route_compiled(
280-
compiled: &CompiledRouters,
276+
pub fn match_route_compiled<'a, 'b>(
277+
compiled: &'a CompiledRouters,
281278
method: &str,
282-
path: &str,
283-
) -> Option<Option<(usize, Vec<(String, String)>)>> {
279+
path: &'b str,
280+
) -> Option<Option<(usize, matchit::Params<'a, 'b>)>> {
284281
let g = router_for_compiled(compiled, method)?;
285-
Some(g.at(path).ok().map(|m| {
286-
let mut pmap = Vec::new();
287-
for (k, v) in m.params.iter() {
288-
pmap.push((k.to_string(), v.to_string()));
289-
}
290-
(*m.value, pmap)
291-
}))
282+
Some(g.at(path).ok().map(|m| (*m.value, m.params)))
292283
}
293284

294285
/// All HTTP methods that match `path` in a precomputed [`CompiledRouters`] (lock-free 405 list).
@@ -368,7 +359,14 @@ fn match_route(
368359
path: &str,
369360
) -> Option<Option<(usize, Vec<(String, String)>)>> {
370361
if let Some(c) = &state.compiled {
371-
return match_route_compiled(c, method, path);
362+
let res = match_route_compiled(c, method, path)?;
363+
return Some(res.map(|(idx, params)| {
364+
let mut pmap = Vec::new();
365+
for (k, v) in params.iter() {
366+
pmap.push((k.to_string(), v.to_string()));
367+
}
368+
(idx, pmap)
369+
}));
372370
}
373371
let g = map_method_router(state, method)?;
374372
Some(g.at(path).ok().map(|m| {

0 commit comments

Comments
 (0)