Forum: NGINX terminate a connection after sending headers

2974d09ac2541e892966b762aad84943?d=identicon&s=25 erankor2 (Guest)
on 2014-09-01 23:26
(Received via mailing list)
Hi all,

In the module I'm developing, I have the possibility of encountering an
error after the response headers were already sent. As the headers were
already sent (with status 200) the only way for me to signal the error
to
the client would be to close the connection. I tried calling
ngx_http_finalize_request with both NGX_ERROR and NGX_HTTP_CLOSE and the
connection is not closed.
After debugging it, I found it has to do with the 'if
(mr->write_event_handler)' in ngx_http_terminate_request. I'm not sure
what
is the purpose of this if, but in my case write_event_handler has the
value
ngx_http_request_empty_handler, so the if evaluates to true and the
connection is not terminated. When I forcefully change
write_event_handler
to NULL with GDB, I see the connection is closed as expected.
I searched the code for 'write_event_handler =' and could not find a
single
place where this member gets a value of NULL (it always gets a pointer
to
some function).

Can anyone confirm if this is really a bug ? maybe the if should be
updated
to 'if (mr->write_event_handler != ngx_http_request_empty_handler)' ?

Thank you,

Eran

Posted at Nginx Forum:
http://forum.nginx.org/read.php?2,253006,253006#msg-253006
A8108a0961c6087c43cda32c8616dcba?d=identicon&s=25 Maxim Dounin (Guest)
on 2014-09-02 15:09
(Received via mailing list)
Hello!

On Mon, Sep 01, 2014 at 05:25:56PM -0400, erankor2 wrote:

> is the purpose of this if, but in my case write_event_handler has the value
> ngx_http_request_empty_handler, so the if evaluates to true and the
> connection is not terminated. When I forcefully change write_event_handler
> to NULL with GDB, I see the connection is closed as expected.
> I searched the code for 'write_event_handler =' and could not find a single
> place where this member gets a value of NULL (it always gets a pointer to
> some function).

The r->write_event_handler is set to NULL on initial creation of a
request.  If write_event_handler is not NULL during a request
termination, nginx posts an event instead of freeing the request
request directly (this is done to avoid use-after-free when
processing posted subrequests).  The request is still freed
though.

--
Maxim Dounin
http://nginx.org/
2974d09ac2541e892966b762aad84943?d=identicon&s=25 erankor2 (Guest)
on 2014-09-03 16:10
(Received via mailing list)
Maxim, thank you very much for your response.

