Skip to content

Commit ad677f6

Browse files
committed
Remove heap allocations
1 parent 48b4df7 commit ad677f6

File tree

1 file changed

+142
-125
lines changed

1 file changed

+142
-125
lines changed

src/async_/resolver.rs

Lines changed: 142 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,13 @@
88
//! See <https://nginx.org/en/docs/http/ngx_http_core_module.html#resolver>.
99
1010
use alloc::string::{String, ToString};
11-
use core::ffi::c_void;
1211
use core::fmt;
1312
use core::num::NonZero;
1413
use core::pin::Pin;
1514
use core::ptr::NonNull;
1615
use core::task::{Context, Poll, Waker};
1716

1817
use crate::{
19-
allocator::Box,
2018
collections::Vec,
2119
core::Pool,
2220
ffi::{
@@ -101,7 +99,7 @@ impl From<NonZero<isize>> for ResolverError {
10199
}
102100
}
103101

104-
type Res = Result<Vec<ngx_addr_t>, Error>;
102+
type Res = Result<Vec<ngx_addr_t, Pool>, Error>;
105103

106104
/// A wrapper for an ngx_resolver_t which provides an async Rust API
107105
pub struct Resolver {
@@ -117,12 +115,22 @@ impl Resolver {
117115
}
118116

119117
/// Resolve a name into a set of addresses.
120-
///
121-
/// The set of addresses may not be deterministic, because the
122-
/// implementation of the resolver may race multiple DNS requests.
123-
pub async fn resolve(&self, name: &ngx_str_t, pool: &Pool) -> Res {
124-
let mut resolver = Resolution::new(name, pool, self.resolver, self.timeout)?;
125-
resolver.as_mut().await
118+
pub async fn resolve_name(&self, name: &ngx_str_t, pool: &Pool) -> Res {
119+
let mut ctx = ResolverCtx::new(self.resolver)?;
120+
ctx.name = *name;
121+
ctx.timeout = self.timeout;
122+
123+
Resolution::new(ctx, pool).await
124+
}
125+
126+
/// Resolve a service into a set of addresses.
127+
pub async fn resolve_service(&self, name: &ngx_str_t, service: &ngx_str_t, pool: &Pool) -> Res {
128+
let mut ctx = ResolverCtx::new(self.resolver)?;
129+
ctx.name = *name;
130+
ctx.service = *service;
131+
ctx.timeout = self.timeout;
132+
133+
Resolution::new(ctx, pool).await
126134
}
127135
}
128136

@@ -136,68 +144,22 @@ struct Resolution<'a> {
136144
// Pool used for allocating `Vec<ngx_addr_t>` contents in `Res`. Read by
137145
// the callback handler.
138146
pool: &'a Pool,
139-
// Pointer to the ngx_resolver_ctx_t. Resolution constructs this with
140-
// ngx_resolver_name_start in the constructor, and is responsible for
141-
// freeing it, with ngx_resolver_name_done, once it is no longer needed -
142-
// this happens in either the callback handler, or the drop impl. Calling
143-
// ngx_resolver_name_done before the callback fires ensure nginx does not
144-
// ever call the callback.
145-
ctx: Option<NonNull<ngx_resolver_ctx_t>>,
147+
// Pointer to the ngx_resolver_ctx_t.
148+
ctx: Option<ResolverCtx>,
146149
}
147150

148151
impl<'a> Resolution<'a> {
149-
fn new(
150-
name: &ngx_str_t,
151-
pool: &'a Pool,
152-
resolver: NonNull<ngx_resolver_t>,
153-
timeout: ngx_msec_t,
154-
) -> Result<Pin<Box<Self>>, Error> {
155-
let mut ctx = unsafe {
156-
// Start a new resolver context. This implementation currently
157-
// passes a null for the second argument `temp`. A non-null `temp`
158-
// provides a fast, non-callback-based path for immediately
159-
// returning an addr iff `temp` contains a name which is textual
160-
// form of an addr.
161-
let ctx = ngx_resolve_start(resolver.as_ptr(), core::ptr::null_mut());
162-
NonNull::new(ctx).ok_or(Error::AllocationFailed)?
163-
};
164-
165-
// Create a pinned Resolution on the heap, so that we can make
166-
// a stable pointer to the Resolution struct.
167-
let mut this = Pin::new(Box::new(Resolution {
152+
pub fn new(mut ctx: ResolverCtx, pool: &'a Pool) -> Self {
153+
ctx.handler = Some(Self::handler);
154+
155+
ctx.set_cancelable(1);
156+
157+
Self {
168158
complete: None,
169159
waker: None,
170160
pool,
171161
ctx: Some(ctx),
172-
}));
173-
174-
{
175-
// Set up the ctx with everything the resolver needs to resolve a
176-
// name, and the handler callback which is called on completion.
177-
let ctx: &mut ngx_resolver_ctx_t = unsafe { ctx.as_mut() };
178-
ctx.name = *name;
179-
ctx.timeout = timeout;
180-
ctx.set_cancelable(1);
181-
ctx.handler = Some(Self::handler);
182-
// Safety: Self::handler, Future::poll, and Drop::drop will have
183-
// access to &mut Resolution. Nginx is single-threaded and we are
184-
// assured only one of those is on the stack at a time, except if
185-
// Self::handler wakes a task which polls or drops the Future,
186-
// which it only does after use of &mut Resolution is complete.
187-
let ptr: &mut Resolution = unsafe { Pin::into_inner_unchecked(this.as_mut()) };
188-
ctx.data = ptr as *mut Resolution as *mut c_void;
189-
}
190-
191-
// Start name resolution using the ctx. If the name is in the dns
192-
// cache, the handler may get called from this stack. Otherwise, it
193-
// will be called later by nginx when it gets a dns response or a
194-
// timeout.
195-
let ret = unsafe { ngx_resolve_name(ctx.as_ptr()) };
196-
if let Some(e) = NonZero::new(ret) {
197-
return Err(Error::Resolver(ResolverError::from(e), name.to_string()));
198162
}
199-
200-
Ok(this)
201163
}
202164

203165
// Nginx will call this handler when name resolution completes. If the
@@ -206,74 +168,38 @@ impl<'a> Resolution<'a> {
206168
unsafe extern "C" fn handler(ctx: *mut ngx_resolver_ctx_t) {
207169
let mut data = unsafe { NonNull::new_unchecked((*ctx).data as *mut Resolution) };
208170
let this: &mut Resolution = unsafe { data.as_mut() };
209-
this.complete = Some(Self::resolve_result(ctx, this.pool));
210171

211-
let mut ctx = this.ctx.take().expect("ctx must be present");
212-
unsafe { nginx_sys::ngx_resolve_name_done(ctx.as_mut()) };
172+
if let Some(ctx) = this.ctx.take() {
173+
this.complete = Some(ctx.into_result(this.pool));
174+
}
213175

214176
// Wake last, after all use of &mut Resolution, because wake may
215177
// poll Resolution future on current stack.
216178
if let Some(waker) = this.waker.take() {
217179
waker.wake();
218180
}
219181
}
220-
221-
/// Take the results in a ctx and make an owned copy as a
222-
/// Result<Vec<ngx_addr_t>, Error>, where the internals of the ngx_addr_t
223-
/// are allocated on the given Pool
224-
fn resolve_result(ctx: *mut ngx_resolver_ctx_t, pool: &Pool) -> Res {
225-
let ctx = unsafe { ctx.as_ref().unwrap() };
226-
if let Some(e) = NonZero::new(ctx.state) {
227-
return Err(Error::Resolver(
228-
ResolverError::from(e),
229-
ctx.name.to_string(),
230-
));
231-
}
232-
if ctx.addrs.is_null() {
233-
Err(Error::AllocationFailed)?;
234-
}
235-
236-
if ctx.naddrs > 0 {
237-
unsafe { core::slice::from_raw_parts(ctx.addrs, ctx.naddrs) }
238-
.iter()
239-
.map(|addr| Self::copy_resolved_addr(addr, pool))
240-
.collect::<Result<Vec<_>, _>>()
241-
} else {
242-
Ok(Vec::new())
243-
}
244-
}
245-
246-
/// Take the contents of an ngx_resolver_addr_t and make an owned copy as
247-
/// an ngx_addr_t, using the Pool for allocation of the internals.
248-
fn copy_resolved_addr(
249-
addr: &nginx_sys::ngx_resolver_addr_t,
250-
pool: &Pool,
251-
) -> Result<ngx_addr_t, Error> {
252-
let sockaddr = pool.alloc(addr.socklen as usize) as *mut nginx_sys::sockaddr;
253-
if sockaddr.is_null() {
254-
Err(Error::AllocationFailed)?;
255-
}
256-
unsafe {
257-
addr.sockaddr
258-
.cast::<u8>()
259-
.copy_to_nonoverlapping(sockaddr.cast(), addr.socklen as usize)
260-
};
261-
262-
let name = unsafe { ngx_str_t::from_bytes(pool.as_ptr(), addr.name.as_bytes()) }
263-
.ok_or(Error::AllocationFailed)?;
264-
265-
Ok(ngx_addr_t {
266-
sockaddr,
267-
socklen: addr.socklen,
268-
name,
269-
})
270-
}
271182
}
272183

273184
impl<'a> core::future::Future for Resolution<'a> {
274-
type Output = Result<Vec<ngx_addr_t>, Error>;
185+
type Output = Result<Vec<ngx_addr_t, Pool>, Error>;
186+
275187
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
276188
let mut this = self.as_mut();
189+
190+
if this.waker.is_none() && this.complete.is_none() {
191+
let addr = core::ptr::from_mut(unsafe { Pin::into_inner_unchecked(this.as_mut()) });
192+
193+
if let Some(ctx) = &mut this.ctx {
194+
// Start name resolution using the ctx. If the name is in the dns
195+
// cache, the handler may get called from this stack. Otherwise, it
196+
// will be called later by nginx when it gets a dns response or a
197+
// timeout.
198+
ctx.data = addr.cast();
199+
ctx.resolve()?;
200+
}
201+
}
202+
277203
// The handler populates this.complete, and we consume it here:
278204
match this.complete.take() {
279205
Some(res) => Poll::Ready(res),
@@ -292,15 +218,106 @@ impl<'a> core::future::Future for Resolution<'a> {
292218
}
293219
}
294220

295-
impl<'a> Drop for Resolution<'a> {
221+
struct ResolverCtx(NonNull<ngx_resolver_ctx_t>);
222+
223+
impl core::ops::Deref for ResolverCtx {
224+
type Target = ngx_resolver_ctx_t;
225+
226+
fn deref(&self) -> &Self::Target {
227+
// SAFETY: this wrapper is always constructed with a valid non-empty resolve context
228+
unsafe { self.0.as_ref() }
229+
}
230+
}
231+
232+
impl core::ops::DerefMut for ResolverCtx {
233+
fn deref_mut(&mut self) -> &mut Self::Target {
234+
// SAFETY: this wrapper is always constructed with a valid non-empty resolve context
235+
unsafe { self.0.as_mut() }
236+
}
237+
}
238+
239+
impl Drop for ResolverCtx {
296240
fn drop(&mut self) {
297-
// ctx is taken and freed if the Resolution reaches the handler
298-
// callback, but if dropped before that callback, this will cancel any
299-
// ongoing work as well as free the ctx memory.
300-
if let Some(mut ctx) = self.ctx.take() {
301-
unsafe {
302-
nginx_sys::ngx_resolve_name_done(ctx.as_mut());
241+
unsafe {
242+
nginx_sys::ngx_resolve_name_done(self.0.as_mut());
243+
}
244+
}
245+
}
246+
247+
impl ResolverCtx {
248+
/// Creates a new resolver context.
249+
///
250+
/// This implementation currently passes a null for the second argument `temp`. A non-null
251+
/// `temp` provides a fast, non-callback-based path for immediately returning an addr if
252+
/// `temp` contains a name which is textual form of an addr.
253+
pub fn new(resolver: NonNull<ngx_resolver_t>) -> Result<Self, Error> {
254+
let ctx = unsafe { ngx_resolve_start(resolver.as_ptr(), core::ptr::null_mut()) };
255+
NonNull::new(ctx).map(Self).ok_or(Error::AllocationFailed)
256+
}
257+
258+
/// Starts name resolution.
259+
pub fn resolve(&mut self) -> Result<(), Error> {
260+
let ret = unsafe { ngx_resolve_name(self.0.as_ptr()) };
261+
262+
if let Some(e) = NonZero::new(ret) {
263+
let name = self.name.to_string();
264+
Err(Error::Resolver(ResolverError::from(e), name))
265+
} else {
266+
Ok(())
267+
}
268+
}
269+
270+
/// Take the results in a ctx and make an owned copy as a
271+
/// Result<Vec<ngx_addr_t>, Error>, where the internals of the ngx_addr_t
272+
/// are allocated on the given Pool
273+
pub fn into_result(self, pool: &Pool) -> Result<Vec<ngx_addr_t, Pool>, Error> {
274+
if let Some(e) = NonZero::new(self.state) {
275+
return Err(Error::Resolver(
276+
ResolverError::from(e),
277+
self.name.to_string(),
278+
));
279+
}
280+
if self.addrs.is_null() {
281+
Err(Error::AllocationFailed)?;
282+
}
283+
284+
let mut out = Vec::new_in(pool.clone());
285+
286+
if self.naddrs > 0 {
287+
out.try_reserve_exact(self.naddrs)
288+
.map_err(|_| Error::AllocationFailed)?;
289+
290+
for addr in unsafe { core::slice::from_raw_parts(self.addrs, self.naddrs) } {
291+
out.push(copy_resolved_addr(addr, pool)?);
303292
}
304293
}
294+
295+
Ok(out)
305296
}
306297
}
298+
299+
/// Take the contents of an ngx_resolver_addr_t and make an owned copy as
300+
/// an ngx_addr_t, using the Pool for allocation of the internals.
301+
fn copy_resolved_addr(
302+
addr: &nginx_sys::ngx_resolver_addr_t,
303+
pool: &Pool,
304+
) -> Result<ngx_addr_t, Error> {
305+
let sockaddr = pool.alloc(addr.socklen as usize) as *mut nginx_sys::sockaddr;
306+
if sockaddr.is_null() {
307+
Err(Error::AllocationFailed)?;
308+
}
309+
unsafe {
310+
addr.sockaddr
311+
.cast::<u8>()
312+
.copy_to_nonoverlapping(sockaddr.cast(), addr.socklen as usize)
313+
};
314+
315+
let name = unsafe { ngx_str_t::from_bytes(pool.as_ptr(), addr.name.as_bytes()) }
316+
.ok_or(Error::AllocationFailed)?;
317+
318+
Ok(ngx_addr_t {
319+
sockaddr,
320+
socklen: addr.socklen,
321+
name,
322+
})
323+
}

0 commit comments

Comments
 (0)