mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-02-01 02:52:00 +00:00
a70bfaaf8b
Commit0c7a4b6
("MINOR: tasks: Don't set the TASK_RUNNING flag when adding in the tasklet list.") revealed a hole in the way tasks may be freed : they could be removed while in the run queue when the TASK_QUEUED flag was present but not the TASK_RUNNING one. But it seems the issue was emphasized by commitcde7902
("MEDIUM: tasks: improve fairness between the local and global queues") though the code it replaces was already affected given how late the TASK_RUNNING flag was set after removal from the global queue. At the moment the task is picked from the global run queue, if it is the last one, the global run queue lock is dropped, and then the TASK_RUNNING flag was added. In the mean time another thread might have performed a task_free(), and immediately after, the TASK_RUNNING flag was re-added to the task, which was then added to the tasklet list. The unprotected window was extremely faint but does definitely exist and inconsistent task lists have been observed a few times during very intensive tests over the last few days. From this point various options are possible, the task might have been re-allocated while running, and assigned state 0 and/or state QUEUED while it was still running, resulting in the tast not being put back into the tree. This commit simply makes sure that tests on TASK_RUNNING before removing the task also cover TASK_QUEUED. It must be backported to 1.9 along with the previous ones touching that area.
131 lines
3.8 KiB
C
131 lines
3.8 KiB
C
/*
|
|
* include/proto/applet.h
|
|
* This file contains applet function prototypes
|
|
*
|
|
* Copyright (C) 2000-2015 Willy Tarreau - w@1wt.eu
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation, version 2.1
|
|
* exclusively.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#ifndef _PROTO_APPLET_H
|
|
#define _PROTO_APPLET_H
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <common/config.h>
|
|
#include <common/mini-clist.h>
|
|
#include <types/applet.h>
|
|
#include <proto/connection.h>
|
|
#include <proto/task.h>
|
|
|
|
extern unsigned int nb_applets;
|
|
|
|
struct task *task_run_applet(struct task *t, void *context, unsigned short state);
|
|
|
|
int appctx_buf_available(void *arg);
|
|
|
|
|
|
/* Initializes all required fields for a new appctx. Note that it does the
|
|
* minimum acceptable initialization for an appctx. This means only the
|
|
* 3 integer states st0, st1, st2 and the chunk used to gather unfinished
|
|
* commands are zeroed
|
|
*/
|
|
static inline void appctx_init(struct appctx *appctx, unsigned long thread_mask)
|
|
{
|
|
appctx->st0 = appctx->st1 = appctx->st2 = 0;
|
|
appctx->chunk = NULL;
|
|
appctx->io_release = NULL;
|
|
appctx->thread_mask = thread_mask;
|
|
appctx->state = 0;
|
|
}
|
|
|
|
/* Tries to allocate a new appctx and initialize its main fields. The appctx
|
|
* is returned on success, NULL on failure. The appctx must be released using
|
|
* pool_free(connection) or appctx_free(), since it's allocated from the
|
|
* connection pool. <applet> is assigned as the applet, but it can be NULL.
|
|
*/
|
|
static inline struct appctx *appctx_new(struct applet *applet, unsigned long thread_mask)
|
|
{
|
|
struct appctx *appctx;
|
|
|
|
appctx = pool_alloc(pool_head_connection);
|
|
if (likely(appctx != NULL)) {
|
|
appctx->obj_type = OBJ_TYPE_APPCTX;
|
|
appctx->applet = applet;
|
|
appctx_init(appctx, thread_mask);
|
|
appctx->t = task_new(thread_mask);
|
|
if (unlikely(appctx->t == NULL)) {
|
|
pool_free(pool_head_connection, appctx);
|
|
return NULL;
|
|
}
|
|
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_ADD(&nb_applets, 1);
|
|
}
|
|
return appctx;
|
|
}
|
|
|
|
/* Releases an appctx previously allocated by appctx_new(). Note that
|
|
* we share the connection pool.
|
|
*/
|
|
static inline void __appctx_free(struct appctx *appctx)
|
|
{
|
|
task_delete(appctx->t);
|
|
task_free(appctx->t);
|
|
if (!LIST_ISEMPTY(&appctx->buffer_wait.list)) {
|
|
HA_SPIN_LOCK(BUF_WQ_LOCK, &buffer_wq_lock);
|
|
LIST_DEL(&appctx->buffer_wait.list);
|
|
LIST_INIT(&appctx->buffer_wait.list);
|
|
HA_SPIN_UNLOCK(BUF_WQ_LOCK, &buffer_wq_lock);
|
|
}
|
|
|
|
pool_free(pool_head_connection, appctx);
|
|
_HA_ATOMIC_SUB(&nb_applets, 1);
|
|
}
|
|
|
|
static inline void appctx_free(struct appctx *appctx)
|
|
{
|
|
/* The task is supposed to be run on this thread, so we can just
|
|
* check if it's running already (or about to run) or not
|
|
*/
|
|
if (!(appctx->t->state & (TASK_QUEUED | TASK_RUNNING)))
|
|
__appctx_free(appctx);
|
|
else {
|
|
/* if it's running, or about to run, defer the freeing
|
|
* until the callback is called.
|
|
*/
|
|
appctx->state |= APPLET_WANT_DIE;
|
|
task_wakeup(appctx->t, TASK_WOKEN_OTHER);
|
|
}
|
|
}
|
|
|
|
/* wakes up an applet when conditions have changed */
|
|
static inline void appctx_wakeup(struct appctx *appctx)
|
|
{
|
|
task_wakeup(appctx->t, TASK_WOKEN_OTHER);
|
|
}
|
|
|
|
#endif /* _PROTO_APPLET_H */
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-indent-level: 8
|
|
* c-basic-offset: 8
|
|
* End:
|
|
*/
|