To clarify - the problem is not about freeing the request (I don't think
there's a resource leak here), the problem is that the connection is
just
left hanging until the client closes it / the server is restarted.
It is true that write_event_handler gets initialized to zero when the
request is allocated, but it is set to ngx_http_request_empty_handler
before
the first line of my code even runs. In ngx_http_core_content_phase
there's:

    if (r->content_handler) {
        r->write_event_handler = ngx_http_request_empty_handler;
        ngx_http_finalize_request(r, r->content_handler(r));
        return NGX_OK;
    }

where r->content_handler is the entry point to my code. So, unless I
explicitly reset it back to NULL (something that I never saw in any
other
nginx module) write_event_handler will not be null and the connection
will
be left hanging.

I forked some sample hello world module and modified it to reproduce the
problem, please check it out here:
https://github.com/erankor/nginx-hello-world-modul...

In that code, I'm sending the response headers and then trigger a timer
for
1 second. In the timer callback I close the request with NGX_ERROR, but
the
connection remains active (I used a timer here since that's the easiest
solution to defer the execution, in my real project I'm performing
asynchronous file I/O)

Thank you !

Eran

Posted at Nginx Forum:
http://forum.nginx.org/read.php?2,253006,253040#msg-253040
A8108a0961c6087c43cda32c8616dcba?d=identicon&s=25 Maxim Dounin (Guest)
on 2014-09-04 03:10
(Received via mailing list)
Hello!

On Wed, Sep 03, 2014 at 10:10:01AM -0400, erankor2 wrote:

>     if (r->content_handler) {
> I forked some sample hello world module and modified it to reproduce the
> problem, please check it out here:
>
https://github.com/erankor/nginx-hello-world-modul...
>
> In that code, I'm sending the response headers and then trigger a timer for
> 1 second. In the timer callback I close the request with NGX_ERROR, but the
> connection remains active (I used a timer here since that's the easiest
> solution to defer the execution, in my real project I'm performing
> asynchronous file I/O)

The problem is that you use your own timer, and don't run posted
requests after it - but the request termination code relies on
posted requests being run.

If you are using your own events, you should do processing similar
to ngx_http_request_handler(), see src/http/ngx_http_request.c.
Notably, you have to call the ngx_http_run_posted_requests()
function after the code which calls ngx_http_finalize_request().

In this particular case, trivial fix is to do something like:

 static void
 event_callback(ngx_event_t *ev)
 {
+       ngx_connection_t   *c;
        ngx_http_request_t *r = (ngx_http_request_t *)ev->data;

+       c = r->connection;
+
        ngx_http_finalize_request(r, NGX_ERROR);
+
+       ngx_http_run_posted_requests(c);
 }


--
Maxim Dounin
http://nginx.org/
2974d09ac2541e892966b762aad84943?d=identicon&s=25 erankor2 (Guest)
on 2014-09-04 22:39
(Received via mailing list)
Thank you very much Maxim ! this works !

However, I bumped into a new problem, I use 2 different types of
asyncronous
operations in my code - file I/O and http requests. When I call
ngx_http_run_posted_requests from the aio callback it works well, but
when I
call it from the HTTP completion callback it actually makes the request
hang.
I can see that it calls ngx_http_upstream_check_broken_connection in
r->write_event_handler(r). I guess that in this case I should let
ngx_http_run_posted_requests run from the upstream module instead of
calling
it myself.

So my questions are:
1. Is there a simple rule that dictates when I should call
ngx_http_run_posted_requests and when I should not ?
I can work around the problem by doing something like 'if called from
the
aio callback call ngx_http_run_posted_requests' otherwise, don't call,
but
that doesn't feel like the correct solution.
2. A simpler solution that seems to work for me, is to call
ngx_http_run_posted_requests only when I see that r->main->count is one
(before finalize). But I don't know if that solution makes any sense -
is
there any relation between these posted_requests and the request count ?
My understanding (before reading your reply :)) was that whenever I do
something asynchronous I must increment the count, whenever I call
finalize_request the count is decremented and once the count reaches
zero
the request is terminated. So, what I'm missing is why the termination
of
the request has to be deferred via posted requests - couldn't
ngx_http_terminate_request just close the request only when it sees the
count reached zero instead ?

Thanks again for all your help, I really appreciate it !

Eran

Posted at Nginx Forum:
http://forum.nginx.org/read.php?2,253006,253081#msg-253081
A8108a0961c6087c43cda32c8616dcba?d=identicon&s=25 Maxim Dounin (Guest)
on 2014-09-05 06:35
(Received via mailing list)
Hello!

On Thu, Sep 04, 2014 at 04:38:41PM -0400, erankor2 wrote:

> it myself.
>
> So my questions are:
> 1. Is there a simple rule that dictates when I should call
> ngx_http_run_posted_requests and when I should not ?
> I can work around the problem by doing something like 'if called from the
> aio callback call ngx_http_run_posted_requests' otherwise, don't call, but
> that doesn't feel like the correct solution.

You should call ngx_http_run_posted_requests() after low-level
events.  That is, there is no need to call it after http-related
events, as ngx_http_run_posted_requests() will be called by
ngx_http_request_handler().  But if your own low-level events are
triggered, like the timer in your code, it's your responsibility
to call ngx_http_request_handler() after them.

> 2. A simpler solution that seems to work for me, is to call
> ngx_http_run_posted_requests only when I see that r->main->count is one
> (before finalize). But I don't know if that solution makes any sense - is
> there any relation between these posted_requests and the request count ?

No, it doesn't make sense to check r->main->count.

> My understanding (before reading your reply :)) was that whenever I do
> something asynchronous I must increment the count, whenever I call
> finalize_request the count is decremented and once the count reaches zero
> the request is terminated. So, what I'm missing is why the termination of
> the request has to be deferred via posted requests - couldn't
> ngx_http_terminate_request just close the request only when it sees the
> count reached zero instead ?

Termination on errors which you are doing isn't expected to wait
for r->main->count to reach zero.  It is expected to proactively
stop all ongoing activity by executing cleanup handlers, and then
free the request.

--
Maxim Dounin
http://nginx.org/
2974d09ac2541e892966b762aad84943?d=identicon&s=25 erankor2 (Guest)
on 2014-09-07 22:50
(Received via mailing list)
Thank you very much, Maxim.

I implemented the solution as you advised.

Eran

Posted at Nginx Forum:
http://forum.nginx.org/read.php?2,253006,253116#msg-253116
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.