Skip to content

Commit b9bef62

Browse files
committed
fix memory leak, minor internal refactors, and comments
1 parent b61a1bd commit b9bef62

File tree

1 file changed

+56
-13
lines changed

1 file changed

+56
-13
lines changed

src/async_/resolver.rs

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -125,33 +125,49 @@ impl Resolver {
125125
/// The set of addresses may not be deterministic, because the
126126
/// implementation of the resolver may race multiple DNS requests.
127127
pub async fn resolve(&self, name: &ngx_str_t, pool: &Pool) -> Res {
128-
let ctx = unsafe {
129-
NonNull::new(ngx_resolve_start(
130-
self.resolver.as_ptr(),
131-
core::ptr::null_mut(),
132-
))
133-
.ok_or(Error::AllocationFailed)?
134-
};
135-
136-
let mut resolver = Resolution::new(name, pool, self.timeout, ctx)?;
128+
let mut resolver = Resolution::new(name, pool, self.resolver, self.timeout)?;
137129
resolver.as_mut().await
138130
}
139131
}
140132

141133
struct Resolution<'a> {
134+
// Storage for the result of the resolution `Res`. Populated by the
135+
// callback handler, and taken by the Future::poll impl.
142136
complete: Option<Res>,
137+
// Storage for a pending Waker. Populated by the Future::poll impl,
138+
// and taken by the callback handler.
143139
waker: Option<Waker>,
140+
// Pool used for allocating `Vec<ngx_addr_t>` contents in `Res`. Read by
141+
// the callback handler.
144142
pool: &'a Pool,
143+
// Pointer to the ngx_resolver_ctx_t. Resolution constructs this with
144+
// ngx_resolver_name_start in the constructor, and is responsible for
145+
// freeing it, with ngx_resolver_name_done, once it is no longer needed -
146+
// this happens in either the callback handler, or the drop impl. Calling
147+
// ngx_resolver_name_done before the callback fires ensure nginx does not
148+
// ever call the callback.
145149
ctx: Option<NonNull<ngx_resolver_ctx_t>>,
146150
}
147151

148152
impl<'a> Resolution<'a> {
149153
fn new(
150154
name: &ngx_str_t,
151155
pool: &'a Pool,
156+
resolver: NonNull<ngx_resolver_t>,
152157
timeout: ngx_msec_t,
153-
mut ctx: NonNull<ngx_resolver_ctx_t>,
154158
) -> Result<Pin<Box<Self>>, Error> {
159+
let ctx = unsafe {
160+
// Start a new resolver context. This implementation currently
161+
// passes a null for the second argument `temp`. A non-null `temp`
162+
// provides a fast, non-callback-based path for immediately
163+
// returning an addr iff `temp` contains a name which is textual
164+
// form of an addr.
165+
let ctx = ngx_resolve_start(resolver.as_ptr(), core::ptr::null_mut());
166+
NonNull::new(ctx).ok_or(Error::AllocationFailed)?
167+
};
168+
169+
// Create a pinned Resolution on the heap, so that we can make
170+
// a stable pointer to the Resolution struct.
155171
let mut this = Pin::new(Box::new(Resolution {
156172
complete: None,
157173
waker: None,
@@ -160,15 +176,26 @@ impl<'a> Resolution<'a> {
160176
}));
161177

162178
{
179+
// Set up the ctx with everything the resolver needs to resolve a
180+
// name, and the handler callback which is called on completion.
163181
let ctx: &mut ngx_resolver_ctx_t = unsafe { ctx.as_mut() };
164182
ctx.name = *name;
165183
ctx.timeout = timeout;
166184
ctx.set_cancelable(1);
167185
ctx.handler = Some(Self::handler);
186+
// Safety: Self::handler, Future::poll, and Drop::drop will have
187+
// access to &mut Resolution. Nginx is single-threaded and we are
188+
// assured only one of those is on the stack at a time, except if
189+
// Self::handler wakes a task which polls or drops the Future,
190+
// which it only does after use of &mut Resolution is complete.
168191
let ptr: &mut Resolution = unsafe { Pin::into_inner_unchecked(this.as_mut()) };
169192
ctx.data = ptr as *mut Resolution as *mut c_void;
170193
}
171194

195+
// Start name resolution using the ctx. If the name is in the dns
196+
// cache, the handler may get called from this stack. Otherwise, it
197+
// will be called later by nginx when it gets a dns response or a
198+
// timeout.
172199
let ret = unsafe { ngx_resolve_name(ctx.as_ptr()) };
173200
if ret != 0 {
174201
return Err(Error::Resolver(
@@ -180,18 +207,26 @@ impl<'a> Resolution<'a> {
180207
Ok(this)
181208
}
182209

210+
// Nginx will call this handler when name resolution completes. If the
211+
// result is cached, this could be
183212
unsafe extern "C" fn handler(ctx: *mut ngx_resolver_ctx_t) {
184213
let mut data = unsafe { NonNull::new_unchecked((*ctx).data as *mut Resolution) };
185214
let this: &mut Resolution = unsafe { data.as_mut() };
186215
this.complete = Some(Self::resolve_result(ctx, this.pool));
187-
this.ctx.take();
216+
217+
let mut ctx = this.ctx.take().expect("ctx must be present");
218+
unsafe { nginx_sys::ngx_resolve_name_done(ctx.as_mut()) };
219+
220+
// Wake last, after all use of &mut Resolution, because wake may
221+
// poll Resolution future on current stack.
188222
if let Some(waker) = this.waker.take() {
189-
// Wake last, after all use of &mut Resolution, because wake may
190-
// poll Resolution future on current stack.
191223
waker.wake();
192224
}
193225
}
194226

227+
/// Take the results in a ctx and make an owned copy as a
228+
/// Result<Vec<ngx_addr_t>, Error>, where both the Vec and internals of
229+
/// the ngx_addr_t are allocated on the given Pool
195230
fn resolve_result(ctx: *mut ngx_resolver_ctx_t, pool: &Pool) -> Res {
196231
let ctx = unsafe { ctx.as_ref().unwrap() };
197232
let s = ctx.state;
@@ -211,6 +246,8 @@ impl<'a> Resolution<'a> {
211246
Ok(out)
212247
}
213248

249+
/// Take the contents of an ngx_resolver_addr_t and make an owned copy as
250+
/// an ngx_addr_t, using the Pool for allocation of the internals.
214251
fn copy_resolved_addr(
215252
addr: *mut nginx_sys::ngx_resolver_addr_t,
216253
pool: &Pool,
@@ -247,9 +284,12 @@ impl<'a> core::future::Future for Resolution<'a> {
247284
type Output = Result<Vec<ngx_addr_t>, Error>;
248285
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
249286
let mut this = self.as_mut();
287+
// The handler populates this.complete, and we consume it here:
250288
match this.complete.take() {
251289
Some(res) => Poll::Ready(res),
252290
None => {
291+
// If the handler has not yet fired, populate the waker field,
292+
// which the handler will consume:
253293
match &mut self.waker {
254294
None => {
255295
self.waker = Some(cx.waker().clone());
@@ -264,6 +304,9 @@ impl<'a> core::future::Future for Resolution<'a> {
264304

265305
impl<'a> Drop for Resolution<'a> {
266306
fn drop(&mut self) {
307+
// ctx is taken and freed if the Resolution reaches the handler
308+
// callback, but if dropped before that callback, this will cancel any
309+
// ongoing work as well as free the ctx memory.
267310
if let Some(mut ctx) = self.ctx.take() {
268311
unsafe {
269312
nginx_sys::ngx_resolve_name_done(ctx.as_mut());

0 commit comments

Comments
 (0)