mirror of
https://github.com/haproxy/haproxy.git
synced 2026-03-10 01:50:49 -04:00
Since465a6c8("BUG/MEDIUM: applet: only set appctx->sedesc on successful allocation"), sedesc is attached to the appctx after the task is successfully allocated. If the task fails to allocate: current sedesc cleanup is performed on appctx->sedesc which still points to NULL so sedesc won't be freed. This is fine when sedesc is provided as argument (!=NULL), but leads to memory leaks if sedesc is allocated locally. It was shown in GH #2086 that if sedesc != NULL when passed as argument, it shouldn't be freed on error paths. This is what465a6c8was trying to address. In an attempt to fix both issues at once, we do as Christopher suggested: that is moving sedesc allocation attempt at the end of the function, so that we don't have to free it in case of error, thus removing the ambiguity. (We won't risk freeing a sedesc that does not belong to us) If we fail to allocate sedesc, then the task that was previously created locally is simply destroyed. This needs to be backported to 2.6 with465a6c8("BUG/MEDIUM: applet: only set appctx->sedesc on successful allocation") [Copy pasting the original backport note from Willy: In 2.6 the function is slightly different and called appctx_new(), though the issue is exactly the same.]
279 lines
8.7 KiB
C
279 lines
8.7 KiB
C
/*
|
|
* Functions managing applets
|
|
*
|
|
* Copyright 2000-2015 Willy Tarreau <w@1wt.eu>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <haproxy/api.h>
|
|
#include <haproxy/applet.h>
|
|
#include <haproxy/channel.h>
|
|
#include <haproxy/list.h>
|
|
#include <haproxy/sc_strm.h>
|
|
#include <haproxy/stconn.h>
|
|
#include <haproxy/stream.h>
|
|
#include <haproxy/task.h>
|
|
|
|
unsigned int nb_applets = 0;
|
|
|
|
DECLARE_POOL(pool_head_appctx, "appctx", sizeof(struct appctx));
|
|
|
|
/* Tries to allocate a new appctx and initialize all of its fields. The appctx
|
|
* is returned on success, NULL on failure. The appctx must be released using
|
|
* appctx_free(). <applet> is assigned as the applet, but it can be NULL. <thr>
|
|
* is the thread ID to start the applet on, and a negative value allows the
|
|
* applet to start anywhere. Backend applets may only be created on the current
|
|
* thread.
|
|
*/
|
|
struct appctx *appctx_new_on(struct applet *applet, struct sedesc *sedesc, int thr)
|
|
{
|
|
struct appctx *appctx;
|
|
|
|
/* Backend appctx cannot be started on another thread than the local one */
|
|
BUG_ON(thr != tid && sedesc);
|
|
|
|
appctx = pool_zalloc(pool_head_appctx);
|
|
if (unlikely(!appctx))
|
|
goto fail_appctx;
|
|
|
|
LIST_INIT(&appctx->wait_entry);
|
|
appctx->obj_type = OBJ_TYPE_APPCTX;
|
|
appctx->applet = applet;
|
|
appctx->sess = NULL;
|
|
|
|
appctx->t = task_new_on(thr);
|
|
if (unlikely(!appctx->t))
|
|
goto fail_task;
|
|
|
|
if (!sedesc) {
|
|
sedesc = sedesc_new();
|
|
if (unlikely(!sedesc))
|
|
goto fail_endp;
|
|
sedesc->se = appctx;
|
|
se_fl_set(sedesc, SE_FL_T_APPLET | SE_FL_ORPHAN);
|
|
}
|
|
|
|
appctx->sedesc = sedesc;
|
|
appctx->t->process = task_run_applet;
|
|
appctx->t->context = appctx;
|
|
|
|
LIST_INIT(&appctx->buffer_wait.list);
|
|
appctx->buffer_wait.target = appctx;
|
|
appctx->buffer_wait.wakeup_cb = appctx_buf_available;
|
|
|
|
_HA_ATOMIC_INC(&nb_applets);
|
|
return appctx;
|
|
|
|
fail_endp:
|
|
task_destroy(appctx->t);
|
|
fail_task:
|
|
pool_free(pool_head_appctx, appctx);
|
|
fail_appctx:
|
|
return NULL;
|
|
}
|
|
|
|
/* Finalize the frontend appctx startup. It must not be called for a backend
|
|
* appctx. This function is responsible to create the appctx's session and the
|
|
* frontend stream connector. By transitivity, the stream is also created.
|
|
*
|
|
* It returns 0 on success and -1 on error. In this case, it is the caller
|
|
* responsibility to release the appctx. However, the session is released if it
|
|
* was created. On success, if an error is encountered in the caller function,
|
|
* the stream must be released instead of the appctx. To be sure,
|
|
* appctx_free_on_early_error() must be called in this case.
|
|
*/
|
|
int appctx_finalize_startup(struct appctx *appctx, struct proxy *px, struct buffer *input)
|
|
{
|
|
struct session *sess;
|
|
|
|
/* async startup is only possible for frontend appctx. Thus for orphan
|
|
* appctx. Because no backend appctx can be orphan.
|
|
*/
|
|
BUG_ON(!se_fl_test(appctx->sedesc, SE_FL_ORPHAN));
|
|
|
|
sess = session_new(px, NULL, &appctx->obj_type);
|
|
if (!sess)
|
|
return -1;
|
|
if (!sc_new_from_endp(appctx->sedesc, sess, input)) {
|
|
session_free(sess);
|
|
return -1;
|
|
}
|
|
appctx->sess = sess;
|
|
return 0;
|
|
}
|
|
|
|
/* Release function to call when an error occurred during init stage of a
|
|
* frontend appctx. For a backend appctx, it just calls appctx_free()
|
|
*/
|
|
void appctx_free_on_early_error(struct appctx *appctx)
|
|
{
|
|
/* If a frontend appctx is attached to a stream connector, release the stream
|
|
* instead of the appctx.
|
|
*/
|
|
if (!se_fl_test(appctx->sedesc, SE_FL_ORPHAN) && !(appctx_sc(appctx)->flags & SC_FL_ISBACK)) {
|
|
stream_free(appctx_strm(appctx));
|
|
return;
|
|
}
|
|
appctx_free(appctx);
|
|
}
|
|
|
|
/* reserves a command context of at least <size> bytes in the <appctx>, for
|
|
* use by a CLI command or any regular applet. The pointer to this context is
|
|
* stored in ctx.svcctx and is returned. The caller doesn't need to release
|
|
* it as it's allocated from reserved space. If the size is larger than
|
|
* APPLET_MAX_SVCCTX a crash will occur (hence that will never happen outside
|
|
* of development).
|
|
*
|
|
* Note that the command does *not* initialize the area, so that it can easily
|
|
* be used upon each entry in a function. It's left to the initialization code
|
|
* to do it if needed. The CLI will always zero the whole area before calling
|
|
* a keyword's ->parse() function.
|
|
*/
|
|
void *applet_reserve_svcctx(struct appctx *appctx, size_t size)
|
|
{
|
|
BUG_ON(size > APPLET_MAX_SVCCTX);
|
|
appctx->svcctx = &appctx->svc.storage;
|
|
return appctx->svcctx;
|
|
}
|
|
|
|
/* This is used to reset an svcctx and the svc.storage without releasing the
|
|
* appctx. In fact this is only used by the CLI applet between commands.
|
|
*/
|
|
void applet_reset_svcctx(struct appctx *appctx)
|
|
{
|
|
memset(&appctx->svc.storage, 0, APPLET_MAX_SVCCTX);
|
|
appctx->svcctx = NULL;
|
|
}
|
|
|
|
/* call the applet's release() function if any, and marks the sedesc as shut.
|
|
* Needs to be called upon close().
|
|
*/
|
|
void appctx_shut(struct appctx *appctx)
|
|
{
|
|
if (se_fl_test(appctx->sedesc, SE_FL_SHR | SE_FL_SHW))
|
|
return;
|
|
|
|
if (appctx->applet->release)
|
|
appctx->applet->release(appctx);
|
|
|
|
se_fl_set(appctx->sedesc, SE_FL_SHRR | SE_FL_SHWN);
|
|
}
|
|
|
|
/* Callback used to wake up an applet when a buffer is available. The applet
|
|
* <appctx> is woken up if an input buffer was requested for the associated
|
|
* stream connector. In this case the buffer is immediately allocated and the
|
|
* function returns 1. Otherwise it returns 0. Note that this automatically
|
|
* covers multiple wake-up attempts by ensuring that the same buffer will not
|
|
* be accounted for multiple times.
|
|
*/
|
|
int appctx_buf_available(void *arg)
|
|
{
|
|
struct appctx *appctx = arg;
|
|
struct stconn *sc = appctx_sc(appctx);
|
|
|
|
/* allocation requested ? */
|
|
if (!(sc->flags & SC_FL_NEED_BUFF))
|
|
return 0;
|
|
|
|
sc_have_buff(sc);
|
|
|
|
/* was already allocated another way ? if so, don't take this one */
|
|
if (c_size(sc_ic(sc)) || sc_ic(sc)->pipe)
|
|
return 0;
|
|
|
|
/* allocation possible now ? */
|
|
if (!b_alloc(&sc_ic(sc)->buf)) {
|
|
sc_need_buff(sc);
|
|
return 0;
|
|
}
|
|
|
|
task_wakeup(appctx->t, TASK_WOKEN_RES);
|
|
return 1;
|
|
}
|
|
|
|
/* Default applet handler */
|
|
struct task *task_run_applet(struct task *t, void *context, unsigned int state)
|
|
{
|
|
struct appctx *app = context;
|
|
struct stconn *sc;
|
|
unsigned int rate;
|
|
size_t count;
|
|
|
|
if (app->state & APPLET_WANT_DIE) {
|
|
__appctx_free(app);
|
|
return NULL;
|
|
}
|
|
|
|
if (se_fl_test(app->sedesc, SE_FL_ORPHAN)) {
|
|
/* Finalize init of orphan appctx. .init callback function must
|
|
* be defined and it must finalize appctx startup.
|
|
*/
|
|
BUG_ON(!app->applet->init);
|
|
|
|
if (appctx_init(app) == -1) {
|
|
appctx_free_on_early_error(app);
|
|
return NULL;
|
|
}
|
|
BUG_ON(!app->sess || !appctx_sc(app) || !appctx_strm(app));
|
|
}
|
|
|
|
sc = appctx_sc(app);
|
|
|
|
/* We always pretend the applet can't get and doesn't want to
|
|
* put, it's up to it to change this if needed. This ensures
|
|
* that one applet which ignores any event will not spin.
|
|
*/
|
|
applet_need_more_data(app);
|
|
applet_have_no_more_data(app);
|
|
|
|
/* 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
|
|
* some other processing if needed. The applet doesn't have anything to
|
|
* do if it needs the buffer, it will be called again upon readiness.
|
|
*/
|
|
if (!sc_alloc_ibuf(sc, &app->buffer_wait))
|
|
applet_have_more_data(app);
|
|
|
|
count = co_data(sc_oc(sc));
|
|
app->applet->fct(app);
|
|
|
|
/* now check if the applet has released some room and forgot to
|
|
* notify the other side about it.
|
|
*/
|
|
if (count != co_data(sc_oc(sc))) {
|
|
sc_oc(sc)->flags |= CF_WRITE_EVENT | CF_WROTE_DATA;
|
|
sc_have_room(sc_opposite(sc));
|
|
}
|
|
|
|
if (sc_ic(sc)->flags & CF_READ_EVENT)
|
|
sc_ep_report_read_activity(sc);
|
|
|
|
if (channel_is_empty(sc_oc(sc)))
|
|
sc_ep_report_send_activity(sc);
|
|
else
|
|
sc_ep_report_blocked_send(sc);
|
|
|
|
/* measure the call rate and check for anomalies when too high */
|
|
if (((b_size(sc_ib(sc)) && sc->flags & SC_FL_NEED_BUFF) || // asks for a buffer which is present
|
|
(b_size(sc_ib(sc)) && !b_data(sc_ib(sc)) && sc->flags & SC_FL_NEED_ROOM) || // asks for room in an empty buffer
|
|
(b_data(sc_ob(sc)) && sc_is_send_allowed(sc)) || // asks for data already present
|
|
(!b_data(sc_ib(sc)) && b_data(sc_ob(sc)) && // didn't return anything ...
|
|
(sc_oc(sc)->flags & (CF_WRITE_EVENT|CF_SHUTW_NOW)) == CF_SHUTW_NOW))) { // ... and left data pending after a shut
|
|
rate = update_freq_ctr(&app->call_rate, 1);
|
|
if (rate >= 100000 && app->call_rate.prev_ctr) // looped like this more than 100k times over last second
|
|
stream_dump_and_crash(&app->obj_type, read_freq_ctr(&app->call_rate));
|
|
}
|
|
|
|
sc->app_ops->wake(sc);
|
|
channel_release_buffer(sc_ic(sc), &app->buffer_wait);
|
|
return t;
|
|
}
|