s3-prefork: implement prefork framework
authorSimo Sorce <idra@samba.org>
Wed, 20 Apr 2011 20:28:57 +0000 (16:28 -0400)
committerAndreas Schneider <asn@samba.org>
Wed, 10 Aug 2011 16:14:03 +0000 (18:14 +0200)
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>
source3/Makefile.in
source3/lib/server_prefork.c [new file with mode: 0644]
source3/lib/server_prefork.h [new file with mode: 0644]
source3/wscript_build

index 5fa848c91837fae25249e7eea1cc167abcc8034a..4a79f97e75358a827bdfd16e175aef978a916c2d 100644 (file)
@@ -465,6 +465,7 @@ LIB_OBJ = $(LIBSAMBAUTIL_OBJ) $(UTIL_OBJ) $(CRYPTO_OBJ) $(LIBTSOCKET_OBJ) \
          lib/module.o lib/events.o @LIBTEVENT_OBJ0@ \
          @CCAN_OBJ@ \
          lib/server_contexts.o \
+         lib/server_prefork.o \
          lib/ldap_escape.o @CHARSET_STATIC@ \
          ../libcli/security/secdesc.o ../libcli/security/access_check.o \
          ../libcli/security/secace.o ../libcli/security/object_tree.o \
diff --git a/source3/lib/server_prefork.c b/source3/lib/server_prefork.c
new file mode 100644 (file)
index 0000000..4aa3f48
--- /dev/null
@@ -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 (file)
index 0000000..d6d7bf9
--- /dev/null
@@ -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);
index 36e8c5068c5581ce9601edad908ebc81a76e674d..2ad6e8d3792d148eed3917dc9769059e51b73b3c 100755 (executable)
@@ -81,6 +81,7 @@ LIB_SRC = '''
           lib/sessionid_tdb.c
           lib/module.c lib/events.c
           lib/server_contexts.c
+          lib/server_prefork.c
           lib/ldap_escape.c
           lib/fncall.c
           libads/krb5_errs.c lib/system_smbd.c lib/audit.c