mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-07 07:37:02 +02:00
BUG/MEDIUM: applet: always check a fast running applet's activity before killing
In issue #277 is reported a strange problem related to a fast-spinning applet which seems to show valid progress being made. It's uncertain how this can happen, maybe some very specific timing patterns manage to place just a few bytes in each buffer and result in the peers applet being called a lot. But it appears possible to artificially cross the spinning threshold by asking for monster stats page (500 MB) and limiting the send() size to 1 MSS (1460 bytes), causing the stats page to be called for very small blocks which most often do not leave enough room to place a new chunk. The idea developed in this patch consists in not crashing for an applet which reaches a very high call rate if it shows some indication of progress. Detecting progress on applets is not trivial but in our case we know that they must at least not claim to wait for a buffer allocation if this buffer is present, wait for room if the buffer is empty, ask for more data without polling if such data are still present, nor leave with an empty input buffer without having written anything nor read anything from the other side while a shutw is pending. Doing so doesn't affect normal behaviors nor abuses of our existing applets and does at least protect against an applet performing an early return without processing events, or one causing an endless loop by asking for impossible conditions. This must be backported to 2.0.
This commit is contained in:
parent
d89331ecb5
commit
19920d6fc9
18
src/applet.c
18
src/applet.c
@ -76,12 +76,6 @@ struct task *task_run_applet(struct task *t, void *context, unsigned short state
|
||||
si_cant_get(si);
|
||||
si_rx_endp_done(si);
|
||||
|
||||
/* measure the call rate */
|
||||
rate = update_freq_ctr(&app->call_rate, 1);
|
||||
if (rate >= 100000 && app->call_rate.prev_ctr) { // make sure to wait at least a full second
|
||||
stream_dump_and_crash(&app->obj_type, read_freq_ctr(&app->call_rate));
|
||||
}
|
||||
|
||||
/* Now we'll try to allocate the input buffer. We wake up the applet in
|
||||
* all cases. So this is the applet's responsibility to check if this
|
||||
* buffer was allocated or not. This leaves a chance for applets to do
|
||||
@ -92,6 +86,18 @@ struct task *task_run_applet(struct task *t, void *context, unsigned short state
|
||||
si_rx_endp_more(si);
|
||||
|
||||
app->applet->fct(app);
|
||||
|
||||
/* measure the call rate and check for anomalies when too high */
|
||||
rate = update_freq_ctr(&app->call_rate, 1);
|
||||
if (rate >= 100000 && app->call_rate.prev_ctr && // looped more than 100k times over last second
|
||||
((b_size(si_ib(si)) && si->flags & SI_FL_RXBLK_BUFF) || // asks for a buffer which is present
|
||||
(b_size(si_ib(si)) && !b_data(si_ib(si)) && si->flags & SI_FL_RXBLK_ROOM) || // asks for room in an empty buffer
|
||||
(b_data(si_ob(si)) && si_tx_endp_ready(si) && !si_tx_blocked(si)) || // asks for data already present
|
||||
(!b_data(si_ib(si)) && b_data(si_ob(si)) && // didn't return anything ...
|
||||
(si_oc(si)->flags & (CF_WRITE_PARTIAL|CF_SHUTW_NOW)) == CF_SHUTW_NOW))) { // ... and left data pending after a shut
|
||||
stream_dump_and_crash(&app->obj_type, read_freq_ctr(&app->call_rate));
|
||||
}
|
||||
|
||||
si_applet_wake_cb(si);
|
||||
channel_release_buffer(si_ic(si), &app->buffer_wait);
|
||||
return t;
|
||||
|
Loading…
Reference in New Issue
Block a user