On 7/10/20 9:55 AM, Ruediger Pluem wrote:
> 
> 
> On 7/1/20 6:35 PM, [email protected] wrote:
>> Author: ylavic
>> Date: Wed Jul  1 16:35:48 2020
>> New Revision: 1879401
>>
>> URL: http://svn.apache.org/viewvc?rev=1879401&view=rev
>> Log:
>> mod_proxy: improved and reentrant tunneling loop.
>>
>> modules/proxy/mod_proxy.h:
>>     Rename AP_PROXY_TRANSFER_SHOULD_YIELD to AP_PROXY_TRANSFER_YIELD_PENDING
>>     and add AP_PROXY_TRANSFER_YIELD_MAX_READS.
>>
>> modules/proxy/mod_proxy_http.c:
>> modules/proxy/mod_proxy_wstunnel.c:
>>     Removing of reqtimeout filter is now handled by ap_proxy_tunnel_create().
>>
>> modules/proxy/proxy_util.c:
>>     ap_proxy_transfer_between_connections():
>>         Reorganize loop to break out early.
>>         When AP_PROXY_TRANSFER_YIELD_PENDING, if !ap_filter_should_yield() we
>>         still need to run and check ap_filter_output_pending() since it may
>>         release pending data.
>>         When AP_PROXY_TRANSFER_YIELD_MAX_READS, stop the loop after too much
>>         reads (PROXY_TRANSFER_MAX_READS = 10000) to release the thread and
>>         give the caller a chance to schedule the other direction.
>>         Don't return APR_INCOMPLETE when it comes from an incomplete body
>>         detected by ap_http_filter().
>>
>>     ap_proxy_tunnel_create():
>>         Start with POLLOUT on both directions so that any pending output data
>>         is flushed first.
>>
>>     ap_proxy_tunnel_run():
>>         Remove re-init/clear of the pollset for each call so that the 
>> function
>>         is reentrant.
>>         Handle POLLOUT before POLLIN so that we can read in the same pass 
>> once
>>         all buffered output data are flushed, using ap_filter_input_pending()
>>         to drain buffered input data.
>>
>> This is preparatory patch for async websocket tunneling is mod_proxy_http.
>>
>> Modified:
>>     httpd/httpd/trunk/modules/proxy/mod_proxy.h
>>     httpd/httpd/trunk/modules/proxy/mod_proxy_http.c
>>     httpd/httpd/trunk/modules/proxy/mod_proxy_wstunnel.c
>>     httpd/httpd/trunk/modules/proxy/proxy_util.c
>>
> 
>> Modified: httpd/httpd/trunk/modules/proxy/proxy_util.c
>> URL: 
>> http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/proxy/proxy_util.c?rev=1879401&r1=1879400&r2=1879401&view=diff
>> ==============================================================================
>> --- httpd/httpd/trunk/modules/proxy/proxy_util.c (original)
>> +++ httpd/httpd/trunk/modules/proxy/proxy_util.c Wed Jul  1 16:35:48 2020
> 
>> @@ -4410,51 +4470,88 @@ PROXY_DECLARE(int) ap_proxy_tunnel_run(p
>>                        "proxy: %s: woken up, %i result(s)", scheme, 
>> nresults);
>>  
>>          for (i = 0; i < nresults; i++) {
>> -            const apr_pollfd_t *cur = &results[i];
>> -            int revents = cur->rtnevents;
>> +            const apr_pollfd_t *pfd = &results[i];
>> +            struct proxy_tunnel_conn *tc = pfd->client_data;
>> +
>> +            ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r,
>> +                          "proxy: %s: #%i: %s: %hx/%hx", scheme, i,
>> +                          tc->name, pfd->rtnevents, tc->pfd->reqevents);
>>  
>>              /* sanity check */
>> -            if (cur->desc.s != client->pfd->desc.s
>> -                    && cur->desc.s != origin->pfd->desc.s) {
>> +            if (pfd->desc.s != client->pfd->desc.s
>> +                    && pfd->desc.s != origin->pfd->desc.s) {
>>                  ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10222)
>>                                "proxy: %s: unknown socket in pollset", 
>> scheme);
>>                  rc = HTTP_INTERNAL_SERVER_ERROR;
>>                  goto cleanup;
>>              }
>> -
>> -            in = cur->client_data;
>> -            if (revents & APR_POLLOUT) {
>> -                in = in->other;
>> -            }
>> -            else if (!(revents & (APR_POLLIN | APR_POLLHUP))) {
>> +            if (!(pfd->rtnevents & (APR_POLLIN | APR_POLLHUP | 
>> APR_POLLOUT))) {
>>                  /* this catches POLLERR/POLLNVAL etc.. */
>>                  ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10220)
>>                                "proxy: %s: polling events error (%x)",
>> -                              scheme, revents);
>> +                              scheme, pfd->rtnevents);
>>                  rc = HTTP_INTERNAL_SERVER_ERROR;
>>                  goto cleanup;
>>              }
>> -            out = in->other;
>>  
>> -            ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r,
>> -                          "proxy: %s: #%i: %s/%hx => %s/%hx: %x",
>> -                          scheme, i, in->name, in->pfd->reqevents,
>> -                          out->name, out->pfd->reqevents, revents);
>> +            if (pfd->rtnevents & APR_POLLOUT) {
>> +                struct proxy_tunnel_conn *out = tc, *in = tc->other;
>> +
>> +                ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r,
>> +                              "proxy: %s: %s output ready",
>> +                              scheme, out->name);
>> +
>> +                rc = ap_filter_output_pending(out->c);
>> +                if (rc == OK) {
>> +                    /* Keep polling out (only) */
>> +                    continue;
>> +                }
>> +                if (rc != DECLINED) {
>> +                    /* Real failure, bail out */
>> +                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, 
>> APLOGNO(10221)
>> +                                  "proxy: %s: %s flushing failed (%i)",
>> +                                  scheme, out->name, rc);
>> +                    goto cleanup;
>> +                }
>> +                rc = OK;
>> +
>> +                /* No more pending data. If the input side is not readable
>> +                 * anymore it's time to shutdown for write (this direction
>> +                 * is over). Otherwise back to normal business.
>> +                 */
>> +                del_pollset(pollset, out->pfd, APR_POLLOUT);
>> +                if (in->readable) {
>> +                    ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r,
>> +                                  "proxy: %s: %s resume writable",
>> +                                  scheme, out->name);
>> +                    add_pollset(pollset, in->pfd, APR_POLLIN);
>> +                    out->writable = 1;
>> +                }
>> +                else {
>> +                    ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
>> +                                  "proxy: %s: %s write shutdown",
>> +                                  scheme, out->name);
>> +                    apr_socket_shutdown(out->pfd->desc.s, 1);
>> +                }
>> +            }
>>  
>> -            if (in->readable && (in->drain || !(revents & APR_POLLOUT))) {
>> +            if (pfd->rtnevents & (APR_POLLIN | APR_POLLHUP)
> 
> Why do we check for APR_POLLHUP here as well? I noticed a case where we 
> request only APR_POLLOUT but get back APR_POLLOUT and
> APR_POLLHUB which causes an endless loop.

The following patch seems to break the spin:

Index: modules/proxy/proxy_util.c
===================================================================
--- modules/proxy/proxy_util.c  (revision 1879543)
+++ modules/proxy/proxy_util.c  (working copy)
@@ -4527,7 +4527,7 @@
                 }
             }

-            if (pfd->rtnevents & (APR_POLLIN | APR_POLLHUP)
+            if (pfd->rtnevents & pfd->reqevents & (APR_POLLIN | APR_POLLHUP)
                     || (tc->readable && tc->other->writable
                         && ap_filter_input_pending(tc->c) == OK)) {
                 struct proxy_tunnel_conn *in = tc, *out = tc->other;


Masking the results first with what we requested breaks the spin.


Regards

RĂ¼diger

Reply via email to