diff options
author | Simo Sorce <idra@samba.org> | 2011-04-20 16:28:57 -0400 |
---|---|---|
committer | Andreas Schneider <asn@samba.org> | 2011-08-10 18:14:03 +0200 |
commit | 4fef4fe4986245bfd0ac0c3a3d3a10d330bae09c (patch) | |
tree | b0db047b976263251836a655eff3cdea3ca4812d /source3/lib | |
parent | 88b901b6ee92ea5eb4420981625fb6a6b299e368 (diff) | |
download | samba-4fef4fe4986245bfd0ac0c3a3d3a10d330bae09c.tar.gz samba-4fef4fe4986245bfd0ac0c3a3d3a10d330bae09c.tar.bz2 samba-4fef4fe4986245bfd0ac0c3a3d3a10d330bae09c.zip |
s3-prefork: implement prefork framework
Primarily built for forked off rpc service daemons, but not tied to rpc
services and generic enough to be used elsewhere easily.
Signed-off-by: Andreas Schneider <asn@samba.org>
Diffstat (limited to 'source3/lib')
-rw-r--r-- | source3/lib/server_prefork.c | 502 | ||||
-rw-r--r-- | source3/lib/server_prefork.h | 76 |
2 files changed, 578 insertions, 0 deletions
diff --git a/source3/lib/server_prefork.c b/source3/lib/server_prefork.c new file mode 100644 index 0000000000..4aa3f482cc --- /dev/null +++ b/source3/lib/server_prefork.c @@ -0,0 +1,502 @@ +/* + Unix SMB/CIFS implementation. + Common server globals + + Copyright (C) Simo Sorce <idra@samba.org> 2011 + + 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 "system/time.h" +#include "system/shmem.h" +#include "system/filesys.h" +#include "server_prefork.h" +#include "../lib/util/util.h" + +struct prefork_pool { + + int listen_fd; + int lock_fd; + + prefork_main_fn_t *main_fn; + void *private_data; + + int pool_size; + struct pf_worker_data *pool; +}; + +int prefork_pool_destructor(struct prefork_pool *pfp) +{ + munmap(pfp->pool, pfp->pool_size * sizeof(struct pf_worker_data)); + return 0; +} + +bool prefork_create_pool(struct tevent_context *ev_ctx, + TALLOC_CTX *mem_ctx, int listen_fd, + int min_children, int max_children, + prefork_main_fn_t *main_fn, void *private_data, + struct prefork_pool **pf_pool) +{ + struct prefork_pool *pfp; + pid_t pid; + time_t now = time(NULL); + size_t data_size; + int ret; + int i; + + pfp = talloc(mem_ctx, struct prefork_pool); + if (!pfp) { + DEBUG(1, ("Out of memory!\n")); + return false; + } + pfp->listen_fd = listen_fd; + pfp->main_fn = main_fn; + pfp->private_data = private_data; + + pfp->lock_fd = create_unlink_tmp(NULL); + if (pfp->lock_fd == -1) { + DEBUG(1, ("Failed to create prefork lock fd!\n")); + talloc_free(pfp); + return false; + } + + pfp->pool_size = max_children; + data_size = sizeof(struct pf_worker_data) * max_children; + + pfp->pool = mmap(NULL, data_size, PROT_READ|PROT_WRITE, + MAP_SHARED|MAP_ANONYMOUS, -1, 0); + if (pfp->pool == MAP_FAILED) { + DEBUG(1, ("Failed to mmap memory for prefork pool!\n")); + talloc_free(pfp); + return false; + } + talloc_set_destructor(pfp, prefork_pool_destructor); + + for (i = 0; i < min_children; i++) { + pid = sys_fork(); + switch (pid) { + case -1: + DEBUG(1, ("Failed to prefork child n. %d !\n", i)); + break; + + case 0: /* THE CHILD */ + + pfp->pool[i].status = PF_WORKER_IDLE; + + ret = pfp->main_fn(ev_ctx, &pfp->pool[i], + pfp->listen_fd, pfp->lock_fd, + pfp->private_data); + exit(ret); + + default: /* THE PARENT */ + pfp->pool[i].pid = pid; + pfp->pool[i].started = now; + break; + } + } + + *pf_pool = pfp; + return true; +} + +int prefork_add_children(struct tevent_context *ev_ctx, + struct prefork_pool *pfp, + int num_children) +{ + pid_t pid; + time_t now = time(NULL); + int ret; + int i, j; + + for (i = 0, j = 0; i < pfp->pool_size && j < num_children; i++) { + + if (pfp->pool[i].status != PF_WORKER_NONE) { + continue; + } + + pid = sys_fork(); + switch (pid) { + case -1: + DEBUG(1, ("Failed to prefork child n. %d !\n", j)); + break; + + case 0: /* THE CHILD */ + + pfp->pool[i].status = PF_WORKER_IDLE; + ret = pfp->main_fn(ev_ctx, &pfp->pool[i], + pfp->listen_fd, pfp->lock_fd, + pfp->private_data); + + pfp->pool[i].status = PF_WORKER_EXITING; + exit(ret); + + default: /* THE PARENT */ + pfp->pool[i].pid = pid; + pfp->pool[i].started = now; + j++; + break; + } + } + + DEBUG(5, ("Added %d children!\n", j)); + + return j; +} + +struct prefork_oldest { + int num; + time_t started; +}; + +/* sort in inverse order */ +static int prefork_sort_oldest(const void *ap, const void *bp) +{ + struct prefork_oldest *a = (struct prefork_oldest *)ap; + struct prefork_oldest *b = (struct prefork_oldest *)bp; + + if (a->started == b->started) { + return 0; + } + if (a->started < b->started) { + return 1; + } + return -1; +} + +int prefork_retire_children(struct prefork_pool *pfp, + int num_children, time_t age_limit) +{ + time_t now = time(NULL); + struct prefork_oldest *oldest; + int i, j; + + oldest = talloc_array(pfp, struct prefork_oldest, pfp->pool_size); + if (!oldest) { + return -1; + } + + for (i = 0; i < pfp->pool_size; i++) { + oldest[i].num = i; + if (pfp->pool[i].status == PF_WORKER_IDLE) { + oldest[i].started = pfp->pool[i].started; + } else { + oldest[i].started = now; + } + } + + qsort(oldest, pfp->pool_size, + sizeof(struct prefork_oldest), + prefork_sort_oldest); + + for (i = 0, j = 0; i < pfp->pool_size && j < num_children; i++) { + if (pfp->pool[i].status == PF_WORKER_IDLE && + pfp->pool[i].started <= age_limit) { + /* tell the child it's time to give up */ + DEBUG(5, ("Retiring pid %d!\n", pfp->pool[i].pid)); + pfp->pool[i].cmds = PF_SRV_MSG_EXIT; + kill(pfp->pool[i].pid, SIGHUP); + j++; + } + } + + return j; +} + +int prefork_count_active_children(struct prefork_pool *pfp, int *total) +{ + int i, a, t; + + a = 0; + t = 0; + for (i = 0; i < pfp->pool_size; i++) { + if (pfp->pool[i].status == PF_WORKER_NONE) { + continue; + } + + t++; + + if (pfp->pool[i].num_clients == 0) { + continue; + } + + a++; + } + + *total = t; + return a; +} + +/* to be used to finally mark a children as dead, so that it's slot can + * be reused */ +bool prefork_mark_pid_dead(struct prefork_pool *pfp, pid_t pid) +{ + int i; + + for (i = 0; i < pfp->pool_size; i++) { + if (pfp->pool[i].pid == pid) { + if (pfp->pool[i].status != PF_WORKER_EXITING) { + DEBUG(2, ("pid %d terminated abnormally!\n", + (int)pid)); + } + + /* reset all fields, + * this makes status = PF_WORK_NONE */ + memset(&pfp->pool[i], 0, + sizeof(struct pf_worker_data)); + + return true; + } + } + + return false; +} + +/* ==== Functions used by children ==== */ + +static SIG_ATOMIC_T pf_alarm; + +static void pf_alarm_cb(int signum) +{ + pf_alarm = 1; +} + + +/* + * Parameters: + * pf - the worker shared data structure + * lock_fd - the file descriptor used for locking + * timeout - expressed in seconds: + * -1 never timeouts, + * 0 timeouts immediately + * N seconds before timing out + * + * Returns values: + * negative errno on fatal error + * 0 on success to acquire lock + * -1 on timeout/lock held by other + * -2 on server msg to terminate + * ERRNO on other errors + */ + +static int prefork_grab_lock(struct pf_worker_data *pf, + int lock_fd, int timeout) +{ + struct flock lock; + int op; + int ret; + + if (pf->cmds == PF_SRV_MSG_EXIT) { + return -2; + } + + pf_alarm = 0; + + if (timeout > 0) { + CatchSignal(SIGALRM, pf_alarm_cb); + alarm(timeout); + } + + if (timeout == 0) { + op = F_SETLK; + } else { + op = F_SETLKW; + } + + ret = 0; + do { + ZERO_STRUCT(lock); + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + + ret = fcntl(lock_fd, op, &lock); + if (ret == 0) break; + + ret = errno; + + if (pf->cmds == PF_SRV_MSG_EXIT) { + ret = -2; + goto done; + } + + switch (ret) { + case EINTR: + break; + + case EACCES: + case EAGAIN: + /* lock held by other proc */ + ret = -1; + goto done; + default: + goto done; + } + + if (pf_alarm == 1) { + /* timed out */ + ret = -1; + goto done; + } + } while (timeout != 0); + + if (ret != 0) { + /* We have the Lock */ + pf->status = PF_WORKER_ACCEPTING; + } + +done: + if (timeout > 0) { + alarm(0); + CatchSignal(SIGALRM, SIG_IGN); + } + + if (ret > 0) { + DEBUG(1, ("Failed to get lock (%d, %s)!\n", + ret, strerror(ret))); + } + return ret; +} + +/* + * Parameters: + * pf - the worker shared data structure + * lock_fd - the file descriptor used for locking + * timeout - expressed in seconds: + * -1 never timeouts, + * 0 timeouts immediately + * N seconds before timing out + * + * Returns values: + * negative errno on fatal error + * 0 on success to release lock + * -1 on timeout + * ERRNO on error + */ + +static int prefork_release_lock(struct pf_worker_data *pf, + int lock_fd, int timeout) +{ + struct flock lock; + int op; + int ret; + + pf_alarm = 0; + + if (timeout > 0) { + CatchSignal(SIGALRM, pf_alarm_cb); + alarm(timeout); + } + + if (timeout == 0) { + op = F_SETLK; + } else { + op = F_SETLKW; + } + + do { + ZERO_STRUCT(lock); + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + + ret = fcntl(lock_fd, op, &lock); + if (ret == 0) break; + + ret = errno; + + if (ret != EINTR) { + goto done; + } + + if (pf_alarm == 1) { + /* timed out */ + ret = -1; + goto done; + } + } while (timeout != 0); + +done: + if (timeout > 0) { + alarm(0); + CatchSignal(SIGALRM, SIG_IGN); + } + + if (ret > 0) { + DEBUG(1, ("Failed to release lock (%d, %s)!\n", + ret, strerror(ret))); + } + return ret; +} + +/* returns: + * negative errno on error + * -2 if server commands to terminate + * 0 if all ok + * ERRNO on other errors + */ + +int prefork_wait_for_client(struct pf_worker_data *pf, + int lock_fd, int listen_fd, + struct sockaddr *addr, + socklen_t *addrlen, int *fd) +{ + int ret; + int sd = -1; + int err; + + ret = prefork_grab_lock(pf, lock_fd, -1); + if (ret != 0) { + return ret; + } + + err = 0; + do { + sd = accept(listen_fd, addr, addrlen); + + if (sd != -1) break; + + if (errno == EINTR) { + if (pf->cmds == PF_SRV_MSG_EXIT) { + err = -2; + } + } else { + err = errno; + } + + } while ((sd == -1) && (err == 0)); + + /* return lock now, even if the accept failed. + * if it takes more than 10 seconds we are in deep trouble */ + ret = prefork_release_lock(pf, lock_fd, 2); + if (ret != 0) { + /* we were unable to release the lock!! */ + DEBUG(0, ("Terminating due to fatal failure!\n")); + + /* Just exit we cannot hold the whole server, better to error + * on this one client and hope it was a transiet problem */ + err = -2; + } + + if (err != 0) { + if (sd != -1) { + close(sd); + sd = -1; + } + return err; + } + + pf->status = PF_WORKER_BUSY; + pf->num_clients++; + *fd = sd; + return 0; +} diff --git a/source3/lib/server_prefork.h b/source3/lib/server_prefork.h new file mode 100644 index 0000000000..d6d7bf95c9 --- /dev/null +++ b/source3/lib/server_prefork.h @@ -0,0 +1,76 @@ +/* + Unix SMB/CIFS implementation. + Common server globals + + Copyright (C) Simo Sorce <idra@samba.org> 2011 + + 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 "system/network.h" +#include <tevent.h> + +enum pf_worker_status { + PF_WORKER_NONE = 0, + PF_WORKER_IDLE, + PF_WORKER_ACCEPTING, + PF_WORKER_BUSY, + PF_WORKER_EXITING +}; + +enum pf_server_cmds { + PF_SRV_MSG_NONE = 0, + PF_SRV_MSG_EXIT +}; + +struct pf_worker_data { + pid_t pid; + enum pf_worker_status status; + enum pf_server_cmds cmds; + time_t started; + time_t last_used; + int num_clients; +}; + +typedef int (prefork_main_fn_t)(struct tevent_context *ev, + struct pf_worker_data *pf, + int listen_fd, + int lock_fd, + void *private_data); + +struct prefork_pool; + + +/* ==== Functions used by controlling process ==== */ + +bool prefork_create_pool(struct tevent_context *ev_ctx, + TALLOC_CTX *mem_ctx, int listen_fd, + int min_children, int max_children, + prefork_main_fn_t *main_fn, void *private_data, + struct prefork_pool **pf_pool); + +int prefork_add_children(struct tevent_context *ev_ctx, + struct prefork_pool *pfp, + int num_children); +int prefork_retire_children(struct prefork_pool *pfp, + int num_children, time_t age_limit); +int prefork_count_active_children(struct prefork_pool *pfp, int *total); +bool prefork_mark_pid_dead(struct prefork_pool *pfp, pid_t pid); + +/* ==== Functions used by children ==== */ + +int prefork_wait_for_client(struct pf_worker_data *pf, + int lock_fd, int listen_fd, + struct sockaddr *addr, + socklen_t *addrlen, int *fd); |