s3-prefrok: Handle only valid children
[idra/samba.git] / source3 / lib / server_prefork.c
index b337fa0c3b4ef0dfe2495ee5df937e951805788d..71441c3303bbea0eaef1577ec2e33a675718d1b2 100644 (file)
 
 struct prefork_pool {
 
-       int listen_fd;
-       int lock_fd;
+       int listen_fd_size;
+       int *listen_fds;
 
        prefork_main_fn_t *main_fn;
        void *private_data;
 
        int pool_size;
        struct pf_worker_data *pool;
+
+       int allowed_clients;
+
+       prefork_sigchld_fn_t *sigchld_fn;
+       void *sigchld_data;
 };
 
-int prefork_pool_destructor(struct prefork_pool *pfp)
+static bool prefork_setup_sigchld_handler(struct tevent_context *ev_ctx,
+                                           struct prefork_pool *pfp);
+
+static int prefork_pool_destructor(struct prefork_pool *pfp)
 {
-       munmap(pfp->pool, pfp->pool_size * sizeof(struct pf_worker_data));
+       anonymous_shared_free(pfp->pool);
        return 0;
 }
 
-bool prefork_create_pool(struct tevent_context *ev_ctx,
-                        TALLOC_CTX *mem_ctx, int listen_fd,
+bool prefork_create_pool(TALLOC_CTX *mem_ctx,
+                        struct tevent_context *ev_ctx,
+                        struct messaging_context *msg_ctx,
+                        int listen_fd_size, int *listen_fds,
                         int min_children, int max_children,
                         prefork_main_fn_t *main_fn, void *private_data,
                         struct prefork_pool **pf_pool)
@@ -56,29 +66,30 @@ bool prefork_create_pool(struct tevent_context *ev_ctx,
        size_t data_size;
        int ret;
        int i;
+       bool ok;
 
-       pfp = talloc(mem_ctx, struct prefork_pool);
+       pfp = talloc_zero(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);
+       pfp->listen_fd_size = listen_fd_size;
+       pfp->listen_fds = talloc_array(pfp, int, listen_fd_size);
+       if (!pfp->listen_fds) {
+               DEBUG(1, ("Out of memory!\n"));
                return false;
        }
+       for (i = 0; i < listen_fd_size; i++) {
+               pfp->listen_fds[i] = listen_fds[i];
+       }
+       pfp->main_fn = main_fn;
+       pfp->private_data = private_data;
 
        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) {
+       pfp->pool = anonymous_shared_allocate(data_size);
+       if (pfp->pool == NULL) {
                DEBUG(1, ("Failed to mmap memory for prefork pool!\n"));
                talloc_free(pfp);
                return false;
@@ -86,6 +97,10 @@ bool prefork_create_pool(struct tevent_context *ev_ctx,
        talloc_set_destructor(pfp, prefork_pool_destructor);
 
        for (i = 0; i < min_children; i++) {
+
+               pfp->pool[i].allowed_clients = 1;
+               pfp->pool[i].started = now;
+
                pid = sys_fork();
                switch (pid) {
                case -1:
@@ -94,24 +109,68 @@ bool prefork_create_pool(struct tevent_context *ev_ctx,
 
                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->pool[i].status = PF_WORKER_ALIVE;
+                       ret = pfp->main_fn(ev_ctx, msg_ctx,
+                                          &pfp->pool[i], i + 1,
+                                          pfp->listen_fd_size,
+                                          pfp->listen_fds,
                                           pfp->private_data);
                        exit(ret);
 
                default: /* THE PARENT */
                        pfp->pool[i].pid = pid;
-                       pfp->pool[i].started = now;
                        break;
                }
        }
 
+       ok = prefork_setup_sigchld_handler(ev_ctx, pfp);
+       if (!ok) {
+               DEBUG(1, ("Failed to setup SIGCHLD Handler!\n"));
+               talloc_free(pfp);
+               return false;
+       }
+
        *pf_pool = pfp;
        return true;
 }
 
+/* Provide the new max children number in new_max
+ * (must be larger than current max).
+ * Returns: 0 if all fine
+ *         ENOSPC if mremap fails to expand
+ *         EINVAL if new_max is invalid
+ */
+int prefork_expand_pool(struct prefork_pool *pfp, int new_max)
+{
+       struct prefork_pool *pool;
+       size_t old_size;
+       size_t new_size;
+       int ret;
+
+       if (new_max <= pfp->pool_size) {
+               return EINVAL;
+       }
+
+       old_size = sizeof(struct pf_worker_data) * pfp->pool_size;
+       new_size = sizeof(struct pf_worker_data) * new_max;
+
+       pool = anonymous_shared_resize(&pfp->pool, new_size, false);
+       if (pool == NULL) {
+               ret = errno;
+               DEBUG(3, ("Failed to mremap memory (%d: %s)!\n",
+                         ret, strerror(ret)));
+               return ret;
+       }
+
+       memset(&pool[pfp->pool_size], 0, new_size - old_size);
+
+       pfp->pool_size = new_max;
+
+       return 0;
+}
+
 int prefork_add_children(struct tevent_context *ev_ctx,
+                        struct messaging_context *msg_ctx,
                         struct prefork_pool *pfp,
                         int num_children)
 {
@@ -126,6 +185,9 @@ int prefork_add_children(struct tevent_context *ev_ctx,
                        continue;
                }
 
+               pfp->pool[i].allowed_clients = 1;
+               pfp->pool[i].started = now;
+
                pid = sys_fork();
                switch (pid) {
                case -1:
@@ -134,9 +196,11 @@ int prefork_add_children(struct tevent_context *ev_ctx,
 
                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->pool[i].status = PF_WORKER_ALIVE;
+                       ret = pfp->main_fn(ev_ctx, msg_ctx,
+                                          &pfp->pool[i], i + 1,
+                                          pfp->listen_fd_size,
+                                          pfp->listen_fds,
                                           pfp->private_data);
 
                        pfp->pool[i].status = PF_WORKER_EXITING;
@@ -144,7 +208,6 @@ int prefork_add_children(struct tevent_context *ev_ctx,
 
                default: /* THE PARENT */
                        pfp->pool[i].pid = pid;
-                       pfp->pool[i].started = now;
                        j++;
                        break;
                }
@@ -163,8 +226,8 @@ struct prefork_oldest {
 /* 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;
+       const struct prefork_oldest *a = (const struct prefork_oldest *)ap;
+       const struct prefork_oldest *b = (const struct prefork_oldest *)bp;
 
        if (a->started == b->started) {
                return 0;
@@ -189,7 +252,8 @@ int prefork_retire_children(struct prefork_pool *pfp,
 
        for (i = 0; i < pfp->pool_size; i++) {
                oldest[i].num = i;
-               if (pfp->pool[i].status == PF_WORKER_IDLE) {
+               if (pfp->pool[i].status == PF_WORKER_ALIVE ||
+                   pfp->pool[i].status == PF_WORKER_ACCEPTING) {
                        oldest[i].started = pfp->pool[i].started;
                } else {
                        oldest[i].started = now;
@@ -201,7 +265,8 @@ int prefork_retire_children(struct prefork_pool *pfp,
                prefork_sort_oldest);
 
        for (i = 0, j = 0; i < pfp->pool_size && j < num_children; i++) {
-               if (pfp->pool[i].status == PF_WORKER_IDLE &&
+               if ((pfp->pool[i].status == PF_WORKER_ALIVE ||
+                    pfp->pool[i].status == PF_WORKER_ACCEPTING) &&
                    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));
@@ -227,7 +292,7 @@ int prefork_count_active_children(struct prefork_pool *pfp, int *total)
 
                t++;
 
-               if (pfp->pool[i].num_clients == 0) {
+               if (pfp->pool[i].num_clients <= 0) {
                        continue;
                }
 
@@ -238,400 +303,203 @@ int prefork_count_active_children(struct prefork_pool *pfp, int *total)
        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)
+static void prefork_cleanup_loop(struct prefork_pool *pfp)
 {
+       int status;
+       pid_t pid;
        int i;
 
+       /* TODO: should we use a process group id wait instead of looping ? */
        for (i = 0; i < pfp->pool_size; i++) {
-               if (pfp->pool[i].pid == pid) {
+               if (pfp->pool[i].status == PF_WORKER_NONE ||
+                   pfp->pool[i].pid == 0) {
+                       continue;
+               }
+
+               pid = sys_waitpid(pfp->pool[i].pid, &status, WNOHANG);
+               if (pid > 0) {
+
                        if (pfp->pool[i].status != PF_WORKER_EXITING) {
-                               DEBUG(2, ("pid %d terminated abnormally!\n",
-                                         (int)pid));
+                               DEBUG(3, ("Child (%d) terminated abnormally:"
+                                         " %d\n", (int)pid, status));
+                       } else {
+                               DEBUG(10, ("Child (%d) terminated with status:"
+                                          " %d\n", (int)pid, status));
                        }
 
                        /* 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)
+int prefork_count_allowed_connections(struct prefork_pool *pfp)
 {
-       struct flock lock;
-       int op;
-       int ret;
-
-       if (pf->cmds == PF_SRV_MSG_EXIT) {
-               return -2;
-       }
+       int c;
+       int i;
 
-       pf_alarm = 0;
+       c = 0;
+       for (i = 0; i < pfp->pool_size; i++) {
+               if (pfp->pool[i].status == PF_WORKER_NONE ||
+                   pfp->pool[i].status == PF_WORKER_EXITING) {
+                       continue;
+               }
 
-       if (timeout > 0) {
-               CatchSignal(SIGALRM, pf_alarm_cb);
-               alarm(timeout);
-       }
+               if (pfp->pool[i].num_clients < 0) {
+                       continue;
+               }
 
-       if (timeout == 0) {
-               op = F_SETLK;
-       } else {
-               op = F_SETLKW;
+               c += pfp->pool[i].allowed_clients - pfp->pool[i].num_clients;
        }
 
-       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;
+       return c;
+}
 
-               ret = errno;
+void prefork_increase_allowed_clients(struct prefork_pool *pfp, int max)
+{
+       int i;
 
-               if (pf->cmds == PF_SRV_MSG_EXIT) {
-                       ret = -2;
-                       goto done;
+       for (i = 0; i < pfp->pool_size; i++) {
+               if (pfp->pool[i].status == PF_WORKER_NONE ||
+                   pfp->pool[i].status == PF_WORKER_EXITING) {
+                       continue;
                }
 
-               switch (ret) {
-               case EINTR:
-                       break;
-
-               case EACCES:
-               case EAGAIN:
-                       /* lock held by other proc */
-                       ret = -1;
-                       goto done;
-               default:
-                       goto done;
+               if (pfp->pool[i].num_clients < 0) {
+                       continue;
                }
 
-               if (pf_alarm == 1) {
-                       /* timed out */
-                       ret = -1;
-                       goto done;
+               if (pfp->pool[i].allowed_clients < max) {
+                       pfp->pool[i].allowed_clients++;
                }
-       } 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)
+void prefork_decrease_allowed_clients(struct prefork_pool *pfp)
 {
-       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;
+       int i;
 
-               if (ret != EINTR) {
-                       goto done;
+       for (i = 0; i < pfp->pool_size; i++) {
+               if (pfp->pool[i].status == PF_WORKER_NONE ||
+                   pfp->pool[i].status == PF_WORKER_EXITING) {
+                       continue;
                }
 
-               if (pf_alarm == 1) {
-                       /* timed out */
-                       ret = -1;
-                       goto done;
+               if (pfp->pool[i].num_clients < 0) {
+                       continue;
                }
-       } 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)));
+               if (pfp->pool[i].allowed_clients > 1) {
+                       pfp->pool[i].allowed_clients--;
+               }
        }
-       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)
+void prefork_reset_allowed_clients(struct prefork_pool *pfp)
 {
-       int ret;
-       int sd = -1;
-       int err;
+       int i;
 
-       ret = prefork_grab_lock(pf, lock_fd, -1);
-       if (ret != 0) {
-               return ret;
+       for (i = 0; i < pfp->pool_size; i++) {
+               pfp->pool[i].allowed_clients = 1;
        }
+}
 
-       err = 0;
-       do {
-               sd = accept(listen_fd, addr, addrlen);
-
-               if (sd != -1) break;
+void prefork_send_signal_to_all(struct prefork_pool *pfp, int signal_num)
+{
+       int i;
 
-               if (errno == EINTR) {
-                       if (pf->cmds == PF_SRV_MSG_EXIT) {
-                               err = -2;
-                       }
-               } else {
-                       err = errno;
+       for (i = 0; i < pfp->pool_size; i++) {
+               if (pfp->pool[i].status == PF_WORKER_NONE) {
+                       continue;
                }
 
-       } 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;
+               kill(pfp->pool[i].pid, signal_num);
        }
-
-       pf->status = PF_WORKER_BUSY;
-       pf->num_clients++;
-       *fd = sd;
-       return 0;
 }
 
-/* ==== async code ==== */
-
-#define PF_ASYNC_LOCK_GRAB     0x01
-#define PF_ASYNC_LOCK_RELEASE  0x02
-#define PF_ASYNC_ACTION_MASK   0x03
-#define PF_ASYNC_LOCK_DONE     0x04
-
-struct pf_lock_state {
-       struct pf_worker_data *pf;
-       int lock_fd;
-       int flags;
-};
-
-static void prefork_lock_handler(struct tevent_context *ev,
-                                       struct tevent_timer *te,
-                                       struct timeval curtime, void *pvt);
-
-static struct tevent_req *prefork_lock_send(TALLOC_CTX *mem_ctx,
-                                               struct tevent_context *ev,
-                                               struct pf_worker_data *pf,
-                                               int lock_fd, int action)
+static void prefork_sigchld_handler(struct tevent_context *ev_ctx,
+                                   struct tevent_signal *se,
+                                   int signum, int count,
+                                   void *siginfo, void *pvt)
 {
-       struct tevent_req *req;
-       struct pf_lock_state *state;
+       struct prefork_pool *pfp;
 
-       req = tevent_req_create(mem_ctx, &state, struct pf_lock_state);
-       if (!req) {
-               return NULL;
-       }
+       pfp = talloc_get_type_abort(pvt, struct prefork_pool);
 
-       state->pf = pf;
-       state->lock_fd = lock_fd;
-       state->flags = action;
+       /* run the cleanup function to make sure all dead children are
+        * properly and timely retired. */
+       prefork_cleanup_loop(pfp);
 
-       /* try once immediately */
-       prefork_lock_handler(ev, NULL, tevent_timeval_zero(), req);
-       if (state->flags & PF_ASYNC_LOCK_DONE) {
-               tevent_req_post(req, ev);
+       if (pfp->sigchld_fn) {
+               pfp->sigchld_fn(ev_ctx, pfp, pfp->sigchld_data);
        }
-
-       return req;
 }
 
-static void prefork_lock_handler(struct tevent_context *ev,
-                                struct tevent_timer *te,
-                                struct timeval curtime, void *pvt)
+static bool prefork_setup_sigchld_handler(struct tevent_context *ev_ctx,
+                                         struct prefork_pool *pfp)
 {
-       struct tevent_req *req;
-       struct pf_lock_state *state;
-       int ret;
+       struct tevent_signal *se;
 
-       req = talloc_get_type_abort(pvt, struct tevent_req);
-       state = tevent_req_data(req, struct pf_lock_state);
-
-       switch (state->flags & PF_ASYNC_ACTION_MASK) {
-       case PF_ASYNC_LOCK_GRAB:
-               ret = prefork_grab_lock(state->pf, state->lock_fd, 0);
-               break;
-       case PF_ASYNC_LOCK_RELEASE:
-               ret = prefork_release_lock(state->pf, state->lock_fd, 0);
-               break;
-       default:
-               ret = EINVAL;
-               break;
-       }
-
-       switch (ret) {
-       case 0:
-               state->flags |= PF_ASYNC_LOCK_DONE;
-               tevent_req_done(req);
-               return;
-       case -1:
-               te = tevent_add_timer(ev, state,
-                                       tevent_timeval_current_ofs(1, 0),
-                                       prefork_lock_handler, req);
-               tevent_req_nomem(te, req);
-               return;
-       case -2:
-               /* server tells us to stop */
-               state->flags |= PF_ASYNC_LOCK_DONE;
-               tevent_req_error(req, -2);
-               return;
-       default:
-               state->flags |= PF_ASYNC_LOCK_DONE;
-               tevent_req_error(req, ret);
-               return;
+       se = tevent_add_signal(ev_ctx, pfp, SIGCHLD, 0,
+                               prefork_sigchld_handler, pfp);
+       if (!se) {
+               DEBUG(0, ("Failed to setup SIGCHLD handler!\n"));
+               return false;
        }
+
+       return true;
 }
 
-static int prefork_lock_recv(struct tevent_req *req)
+void prefork_set_sigchld_callback(struct prefork_pool *pfp,
+                                 prefork_sigchld_fn_t *sigchld_fn,
+                                 void *private_data)
 {
-       int ret;
-
-       if (!tevent_req_is_unix_error(req, &ret)) {
-               ret = 0;
-       }
-
-       tevent_req_received(req);
-       return ret;
+       pfp->sigchld_fn = sigchld_fn;
+       pfp->sigchld_data = private_data;
 }
 
+/* ==== Functions used by children ==== */
+
 struct pf_listen_state {
        struct tevent_context *ev;
        struct pf_worker_data *pf;
 
-       int lock_fd;
-       int listen_fd;
-
-       struct sockaddr *addr;
-       socklen_t *addrlen;
+       int listen_fd_size;
+       int *listen_fds;
 
        int accept_fd;
 
+       struct tsocket_address *srv_addr;
+       struct tsocket_address *cli_addr;
+
        int error;
 };
 
-static void prefork_listen_lock_done(struct tevent_req *subreq);
+struct pf_listen_ctx {
+       TALLOC_CTX *fde_ctx;
+       struct tevent_req *req;
+       int listen_fd;
+};
+
 static void prefork_listen_accept_handler(struct tevent_context *ev,
                                          struct tevent_fd *fde,
                                          uint16_t flags, void *pvt);
-static void prefork_listen_release_done(struct tevent_req *subreq);
 
 struct tevent_req *prefork_listen_send(TALLOC_CTX *mem_ctx,
                                        struct tevent_context *ev,
                                        struct pf_worker_data *pf,
-                                       int lock_fd, int listen_fd,
-                                       struct sockaddr *addr,
-                                       socklen_t *addrlen)
+                                       int listen_fd_size,
+                                       int *listen_fds)
 {
-       struct tevent_req *req, *subreq;
+       struct tevent_req *req;
        struct pf_listen_state *state;
+       struct pf_listen_ctx *ctx;
+       struct tevent_fd *fde;
+       TALLOC_CTX *fde_ctx;
+       int i;
 
        req = tevent_req_create(mem_ctx, &state, struct pf_listen_state);
        if (!req) {
@@ -640,44 +508,37 @@ struct tevent_req *prefork_listen_send(TALLOC_CTX *mem_ctx,
 
        state->ev = ev;
        state->pf = pf;
-       state->lock_fd = lock_fd;
-       state->listen_fd = listen_fd;
-       state->addr = addr;
-       state->addrlen = addrlen;
+       state->listen_fd_size = listen_fd_size;
+       state->listen_fds = listen_fds;
        state->accept_fd = -1;
        state->error = 0;
 
-       subreq = prefork_lock_send(state, state->ev, state->pf,
-                                  state->lock_fd, PF_ASYNC_LOCK_GRAB);
-       if (tevent_req_nomem(subreq, req)) {
+       fde_ctx = talloc_new(state);
+       if (tevent_req_nomem(fde_ctx, req)) {
                return tevent_req_post(req, ev);
        }
 
-       tevent_req_set_callback(subreq, prefork_listen_lock_done, req);
-       return req;
-}
-
-static void prefork_listen_lock_done(struct tevent_req *subreq)
-{
-       struct tevent_req *req;
-       struct pf_listen_state *state;
-       struct tevent_fd *fde;
-       int ret;
-
-       req = tevent_req_callback_data(subreq, struct tevent_req);
-       state = tevent_req_data(req, struct pf_listen_state);
-
-       ret = prefork_lock_recv(subreq);
-       if (ret != 0) {
-               tevent_req_error(req, ret);
-               return;
+       /* race on accept */
+       for (i = 0; i < state->listen_fd_size; i++) {
+               ctx = talloc(fde_ctx, struct pf_listen_ctx);
+               if (tevent_req_nomem(ctx, req)) {
+                       return tevent_req_post(req, ev);
+               }
+               ctx->fde_ctx = fde_ctx;
+               ctx->req = req;
+               ctx->listen_fd = state->listen_fds[i];
+
+               fde = tevent_add_fd(state->ev, fde_ctx,
+                                   ctx->listen_fd, TEVENT_FD_READ,
+                                   prefork_listen_accept_handler, ctx);
+               if (tevent_req_nomem(fde, req)) {
+                       return tevent_req_post(req, ev);
+               }
        }
 
-       /* next step, accept */
-       fde = tevent_add_fd(state->ev, state,
-                           state->listen_fd, TEVENT_FD_READ,
-                           prefork_listen_accept_handler, req);
-       tevent_req_nomem(fde, req);
+       pf->status = PF_WORKER_ACCEPTING;
+
+       return req;
 }
 
 static void prefork_listen_accept_handler(struct tevent_context *ev,
@@ -685,75 +546,101 @@ static void prefork_listen_accept_handler(struct tevent_context *ev,
                                          uint16_t flags, void *pvt)
 {
        struct pf_listen_state *state;
-       struct tevent_req *req, *subreq;
+       struct tevent_req *req;
+       struct pf_listen_ctx *ctx;
+       struct sockaddr_storage addr;
+       socklen_t addrlen;
        int err = 0;
        int sd = -1;
+       int ret;
 
-       req = talloc_get_type_abort(pvt, struct tevent_req);
-       state = tevent_req_data(req, struct pf_listen_state);
+       ctx = talloc_get_type_abort(pvt, struct pf_listen_ctx);
+       req = ctx->req;
+       state = tevent_req_data(ctx->req, struct pf_listen_state);
+
+       if (state->pf->cmds == PF_SRV_MSG_EXIT) {
+               /* We have been asked to exit, so drop here and the next
+                * child will pick it up */
+               state->pf->status = PF_WORKER_EXITING;
+               state->error = EINTR;
+               goto done;
+       }
 
-       sd = accept(state->listen_fd, state->addr, state->addrlen);
+       ZERO_STRUCT(addr);
+       addrlen = sizeof(addr);
+       sd = accept(ctx->listen_fd, (struct sockaddr *)&addr, &addrlen);
        if (sd == -1) {
-               if (errno == EINTR) {
-                       /* keep trying */
-                       return;
-               }
                err = errno;
                DEBUG(6, ("Accept failed! (%d, %s)\n", err, strerror(err)));
-
        }
 
-       /* do not track the listen fd anymore */
-       talloc_free(fde);
+       /* do not track the listen fds anymore */
+       talloc_free(ctx->fde_ctx);
+       ctx = NULL;
        if (err) {
-               tevent_req_error(req, err);
-               return;
+               state->error = err;
+               goto done;
        }
 
        state->accept_fd = sd;
 
-       /* release lock now */
-       subreq = prefork_lock_send(state, state->ev, state->pf,
-                                  state->lock_fd, PF_ASYNC_LOCK_RELEASE);
-       if (tevent_req_nomem(subreq, req)) {
-               return;
+       ret = tsocket_address_bsd_from_sockaddr(state,
+                                       (struct sockaddr *)(void *)&addr,
+                                       addrlen, &state->cli_addr);
+       if (ret < 0) {
+               state->error = errno;
+               goto done;
        }
-       tevent_req_set_callback(subreq, prefork_listen_release_done, req);
-}
-
-static void prefork_listen_release_done(struct tevent_req *subreq)
-{
-       struct tevent_req *req;
-       int ret;
 
-       req = tevent_req_callback_data(subreq, struct tevent_req);
+       ZERO_STRUCT(addr);
+       addrlen = sizeof(addr);
+       ret = getsockname(sd, (struct sockaddr *)(void *)&addr, &addrlen);
+       if (ret < 0) {
+               state->error = errno;
+               goto done;
+       }
 
-       ret = prefork_lock_recv(subreq);
-       if (ret != 0) {
-               tevent_req_error(req, ret);
-               return;
+       ret = tsocket_address_bsd_from_sockaddr(state,
+                                       (struct sockaddr *)(void *)&addr,
+                                       addrlen, &state->srv_addr);
+       if (ret < 0) {
+               state->error = errno;
+               goto done;
        }
 
+done:
        tevent_req_done(req);
 }
 
-int prefork_listen_recv(struct tevent_req *req, int *fd)
+int prefork_listen_recv(struct tevent_req *req,
+                       TALLOC_CTX *mem_ctx, int *fd,
+                       struct tsocket_address **srv_addr,
+                       struct tsocket_address **cli_addr)
 {
        struct pf_listen_state *state;
-       int ret;
+       int ret = 0;
 
        state = tevent_req_data(req, struct pf_listen_state);
 
-       if (tevent_req_is_unix_error(req, &ret)) {
+       if (state->error) {
+               ret = state->error;
+       } else {
+               tevent_req_is_unix_error(req, &ret);
+       }
+
+       if (ret) {
                if (state->accept_fd != -1) {
                        close(state->accept_fd);
                }
        } else {
                *fd = state->accept_fd;
-               ret = 0;
-               state->pf->status = PF_WORKER_BUSY;
+               *srv_addr = talloc_move(mem_ctx, &state->srv_addr);
+               *cli_addr = talloc_move(mem_ctx, &state->cli_addr);
                state->pf->num_clients++;
        }
+       if (state->pf->status == PF_WORKER_ACCEPTING) {
+               state->pf->status = PF_WORKER_ALIVE;
+       }
 
        tevent_req_received(req);
        return ret;