diff options
-rw-r--r-- | docs-xml/smbdotconf/base/preforkchildren.xml | 24 | ||||
-rw-r--r-- | lib/param/loadparm.c | 2 | ||||
-rw-r--r-- | source3/param/loadparm.c | 1 | ||||
-rw-r--r-- | source4/smbd/process_prefork.c | 391 | ||||
-rw-r--r-- | source4/smbd/wscript_build | 7 |
5 files changed, 425 insertions, 0 deletions
diff --git a/docs-xml/smbdotconf/base/preforkchildren.xml b/docs-xml/smbdotconf/base/preforkchildren.xml new file mode 100644 index 00000000000..720e43909cb --- /dev/null +++ b/docs-xml/smbdotconf/base/preforkchildren.xml @@ -0,0 +1,24 @@ +<samba:parameter name="prefork children" + context="G" + type="integer" + xmlns:samba="http://www.samba.org/samba/DTD/samba-doc"> +<description> + <para>This option controls the number of worker processes that are + started for each service when prefork process model is enabled. + The prefork children are only started for those services that + support prefork (currently only ldap). For processes that don't + support preforking all requests are handled by a single process + for that service. + </para> + + <para>This should be set to a small multiple of the number of CPU's + available on the server</para> + + <para>Additionally the number of prefork children can be specified for + an individual service by using "prefork children: service name" + i.e. "prefork children:ldap = 8" to set the number of ldap + worker processes.</para> +</description> + +<value type="default">1</value> +</samba:parameter> diff --git a/lib/param/loadparm.c b/lib/param/loadparm.c index a74a8734271..a1adb99b1f4 100644 --- a/lib/param/loadparm.c +++ b/lib/param/loadparm.c @@ -2993,6 +2993,8 @@ struct loadparm_context *loadparm_init(TALLOC_CTX *mem_ctx) "rpc server dynamic port range", "49152-65535"); + lpcfg_do_global_parameter(lp_ctx, "prefork children", "1"); + for (i = 0; parm_table[i].label; i++) { if (!(lp_ctx->flags[i] & FLAG_CMDLINE)) { lp_ctx->flags[i] |= FLAG_DEFAULT; diff --git a/source3/param/loadparm.c b/source3/param/loadparm.c index 02c3eb661aa..485d3f75b04 100644 --- a/source3/param/loadparm.c +++ b/source3/param/loadparm.c @@ -943,6 +943,7 @@ static void init_globals(struct loadparm_context *lp_ctx, bool reinit_globals) "49152-65535"); Globals.rpc_low_port = SERVER_TCP_LOW_PORT; Globals.rpc_high_port = SERVER_TCP_HIGH_PORT; + Globals.prefork_children = 1; /* Now put back the settings that were set with lp_set_cmdline() */ apply_lp_set_cmdline(); diff --git a/source4/smbd/process_prefork.c b/source4/smbd/process_prefork.c new file mode 100644 index 00000000000..f2033e96146 --- /dev/null +++ b/source4/smbd/process_prefork.c @@ -0,0 +1,391 @@ +/* + Unix SMB/CIFS implementation. + + process model: prefork (n client connections per process) + + Copyright (C) Andrew Tridgell 1992-2005 + Copyright (C) James J Myers 2003 <myersjj@samba.org> + Copyright (C) Stefan (metze) Metzmacher 2004 + Copyright (C) Andrew Bartlett 2008 <abartlet@samba.org> + Copyright (C) David Disseldorp 2008 <ddiss@sgi.com> + + 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/events/events.h" +#include "lib/messaging/messaging.h" +#include "lib/socket/socket.h" +#include "smbd/process_model.h" +#include "cluster/cluster.h" +#include "param/param.h" +#include "ldb_wrap.h" +#include "lib/util/tfork.h" + +NTSTATUS process_model_prefork_init(void); + +static void sighup_signal_handler(struct tevent_context *ev, + struct tevent_signal *se, + int signum, int count, void *siginfo, + void *private_data) +{ + debug_schedule_reopen_logs(); +} + +static void sigterm_signal_handler(struct tevent_context *ev, + struct tevent_signal *se, + int signum, int count, void *siginfo, + void *private_data) +{ +#if HAVE_GETPGRP + if (getpgrp() == getpid()) { + /* + * We're the process group leader, send + * SIGTERM to our process group. + */ + DBG_NOTICE("SIGTERM: killing children\n"); + kill(-getpgrp(), SIGTERM); + } +#endif + DBG_NOTICE("Exiting pid %d on SIGTERM\n", getpid()); + talloc_free(ev); + exit(127); +} + +/* + called when the process model is selected +*/ +static void prefork_model_init(void) +{ +} + +static void prefork_reload_after_fork(void) +{ + NTSTATUS status; + + ldb_wrap_fork_hook(); + /* Must be done after a fork() to reset messaging contexts. */ + status = imessaging_reinit_all(); + if (!NT_STATUS_IS_OK(status)) { + smb_panic("Failed to re-initialise imessaging after fork"); + } +} + +/* + handle EOF on the parent-to-all-children pipe in the child +*/ +static void prefork_pipe_handler(struct tevent_context *event_ctx, + struct tevent_fd *fde, uint16_t flags, + void *private_data) +{ + /* free the fde which removes the event and stops it firing again */ + TALLOC_FREE(fde); + DBG_NOTICE("Child %d exiting\n", getpid()); + talloc_free(event_ctx); + exit(0); +} + +/* + handle EOF on the child pipe in the parent, so we know when a + process terminates without using SIGCHLD or waiting on all possible pids. + + We need to ensure we do not ignore SIGCHLD because we need it to + work to get a valid error code from samba_runcmd_*(). + */ +static void prefork_child_pipe_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *private_data) +{ + struct tfork *t = NULL; + int status = 0; + pid_t pid = 0; + + /* free the fde which removes the event and stops it firing again */ + TALLOC_FREE(fde); + + /* the child has closed the pipe, assume its dead */ + + /* tfork allocates tfork structures with malloc */ + t = (struct tfork*)private_data; + pid = tfork_child_pid(t); + errno = 0; + status = tfork_status(&t, false); + if (status == -1) { + DBG_ERR("Parent %d, Child %d terminated, " + "unable to get status code from tfork\n", + getpid(), pid); + } else if (WIFEXITED(status)) { + status = WEXITSTATUS(status); + DBG_ERR("Parent %d, Child %d exited with status %d\n", + getpid(), pid, status); + } else if (WIFSIGNALED(status)) { + status = WTERMSIG(status); + DBG_ERR("Parent %d, Child %d terminated with signal %d\n", + getpid(), pid, status); + } + /* tfork allocates tfork structures with malloc */ + free(t); + return; +} + +/* + called when a listening socket becomes readable. +*/ +static void prefork_accept_connection( + struct tevent_context *ev, + struct loadparm_context *lp_ctx, + struct socket_context *listen_socket, + void (*new_conn)(struct tevent_context *, + struct loadparm_context *, + struct socket_context *, + struct server_id, + void *, + void *), + void *private_data, + void *process_context) +{ + NTSTATUS status; + struct socket_context *connected_socket; + pid_t pid = getpid(); + + /* accept an incoming connection. */ + status = socket_accept(listen_socket, &connected_socket); + if (!NT_STATUS_IS_OK(status)) { + /* + * For prefork we can ignore STATUS_MORE_ENTRIES, as once a + * connection becomes available all waiting processes are + * woken, but only one gets work to process. + * AKA the thundering herd. + * In the short term this should not be an issue as the number + * of workers should be a small multiple of the number of cpus + * In the longer term socket_accept needs to implement a + * mutex/semaphore (like apache does) to serialise the accepts + */ + if (!NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) { + DBG_ERR("Worker process (%d), error in accept [%s]\n", + getpid(), nt_errstr(status)); + } + return; + } + + talloc_steal(private_data, connected_socket); + + new_conn(ev, lp_ctx, connected_socket, + cluster_id(pid, socket_get_fd(connected_socket)), + private_data, process_context); +} + +static void setup_handlers(struct tevent_context *ev, int from_parent_fd) { + struct tevent_fd *fde = NULL; + struct tevent_signal *se = NULL; + + fde = tevent_add_fd(ev, ev, from_parent_fd, TEVENT_FD_READ, + prefork_pipe_handler, NULL); + if (fde == NULL) { + smb_panic("Failed to add fd handler after fork"); + } + + se = tevent_add_signal(ev, + ev, + SIGHUP, + 0, + sighup_signal_handler, + NULL); + if (se == NULL) { + smb_panic("Failed to add SIGHUP handler after fork"); + } + + se = tevent_add_signal(ev, + ev, + SIGTERM, + 0, + sigterm_signal_handler, + NULL); + if (se == NULL) { + smb_panic("Failed to add SIGTERM handler after fork"); + } +} + +/* + * called to create a new server task + */ +static void prefork_new_task( + struct tevent_context *ev, + struct loadparm_context *lp_ctx, + const char *service_name, + void (*new_task_fn)(struct tevent_context *, + struct loadparm_context *lp_ctx, + struct server_id , void *, void *), + void *private_data, + const struct service_details *service_details, + int from_parent_fd) +{ + pid_t pid; + struct tfork* t = NULL; + int i, num_children; + + struct tevent_context *ev2; + + t = tfork_create(); + if (t == NULL) { + smb_panic("failure in tfork\n"); + } + + pid = tfork_child_pid(t); + if (pid != 0) { + struct tevent_fd *fde = NULL; + int fd = tfork_event_fd(t); + + /* Register a pipe handler that gets called when the prefork + * master process terminates. + */ + fde = tevent_add_fd(ev, ev, fd, TEVENT_FD_READ, + prefork_child_pipe_handler, t); + if (fde == NULL) { + smb_panic("Failed to add child pipe handler, " + "after fork"); + } + tevent_fd_set_auto_close(fde); + return; + } + + pid = getpid(); + setproctitle("task[%s] pre-fork master", service_name); + + /* + * this will free all the listening sockets and all state that + * is not associated with this new connection + */ + if (tevent_re_initialise(ev) != 0) { + smb_panic("Failed to re-initialise tevent after fork"); + } + prefork_reload_after_fork(); + setup_handlers(ev, from_parent_fd); + + if (service_details->inhibit_pre_fork) { + new_task_fn(ev, lp_ctx, cluster_id(pid, 0), private_data, NULL); + /* The task does not support pre-fork */ + tevent_loop_wait(ev); + TALLOC_FREE(ev); + exit(0); + } + + /* + * This is now the child code. We need a completely new event_context + * to work with + */ + ev2 = s4_event_context_init(NULL); + + /* setup this new connection: process will bind to it's sockets etc + * + * While we can use ev for the child, which has been re-initialised + * above we must run the new task under ev2 otherwise the children would + * be listening on the sockets. Also we don't want the top level + * process accepting and handling requests, it's responsible for + * monitoring and controlling the child work processes. + */ + new_task_fn(ev2, lp_ctx, cluster_id(pid, 0), private_data, NULL); + + { + int default_children; + default_children = lpcfg_prefork_children(lp_ctx); + num_children = lpcfg_parm_int(lp_ctx, NULL, "prefork children", + service_name, default_children); + } + if (num_children == 0) { + DBG_WARNING("Number of pre-fork children for %s is zero, " + "NO worker processes will be started for %s\n", + service_name, service_name); + } + DBG_NOTICE("Forking %d %s worker processes\n", + num_children, service_name); + /* We are now free to spawn some worker processes */ + for (i=0; i < num_children; i++) { + struct tfork* w = NULL; + + w = tfork_create(); + if (t == NULL) { + smb_panic("failure in tfork\n"); + } + + pid = tfork_child_pid(w); + if (pid != 0) { + struct tevent_fd *fde = NULL; + int fd = tfork_event_fd(w); + + fde = tevent_add_fd(ev, ev, fd, TEVENT_FD_READ, + prefork_child_pipe_handler, w); + if (fde == NULL) { + smb_panic("Failed to add child pipe handler, " + "after fork"); + } + tevent_fd_set_auto_close(fde); + } else { + /* tfork uses malloc */ + free(w); + + TALLOC_FREE(ev); + pid = getpid(); + setproctitle("task[%s] pre-forked worker", + service_name); + prefork_reload_after_fork(); + setup_handlers(ev2, from_parent_fd); + tevent_loop_wait(ev2); + talloc_free(ev2); + exit(0); + } + } + + /* Don't listen on the sockets we just gave to the children */ + tevent_loop_wait(ev); + TALLOC_FREE(ev); + /* We need to keep ev2 until we're finished for the messaging to work */ + TALLOC_FREE(ev2); + exit(0); + +} + + +/* called when a task goes down */ +static void prefork_terminate(struct tevent_context *ev, + struct loadparm_context *lp_ctx, + const char *reason, + void *process_context) +{ + DBG_DEBUG("called with reason[%s]\n", reason); +} + +/* called to set a title of a task or connection */ +static void prefork_set_title(struct tevent_context *ev, const char *title) +{ +} + +static const struct model_ops prefork_ops = { + .name = "prefork", + .model_init = prefork_model_init, + .accept_connection = prefork_accept_connection, + .new_task = prefork_new_task, + .terminate = prefork_terminate, + .set_title = prefork_set_title, +}; + +/* + * initialise the prefork process model, registering ourselves with the + * process model subsystem + */ +NTSTATUS process_model_prefork_init(void) +{ + return register_process_model(&prefork_ops); +} diff --git a/source4/smbd/wscript_build b/source4/smbd/wscript_build index c28bc1df38a..ef0aaf773c1 100644 --- a/source4/smbd/wscript_build +++ b/source4/smbd/wscript_build @@ -44,3 +44,10 @@ bld.SAMBA_MODULE('process_model_standard', internal_module=False ) +bld.SAMBA_MODULE('process_model_prefork', + source='process_prefork.c', + subsystem='process_model', + init_function='process_model_prefork_init', + deps='MESSAGING events ldbsamba cluster samba-sockets process_model messages_dgm', + internal_module=False + ) |