mirror of
https://github.com/samba-team/samba.git
synced 2024-12-25 23:21:54 +03:00
c5d07df6ab
This can avoid syscalls when multiple jobs are finished simultaneously Signed-off-by: Volker Lendecke <vl@samba.org> Reviewed-by: Jeremy Allison <jra@samba.org>
333 lines
7.8 KiB
C
333 lines
7.8 KiB
C
/*
|
|
* Unix SMB/CIFS implementation.
|
|
* Async fn calls
|
|
* Copyright (C) Volker Lendecke 2009
|
|
*
|
|
* 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 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
#include "../lib/util/tevent_unix.h"
|
|
|
|
#include "lib/pthreadpool/pthreadpool.h"
|
|
|
|
struct fncall_state {
|
|
struct fncall_context *ctx;
|
|
int job_id;
|
|
bool done;
|
|
|
|
void *private_parent;
|
|
void *job_private;
|
|
};
|
|
|
|
struct fncall_context {
|
|
struct pthreadpool *pool;
|
|
int next_job_id;
|
|
int sig_fd;
|
|
struct tevent_req **pending;
|
|
|
|
struct fncall_state **orphaned;
|
|
int num_orphaned;
|
|
|
|
struct tevent_fd *fde;
|
|
};
|
|
|
|
static void fncall_handler(struct tevent_context *ev, struct tevent_fd *fde,
|
|
uint16_t flags, void *private_data);
|
|
|
|
static int fncall_context_destructor(struct fncall_context *ctx)
|
|
{
|
|
while (talloc_array_length(ctx->pending) != 0) {
|
|
/* No TALLOC_FREE here */
|
|
talloc_free(ctx->pending[0]);
|
|
}
|
|
|
|
while (ctx->num_orphaned != 0) {
|
|
/*
|
|
* We've got jobs in the queue for which the tevent_req has
|
|
* been finished already. Wait for all of them to finish.
|
|
*/
|
|
fncall_handler(NULL, NULL, TEVENT_FD_READ, ctx);
|
|
}
|
|
|
|
pthreadpool_destroy(ctx->pool);
|
|
ctx->pool = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct fncall_context *fncall_context_init(TALLOC_CTX *mem_ctx,
|
|
int max_threads)
|
|
{
|
|
struct fncall_context *ctx;
|
|
int ret;
|
|
|
|
ctx = talloc_zero(mem_ctx, struct fncall_context);
|
|
if (ctx == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
ret = pthreadpool_init(max_threads, &ctx->pool);
|
|
if (ret != 0) {
|
|
TALLOC_FREE(ctx);
|
|
return NULL;
|
|
}
|
|
talloc_set_destructor(ctx, fncall_context_destructor);
|
|
|
|
ctx->sig_fd = pthreadpool_signal_fd(ctx->pool);
|
|
if (ctx->sig_fd == -1) {
|
|
TALLOC_FREE(ctx);
|
|
return NULL;
|
|
}
|
|
|
|
return ctx;
|
|
}
|
|
|
|
static int fncall_next_job_id(struct fncall_context *ctx)
|
|
{
|
|
int num_pending = talloc_array_length(ctx->pending);
|
|
int result;
|
|
|
|
while (true) {
|
|
int i;
|
|
|
|
result = ctx->next_job_id++;
|
|
if (result == 0) {
|
|
continue;
|
|
}
|
|
|
|
for (i=0; i<num_pending; i++) {
|
|
struct fncall_state *state = tevent_req_data(
|
|
ctx->pending[i], struct fncall_state);
|
|
|
|
if (result == state->job_id) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == num_pending) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void fncall_unset_pending(struct tevent_req *req);
|
|
static void fncall_cleanup(struct tevent_req *req,
|
|
enum tevent_req_state req_state);
|
|
|
|
static bool fncall_set_pending(struct tevent_req *req,
|
|
struct fncall_context *ctx,
|
|
struct tevent_context *ev)
|
|
{
|
|
struct tevent_req **pending;
|
|
int num_pending, orphaned_array_length;
|
|
|
|
num_pending = talloc_array_length(ctx->pending);
|
|
|
|
pending = talloc_realloc(ctx, ctx->pending, struct tevent_req *,
|
|
num_pending+1);
|
|
if (pending == NULL) {
|
|
return false;
|
|
}
|
|
pending[num_pending] = req;
|
|
num_pending += 1;
|
|
ctx->pending = pending;
|
|
tevent_req_set_cleanup_fn(req, fncall_cleanup);
|
|
|
|
/*
|
|
* Make sure that the orphaned array of fncall_state structs has
|
|
* enough space. A job can change from pending to orphaned in
|
|
* fncall_cleanup, and to fail in a talloc destructor should be
|
|
* avoided if possible.
|
|
*/
|
|
|
|
orphaned_array_length = talloc_array_length(ctx->orphaned);
|
|
if (num_pending > orphaned_array_length) {
|
|
struct fncall_state **orphaned;
|
|
|
|
orphaned = talloc_realloc(ctx, ctx->orphaned,
|
|
struct fncall_state *,
|
|
orphaned_array_length + 1);
|
|
if (orphaned == NULL) {
|
|
fncall_unset_pending(req);
|
|
return false;
|
|
}
|
|
ctx->orphaned = orphaned;
|
|
}
|
|
|
|
if (ctx->fde != NULL) {
|
|
return true;
|
|
}
|
|
|
|
ctx->fde = tevent_add_fd(ev, ctx->pending, ctx->sig_fd, TEVENT_FD_READ,
|
|
fncall_handler, ctx);
|
|
if (ctx->fde == NULL) {
|
|
fncall_unset_pending(req);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void fncall_unset_pending(struct tevent_req *req)
|
|
{
|
|
struct fncall_state *state = tevent_req_data(req, struct fncall_state);
|
|
struct fncall_context *ctx = state->ctx;
|
|
int num_pending = talloc_array_length(ctx->pending);
|
|
int i;
|
|
|
|
tevent_req_set_cleanup_fn(req, NULL);
|
|
|
|
if (num_pending == 1) {
|
|
TALLOC_FREE(ctx->fde);
|
|
TALLOC_FREE(ctx->pending);
|
|
return;
|
|
}
|
|
|
|
for (i=0; i<num_pending; i++) {
|
|
if (req == ctx->pending[i]) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == num_pending) {
|
|
return;
|
|
}
|
|
if (num_pending > 1) {
|
|
ctx->pending[i] = ctx->pending[num_pending-1];
|
|
}
|
|
ctx->pending = talloc_realloc(NULL, ctx->pending, struct tevent_req *,
|
|
num_pending - 1);
|
|
}
|
|
|
|
static void fncall_cleanup(struct tevent_req *req,
|
|
enum tevent_req_state req_state)
|
|
{
|
|
struct fncall_state *state = tevent_req_data(
|
|
req, struct fncall_state);
|
|
struct fncall_context *ctx = state->ctx;
|
|
|
|
switch (req_state) {
|
|
case TEVENT_REQ_RECEIVED:
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
fncall_unset_pending(req);
|
|
|
|
if (state->done) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Keep around the state of the deleted request until the request has
|
|
* finished in the helper thread. fncall_handler will destroy it.
|
|
*/
|
|
ctx->orphaned[ctx->num_orphaned] = talloc_move(ctx->orphaned, &state);
|
|
ctx->num_orphaned += 1;
|
|
}
|
|
|
|
struct tevent_req *fncall_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
|
|
struct fncall_context *ctx,
|
|
void (*fn)(void *private_data),
|
|
void *private_data)
|
|
{
|
|
struct tevent_req *req;
|
|
struct fncall_state *state;
|
|
int ret;
|
|
|
|
req = tevent_req_create(mem_ctx, &state, struct fncall_state);
|
|
if (req == NULL) {
|
|
return NULL;
|
|
}
|
|
state->ctx = ctx;
|
|
state->job_id = fncall_next_job_id(state->ctx);
|
|
state->done = false;
|
|
|
|
/*
|
|
* We need to keep the private data we handed out to the thread around
|
|
* as long as the job is not finished. This is a bit of an abstraction
|
|
* violation, because the "req->state1->subreq->state2" (we're
|
|
* "subreq", "req" is the request our caller creates) is broken to
|
|
* "ctx->state2->state1", but we are right now in the destructor for
|
|
* "subreq2", so what can we do. We need to keep state1 around,
|
|
* otherwise the helper thread will have no place to put its results.
|
|
*/
|
|
|
|
state->private_parent = talloc_parent(private_data);
|
|
state->job_private = talloc_move(state, &private_data);
|
|
|
|
ret = pthreadpool_add_job(state->ctx->pool, state->job_id, fn,
|
|
state->job_private);
|
|
if (ret == -1) {
|
|
tevent_req_error(req, errno);
|
|
return tevent_req_post(req, ev);
|
|
}
|
|
if (!fncall_set_pending(req, state->ctx, ev)) {
|
|
tevent_req_oom(req);
|
|
return tevent_req_post(req, ev);
|
|
}
|
|
return req;
|
|
}
|
|
|
|
static void fncall_handler(struct tevent_context *ev, struct tevent_fd *fde,
|
|
uint16_t flags, void *private_data)
|
|
{
|
|
struct fncall_context *ctx = talloc_get_type_abort(
|
|
private_data, struct fncall_context);
|
|
int i, num_pending;
|
|
int job_id;
|
|
|
|
if (pthreadpool_finished_jobs(ctx->pool, &job_id, 1) < 0) {
|
|
return;
|
|
}
|
|
|
|
num_pending = talloc_array_length(ctx->pending);
|
|
|
|
for (i=0; i<num_pending; i++) {
|
|
struct fncall_state *state = tevent_req_data(
|
|
ctx->pending[i], struct fncall_state);
|
|
|
|
if (job_id == state->job_id) {
|
|
state->done = true;
|
|
talloc_move(state->private_parent,
|
|
&state->job_private);
|
|
tevent_req_done(ctx->pending[i]);
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (i=0; i<ctx->num_orphaned; i++) {
|
|
if (job_id == ctx->orphaned[i]->job_id) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == ctx->num_orphaned) {
|
|
return;
|
|
}
|
|
|
|
TALLOC_FREE(ctx->orphaned[i]);
|
|
|
|
if (i < ctx->num_orphaned-1) {
|
|
ctx->orphaned[i] = ctx->orphaned[ctx->num_orphaned-1];
|
|
}
|
|
ctx->num_orphaned -= 1;
|
|
}
|
|
|
|
int fncall_recv(struct tevent_req *req, int *perr)
|
|
{
|
|
if (tevent_req_is_unix_error(req, perr)) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|