You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@httpd.apache.org by Dean Gaudet <dg...@arctic.org> on 1997/06/29 23:35:51 UTC

[PATCH] http_main big patch

Here's a heads up... The patch below does the following:

- eliminate a starvation that can occur if some sockets on the listener list
    are really busy.  The sockets further down the list are not considered
    on an equal basis.

- clean up duplicate listener code between WIN32 and Unix/OS2

- eliminate the sleep(1) in wait_or_timeout under unix, replace it with a
    select, and a tuneable SCOREBOARD_MAINTENANCE_INTERVAL, see httpd.h.

- reduce O(n^2) loops to O(n) loops during scoreboard maintenance.

- eliminate the per-hit idle server check, instead the server kills off
    or starts up one child per SCOREBOARD_MAINTENANCE_INTERVAL according
    to min/maxspare settings.

- ditch unused old scoreboard code/functions

I've almost got standalone_main() down to a readable size.

I want to commit this tomorrow ... 'cause it needs guinea pigs.  I expect
I screwed up the WIN32 part, but I had to touch it in order to deal with
the starvation above.

Dean

Index: CHANGES
===================================================================
RCS file: /export/home/cvs/apache/src/CHANGES,v
retrieving revision 1.308
diff -c -3 -r1.308 CHANGES
*** CHANGES	1997/06/29 18:26:56	1.308
--- CHANGES	1997/06/29 21:23:50
***************
*** 1,5 ****
--- 1,15 ----
  Changes with Apache 1.3
+ 
+   *) Revamp of (unix) scoreboard management code such that it avoids
+      unnecessary traversals of the scoreboard on each hit.  This is
+      particularly important for high volume sites with a large
+      HARD_SERVER_LIMIT.  Some of the previous operations were O(n^2),
+      and are now O(n).  See also SCOREBOARD_MAINTENANCE_INTERVAL in
+      httpd.h. [Dean Gaudet]
    
+   *) In configurations using multiple Listen statements it was possible for
+      busy sockets to starve other sockets of service.  [Dean Gaudet]
+ 
    *) Add a ListenBacklog directive to control the backlog parameter
       passed to listen().  Also change the default to 511 from 512.
       [Marc Slemko]
Index: http_conf_globals.h
===================================================================
RCS file: /export/home/cvs/apache/src/http_conf_globals.h,v
retrieving revision 1.12
diff -c -3 -r1.12 http_conf_globals.h
*** http_conf_globals.h	1997/06/28 23:57:55	1.12
--- http_conf_globals.h	1997/06/29 21:23:51
***************
*** 70,75 ****
--- 70,76 ----
  extern int daemons_min_free;
  extern int daemons_max_free;
  extern int daemons_limit;
+ extern int max_daemons_limit;
  extern int suexec_enabled;
  extern int listenbacklog;
  
Index: http_main.c
===================================================================
RCS file: /export/home/cvs/apache/src/http_main.c,v
retrieving revision 1.170
diff -c -3 -r1.170 http_main.c
*** http_main.c	1997/06/29 19:27:21	1.170
--- http_main.c	1997/06/29 21:24:02
***************
*** 156,170 ****
  char *lock_fname;
  char *server_argv0;
  struct in_addr bind_address;
- listen_rec *listeners;
  int daemons_to_start;
  int daemons_min_free;
  int daemons_max_free;
  int daemons_limit;
  time_t restart_time;
  int suexec_enabled = 0;
  int listenbacklog;
  
  char server_root[MAX_STRING_LEN];
  char server_confname[MAX_STRING_LEN];
  
--- 156,190 ----
  char *lock_fname;
  char *server_argv0;
  struct in_addr bind_address;
  int daemons_to_start;
  int daemons_min_free;
  int daemons_max_free;
  int daemons_limit;
+ int max_daemons_limit = -1; /* the max of daemons_limit over all restarts */
  time_t restart_time;
  int suexec_enabled = 0;
  int listenbacklog;
  
+ /*
+  * During config time, listeners is treated as a NULL-terminated list.
+  * child_main previously would start at the beginning of the list each time
+  * through the loop, so a socket early on in the list could easily starve out
+  * sockets later on in the list.  The solution is to start at the listener
+  * after the last one processed.  But to do that fast/easily in child_main it's
+  * way more convenient for listeners to be a ring that loops back on itself.
+  * The routine setup_listeners() is called after config time to both open up
+  * the sockets and to turn the NULL-terminated list into a ring that loops back
+  * on itself.
+  *
+  * head_listener is used by each child to keep track of what they consider
+  * to be the "start" of the ring.  It is also set by make_child to ensure
+  * that new children also don't starve any sockets.
+  *
+  * Note that listeners != NULL is ensured by read_config().
+  */
+ listen_rec *listeners;
+ static listen_rec * APACHE_TLS head_listener;
+ 
  char server_root[MAX_STRING_LEN];
  char server_confname[MAX_STRING_LEN];
  
***************
*** 1102,1151 ****
      return old_status;
  }
  
! void update_scoreboard_global()
!     {
  #ifdef SCOREBOARD_FILE
      lseek(scoreboard_fd,
  	  (char *)&scoreboard_image->global-(char *)scoreboard_image,0);
      force_write(scoreboard_fd,(char *)&scoreboard_image->global,
  		sizeof scoreboard_image->global);
  #endif
-     }
- 
- int get_child_status (int child_num)
- {
-     if (child_num<0 || child_num>=HARD_SERVER_LIMIT)
-     	return -1;
-     else
- 	return scoreboard_image->servers[child_num].status;
  }
  
- int count_busy_servers ()
- {
-     int i;
-     int res = 0;
- 
-     for (i = 0; i < HARD_SERVER_LIMIT; ++i)
-       if (scoreboard_image->servers[i].status == SERVER_BUSY_READ ||
-               scoreboard_image->servers[i].status == SERVER_BUSY_WRITE ||
-               scoreboard_image->servers[i].status == SERVER_BUSY_KEEPALIVE ||
-               scoreboard_image->servers[i].status == SERVER_BUSY_LOG ||
-               scoreboard_image->servers[i].status == SERVER_BUSY_DNS)
-           ++res;
-     return res;
- }
- 
- int count_live_servers()
-     {
-     int i;
-     int res = 0;
- 
-     for (i = 0; i < HARD_SERVER_LIMIT; ++i)
-       if (scoreboard_image->servers[i].status != SERVER_DEAD)
- 	  ++res;
-     return res;
-     }
- 
  short_score get_scoreboard_info(int i)
  {
      return (scoreboard_image->servers[i]);
--- 1122,1137 ----
      return old_status;
  }
  
! static void update_scoreboard_global()
! {
  #ifdef SCOREBOARD_FILE
      lseek(scoreboard_fd,
  	  (char *)&scoreboard_image->global-(char *)scoreboard_image,0);
      force_write(scoreboard_fd,(char *)&scoreboard_image->global,
  		sizeof scoreboard_image->global);
  #endif
  }
  
  short_score get_scoreboard_info(int i)
  {
      return (scoreboard_image->servers[i]);
***************
*** 1181,1228 ****
  }
  #endif
  
- int count_idle_servers ()
- {
-     int i;
-     int res = 0;
- 
-     for (i = 0; i < HARD_SERVER_LIMIT; ++i)
- 	if (scoreboard_image->servers[i].status == SERVER_READY)
- 	    ++res;
- 
-     return res;
- }
  
! int find_free_child_num ()
  {
!     int i;
! 
!     for (i = 0; i < HARD_SERVER_LIMIT; ++i)
! 	if (scoreboard_image->servers[i].status == SERVER_DEAD)
! 	    return i;
! 
!     return -1;
  }
  
! int find_child_by_pid (int pid)
  {
      int i;
  
!     for (i = 0; i < HARD_SERVER_LIMIT; ++i)
  	if (scoreboard_image->servers[i].pid == pid)
  	    return i;
  
      return -1;
  }
  
! void reclaim_child_processes ()
  {
  #ifndef MULTITHREAD
      int i, status;
      int my_pid = getpid();
  
      sync_scoreboard_image();
!     for (i = 0; i < HARD_SERVER_LIMIT; ++i) {
  	int pid = scoreboard_image->servers[i].pid;
  
  	if (pid != my_pid && pid != 0) { 
--- 1167,1203 ----
  }
  #endif
  
  
! /* This is used to optimize the scoreboard manipulation routines.  We know
!  * that over the lifetime of the server, including graceful restarts, that
!  * we've never issued a child a slot >= max_daemons_limit.
!  */
! static void set_max_daemons_limit (void)
  {
!     if (daemons_limit > max_daemons_limit) {
! 	max_daemons_limit = daemons_limit;
!     }
  }
  
! static int find_child_by_pid (int pid)
  {
      int i;
  
!     for (i = 0; i < max_daemons_limit; ++i)
  	if (scoreboard_image->servers[i].pid == pid)
  	    return i;
  
      return -1;
  }
  
! static void reclaim_child_processes ()
  {
  #ifndef MULTITHREAD
      int i, status;
      int my_pid = getpid();
  
      sync_scoreboard_image();
!     for (i = 0; i < max_daemons_limit; ++i) {
  	int pid = scoreboard_image->servers[i].pid;
  
  	if (pid != my_pid && pid != 0) { 
***************
*** 1292,1298 ****
      int status, n;
      int ret = 0;
  
!     for (n = 0; n < HARD_SERVER_LIMIT; ++n) {
  	if (scoreboard_image->servers[n].status != SERVER_DEAD
  		&& waitpid (scoreboard_image->servers[n].pid, &status, WNOHANG)
  		    == -1
--- 1267,1273 ----
      int status, n;
      int ret = 0;
  
!     for (n = 0; n < max_daemons_limit; ++n) {
  	if (scoreboard_image->servers[n].status != SERVER_DEAD
  		&& waitpid (scoreboard_image->servers[n].pid, &status, WNOHANG)
  		    == -1
***************
*** 1354,1360 ****
  	return -1;
      }
      if (ret <= 0) {
! 	sleep (1);
  	return -1;
      }
      return ret;
--- 1329,1339 ----
  	return -1;
      }
      if (ret <= 0) {
! 	struct timeval tv;
! 
! 	tv.tv_sec = SCOREBOARD_MAINTENANCE_INTERVAL / 1000000;
! 	tv.tv_usec = SCOREBOARD_MAINTENANCE_INTERVAL % 1000000;
! 	ap_select(0, NULL, NULL, NULL, &tv);
  	return -1;
      }
      return ret;
***************
*** 1881,1894 ****
      return s;
  }
  
  static listen_rec *old_listeners;
  
  static void copy_listeners(pool *p)
  {
      listen_rec *lr;
  
      ap_assert(old_listeners == NULL);
!     for (lr = listeners; lr; lr = lr->next) {
  	listen_rec *nr = malloc(sizeof *nr);
  	if (nr == NULL) {
  	    fprintf (stderr, "Ouch!  malloc failed in copy_listeners()\n");
--- 1860,1889 ----
      return s;
  }
  
+ 
+ /*
+  * During a restart we keep track of the old listeners here, so that we
+  * can re-use the sockets.  We have to do this because we won't be able
+  * to re-open the sockets ("Address already in use").
+  *
+  * Unlike the listeners ring, old_listeners is a NULL terminated list.
+  *
+  * copy_listeners() makes the copy, find_listener() finds an old listener
+  * and close_unused_listener() cleans up whatever wasn't used.
+  */
  static listen_rec *old_listeners;
  
+ /* unfortunately copy_listeners may be called before listeners is a ring */
  static void copy_listeners(pool *p)
  {
      listen_rec *lr;
  
      ap_assert(old_listeners == NULL);
!     if (listeners == NULL) {
! 	return;
!     }
!     lr = listeners;
!     do {
  	listen_rec *nr = malloc(sizeof *nr);
  	if (nr == NULL) {
  	    fprintf (stderr, "Ouch!  malloc failed in copy_listeners()\n");
***************
*** 1899,1905 ****
  	nr->next = old_listeners;
  	ap_assert(!nr->used);
  	old_listeners = nr;
!     }
  }
  
  
--- 1894,1901 ----
  	nr->next = old_listeners;
  	ap_assert(!nr->used);
  	old_listeners = nr;
! 	lr = lr->next;
!     } while (lr && lr != listeners);
  }
  
  
***************
*** 1931,1936 ****
--- 1927,1979 ----
  }
  
  
+ /* open sockets, and turn the listeners list into a singly linked ring */
+ static void setup_listeners(pool *pconf)
+ {
+     listen_rec *lr;
+     int fd;
+ 
+     listenmaxfd = -1;
+     FD_ZERO (&listenfds);
+     lr = listeners;
+     for(;;) {
+ 	fd = find_listener (lr);
+ 	if (fd < 0) {
+ 	    fd = make_sock (pconf, &lr->local_addr);
+ 	}
+ 	FD_SET (fd, &listenfds);
+ 	if (fd > listenmaxfd) listenmaxfd = fd;
+ 	lr->fd = fd;
+ 	if (lr->next == NULL) break;
+ 	lr = lr->next;
+     }
+     /* turn the list into a ring */
+     lr->next = listeners;
+     head_listener = listeners;
+     close_unused_listeners ();
+ }
+ 
+ 
+ /*
+  * Find a listener which is ready for accept().  This advances the
+  * head_listener global.
+  */
+ static listen_rec *find_ready_listener(fd_set *main_fds)
+ {
+     listen_rec *lr;
+     
+     lr = head_listener;
+     do {
+ 	if (FD_ISSET(lr->fd, main_fds)) {
+ 	    head_listener = lr->next;
+ 	    return (lr);
+ 	}
+ 	lr = lr->next;
+     } while (lr != head_listener);
+     return NULL;
+ }
+ 
+ 
  static int s_iInitCount = 0;
  
  int
***************
*** 2072,2079 ****
  	if (scoreboard_image->global.exit_generation >= generation)
  	    exit(0);
  	
! 	if ((count_idle_servers() >= daemons_max_free)
! 	    || (max_requests_per_child > 0
  	        && ++requests_this_child >= max_requests_per_child))
  	{
  	    exit(0);
--- 2115,2121 ----
  	if (scoreboard_image->global.exit_generation >= generation)
  	    exit(0);
  	
! 	if ((max_requests_per_child > 0
  	        && ++requests_this_child >= max_requests_per_child))
  	{
  	    exit(0);
***************
*** 2103,2111 ****
              if (srv <= 0)
                  continue;
  
! 	    for (lr = listeners; lr; lr = lr->next) {
! 		if (FD_ISSET(lr->fd, &main_fds)) break;
!             }
  	    if (lr == NULL) continue;
  	    sd = lr->fd;
  
--- 2145,2151 ----
              if (srv <= 0)
                  continue;
  
! 	    lr = find_ready_listener(&main_fds);
  	    if (lr == NULL) continue;
  	    sd = lr->fd;
  
***************
*** 2258,2264 ****
      }    
  }
  
! int make_child(server_rec *server_conf, int child_num)
  {
      int pid;
  
--- 2298,2304 ----
      }    
  }
  
! static int make_child(server_rec *server_conf, int child_num)
  {
      int pid;
  
***************
*** 2268,2273 ****
--- 2308,2316 ----
  	child_main (child_num);
      }
  
+     /* avoid starvation */
+     head_listener = head_listener->next;
+ 
      Explain1 ("Starting new child in slot %d", child_num);
      (void)update_child_status (child_num, SERVER_STARTING, (request_rec *)NULL);
  
***************
*** 2311,2316 ****
--- 2354,2432 ----
  }
  
  
+ /* start up a bunch of children */
+ static void startup_children (int number_to_start)
+ {
+     int i;
+ 
+     for (i = 0; number_to_start && i < daemons_limit; ++i ) {
+ 	if (scoreboard_image->servers[i].status != SERVER_DEAD) {
+ 	    continue;
+ 	}
+ 	if (make_child (server_conf, i) < 0) {
+ 	    break;
+ 	}
+ 	--number_to_start;
+     }
+ }
+ 
+ 
+ static void perform_idle_server_maintenance ()
+ {
+     int i;
+     int to_kill;
+     int free_slot;
+     int idle_count;
+ 
+     free_slot = -1;
+     to_kill = -1;
+     idle_count = 0;
+     sync_scoreboard_image ();
+     for (i = 0; i < daemons_limit; ++i) {
+ 	switch (scoreboard_image->servers[i].status) {
+ 	case SERVER_READY:
+ 	    ++idle_count;
+ 	    /* always kill the highest numbered child if we have to...
+ 	     * no really well thought out reason ... other than observing
+ 	     * the server behaviour under linux where lower numbered children
+ 	     * tend to service more hits (and hence are more likely to have
+ 	     * their data in cpu caches).
+ 	     */
+ 	    to_kill = i;
+ 	    break;
+ 	case SERVER_DEAD:
+ 	    /* try to keep children numbers as low as possible */
+ 	    if (free_slot == -1) {
+ 		free_slot = i;
+ 	    }
+ 	    break;
+ 	}
+     }
+     if (idle_count > daemons_max_free) {
+ 	/* kill off one child... we use SIGUSR1 because that'll cause it to
+ 	 * shut down gracefully, in case it happened to pick up a request
+ 	 * while we were counting
+ 	 */
+ 	kill (SIGUSR1, scoreboard_image->servers[to_kill].pid);
+     } else if (idle_count < daemons_min_free) {
+ 	if (free_slot == -1) {
+ 	    /* only report this condition once */
+ 	    static int reported = 0;
+ 
+ 	    if (!reported) {
+ 		log_printf (server_conf,
+ 		    "server reached MaxClients setting, consider"
+ 		    " raising the MaxClients setting");
+ 		reported = 1;
+ 	    }
+ 	} else {
+ 	    make_child (server_conf, free_slot);
+ 	}
+     }
+ }
+ 
+ 
+ 
  /*****************************************************************
   * Executive routines.
   */
***************
*** 2318,2327 ****
  void standalone_main(int argc, char **argv)
  {
      int remaining_children_to_start;
-     listen_rec *lr;
  
      standalone = 1;
-     listenmaxfd = -1;
  
      is_graceful = 0;
      ++generation;
--- 2434,2441 ----
***************
*** 2342,2363 ****
  	ptrans = make_sub_pool (pconf);
  
  	server_conf = read_config (pconf, ptrans, server_confname); 
! 
! 	listenmaxfd = -1;
! 	FD_ZERO (&listenfds);
! 	for (lr = listeners; lr != NULL; lr = lr->next) {
! 	    int fd;
! 	    
! 	    fd = find_listener (lr);
! 	    if (fd < 0) {
! 		fd = make_sock (pconf, &lr->local_addr);
! 	    }
! 	    FD_SET (fd, &listenfds);
! 	    if (fd > listenmaxfd) listenmaxfd = fd;
! 	    lr->fd = fd;
! 	}
! 	close_unused_listeners ();
! 
  	init_modules (pconf, server_conf);
  	open_logs (server_conf, pconf);
  	set_group_privs ();
--- 2456,2463 ----
  	ptrans = make_sub_pool (pconf);
  
  	server_conf = read_config (pconf, ptrans, server_confname); 
! 	set_max_daemons_limit();
! 	setup_listeners (pconf);
  	init_modules (pconf, server_conf);
  	open_logs (server_conf, pconf);
  	set_group_privs ();
***************
*** 2371,2377 ****
  	    note_cleanups_for_fd (pconf, scoreboard_fd);
  	}
  #endif
- 
  	default_server_hostnames (server_conf);
  
  	set_signals ();
--- 2471,2476 ----
***************
*** 2393,2402 ****
  	    remaining_children_to_start = daemons_limit;
  	}
  	if (!is_graceful) {
! 	    while (remaining_children_to_start) {
! 		--remaining_children_to_start;
! 		make_child (server_conf, remaining_children_to_start);
! 	    }
  	}
  
  	log_error ("Server configured -- resuming normal operations",
--- 2492,2499 ----
  	    remaining_children_to_start = daemons_limit;
  	}
  	if (!is_graceful) {
! 	    startup_children (remaining_children_to_start);
! 	    remaining_children_to_start = 0;
  	}
  
  	log_error ("Server configured -- resuming normal operations",
***************
*** 2419,2424 ****
--- 2516,2531 ----
  		if (child_slot >= 0) {
  		    (void)update_child_status (child_slot, SERVER_DEAD,
  			(request_rec *)NULL);
+ 		    if (remaining_children_to_start
+ 			&& child_slot < daemons_limit) {
+ 			/* we're still doing a 1-for-1 replacement of dead
+ 			 * children with new children
+ 			 */
+ 			make_child (server_conf, child_slot);
+ 			--remaining_children_to_start;
+ 			/* don't perform idle maintenance yet */
+ 			continue;
+ 		    }
  		} else if (is_graceful) {
  		    /* Great, we've probably just lost a slot in the
  		     * scoreboard.  Somehow we don't know about this
***************
*** 2432,2449 ****
  	 	 * generation of children needed to be reaped... so assume
  		 * they're all done, and pick up the slack if any is left.
  		 */
! 		while (remaining_children_to_start > 0) {
! 		    child_slot = find_free_child_num ();
! 		    if (child_slot < 0 || child_slot >= daemons_limit) {
! 			remaining_children_to_start = 0;
! 			break;
! 		    }
! 		    if (make_child (server_conf, child_slot) < 0) {
! 			remaining_children_to_start = 0;
! 			break;
! 		    }
! 		    --remaining_children_to_start;
! 		}
  		/* In any event we really shouldn't do the code below because
  		 * few of the servers we just started are in the IDLE state
  		 * yet, so we'd mistakenly create an extra server.
--- 2539,2546 ----
  	 	 * generation of children needed to be reaped... so assume
  		 * they're all done, and pick up the slack if any is left.
  		 */
! 		startup_children (remaining_children_to_start);
! 		remaining_children_to_start = 0;
  		/* In any event we really shouldn't do the code below because
  		 * few of the servers we just started are in the IDLE state
  		 * yet, so we'd mistakenly create an extra server.
***************
*** 2451,2466 ****
  		continue;
  	    }
  
! 	    sync_scoreboard_image ();
! 	    if ((remaining_children_to_start
! 		    || (count_idle_servers () < daemons_min_free))
! 		&& (child_slot = find_free_child_num ()) >= 0
! 		&& child_slot < daemons_limit) {
! 		make_child (server_conf, child_slot);
! 	    }
! 	    if (remaining_children_to_start) {
! 		--remaining_children_to_start;
! 	    }
  	}
  
  	/* we've been told to restart */
--- 2548,2554 ----
  		continue;
  	    }
  
! 	    perform_idle_server_maintenance();
  	}
  
  	/* we've been told to restart */
***************
*** 2581,2586 ****
--- 2669,2675 ----
  
      suexec_enabled = init_suexec();
      server_conf = read_config (pconf, ptrans, server_confname);
+     set_max_daemons_limit();
      init_modules (pconf, server_conf);
      
      if(standalone) {
***************
*** 2797,2802 ****
--- 2886,2898 ----
      requests_this_child = 0;
      (void)update_child_status(child_num, SERVER_READY, (request_rec*)NULL);
  
+     /* XXX: Unlike the make_child code under Unix, there's still a starvation
+      * condition here under WIN32.  All new threads start off at the first
+      * socket, which gives it higher than average preference.  Note that
+      * head_listener is necessarily thread specific data.
+      */
+     head_listener = listeners;
+ 
      /*
       * Setup the jump buffers so that we can return here after
       * a signal or a timeout (yeah, I know, same thing).
***************
*** 3000,3006 ****
      int max_jobs_after_exit_request;
  
      standalone = 1;
!     sd = listenmaxfd = -1;
      nthreads = threads_per_child;
      max_jobs_after_exit_request = excess_requests_per_child;
      max_jobs_per_exe = max_requests_per_child;
--- 3096,3102 ----
      int max_jobs_after_exit_request;
  
      standalone = 1;
!     sd = -1;
      nthreads = threads_per_child;
      max_jobs_after_exit_request = excess_requests_per_child;
      max_jobs_per_exe = max_requests_per_child;
***************
*** 3022,3049 ****
      default_server_hostnames (server_conf);
  
      acquire_mutex(start_mutex);
-     {
- 	listen_rec *lr;
- 	int fd;
- 
-         listenmaxfd = -1;
- 	FD_ZERO(&listenfds);
- 
- 	for (lr=listeners; lr != NULL; lr=lr->next)
- 	{
- 	    fd=find_listener(lr);
- 	    if(fd < 0)
- 	    {
- 		fd = make_sock(pconf, &lr->local_addr);
- 	    }
- 	    FD_SET(fd, &listenfds);
- 	    if (fd > listenmaxfd) listenmaxfd = fd;
- 	    lr->fd=fd;
- 	}
- 	close_unused_listeners();
- 	sd = -1;
-     }
  
      set_signals();
  
      /*
--- 3118,3125 ----
      default_server_hostnames (server_conf);
  
      acquire_mutex(start_mutex);
  
+     setup_listeners(pconf);
      set_signals();
  
      /*
***************
*** 3086,3102 ****
              start_mutex_released = 1;
              /* set the listen queue to 1 */
              {
! 		listen_rec *lr;
  		
! 		for (lr=listeners; lr != NULL; lr=lr->next)
! 		{
  		/* to prove a point - Ben */
  		    ap_assert(!lr->used);
  		    if(lr->used)
  		    {
  			listen(lr->fd, 1);
  		    }
! 		}
              }
          }
          if(!start_exit)
--- 3162,3178 ----
              start_mutex_released = 1;
              /* set the listen queue to 1 */
              {
! 		listen_rec *lr = listeners;
  		
! 		do {
  		/* to prove a point - Ben */
  		    ap_assert(!lr->used);
  		    if(lr->used)
  		    {
  			listen(lr->fd, 1);
  		    }
! 		    lr = lr->next;
! 		} while (lr != listeners);
              }
          }
          if(!start_exit)
***************
*** 3142,3162 ****
  
          {
  	    listen_rec *lr;
! 	    int fd;
! 	    
! 	    for (lr=listeners; lr != NULL; lr=lr->next)
! 	    {
! /*	        if(!lr->used)
!                     continue;*/
!                 fd=lr->fd;
! 	        
! 	        if(FD_ISSET(fd, &listenfds))
! 		    {
! 		    sd = fd;
! 		    break;
! 		    }
  	    }
!         }
  
          do {
              clen = sizeof(sa_client);
--- 3218,3229 ----
  
          {
  	    listen_rec *lr;
! 
! 	    lr = find_ready_listener (&listenfds);
! 	    if (lr != NULL) {
! 		sd = lr->fd;
  	    }
! 	}
  
          do {
              clen = sizeof(sa_client);
***************
*** 3198,3205 ****
      {
  	listen_rec *lr;
  	
! 	for (lr=listeners; lr != NULL; lr=lr->next)
! 	{
  	/* prove the point again */
  	    ap_assert(!lr->used);
  	    if(lr->used)
--- 3265,3272 ----
      {
  	listen_rec *lr;
  	
! 	lr = listeners;
! 	do {
  	/* prove the point again */
  	    ap_assert(!lr->used);
  	    if(lr->used)
***************
*** 3207,3213 ****
  		closesocket(lr->fd);
  		lr->fd = -1;
  	    }
! 	}
      }
  
      for(i=0; i<nthreads; i++)
--- 3274,3281 ----
  		closesocket(lr->fd);
  		lr->fd = -1;
  	    }
! 	    lr = lr->next;
! 	} while (lr != listeners);
      }
  
      for(i=0; i<nthreads; i++)
***************
*** 3468,3473 ****
--- 3536,3542 ----
      setup_prelinked_modules();
  
      server_conf = read_config (pconf, ptrans, server_confname);
+     set_max_daemons_limit();
      init_modules (pconf, server_conf);
      suexec_enabled = init_suexec();
      open_logs(server_conf, pconf);
Index: http_main.h
===================================================================
RCS file: /export/home/cvs/apache/src/http_main.h,v
retrieving revision 1.11
diff -c -3 -r1.11 http_main.h
*** http_main.h	1997/06/15 19:22:26	1.11
--- http_main.h	1997/06/29 21:24:02
***************
*** 93,101 ****
  
  void sync_scoreboard_image ();
  int update_child_status (int child_num, int status, request_rec *r);
- int get_child_status (int child_num);
- int count_busy_servers ();
- int count_idle_servers ();
- 
  unsigned int set_callback_and_alarm(void (*fn)(int), int x);
  int check_alarm();
--- 93,97 ----
Index: httpd.h
===================================================================
RCS file: /export/home/cvs/apache/src/httpd.h,v
retrieving revision 1.119
diff -c -3 -r1.119 httpd.h
*** httpd.h	1997/06/29 19:19:36	1.119
--- httpd.h	1997/06/29 21:24:04
***************
*** 231,236 ****
--- 231,251 ----
  #define HARD_SERVER_LIMIT 256
  #endif
  
+ /*
+  * (Unix, OS/2 only)
+  * Interval, in microseconds, between scoreboard maintenance.  During
+  * each scoreboard maintenance cycle the parent decides if it needs to
+  * spawn a new child (to meet MinSpareServers requirements), or kill off
+  * a child (to meet MaxSpareServers requirements).  It will only spawn or
+  * kill one child per cycle.  Setting this too low will chew cpu.  The
+  * default is probably sufficient for everyone.  But some people may want
+  * to raise this on servers which aren't dedicated to httpd and where they
+  * don't like the httpd waking up each second to see what's going on.
+  */
+ #ifndef SCOREBOARD_MAINTENANCE_INTERVAL
+ #define SCOREBOARD_MAINTENANCE_INTERVAL 1000000
+ #endif
+ 
  /* Number of requests to try to handle in a single process.  If <= 0,
   * the children don't die off.  That's the default here, since I'm still
   * interested in finding and stanching leaks.
Index: mod_status.c
===================================================================
RCS file: /export/home/cvs/apache/src/mod_status.c,v
retrieving revision 1.48
diff -c -3 -r1.48 mod_status.c
*** mod_status.c	1997/06/24 00:32:31	1.48
--- mod_status.c	1997/06/29 21:24:06
***************
*** 96,101 ****
--- 96,102 ----
  #include <time.h>
  #include "scoreboard.h"
  #include "http_log.h"
+ #include "http_conf_globals.h"
  
  #ifdef NEXT
  #include <machine/param.h>
***************
*** 279,285 ****
  	return 0;
  
      sync_scoreboard_image();
!     for (i = 0; i<HARD_SERVER_LIMIT; ++i)
      {
          score_record = get_scoreboard_info(i);
          res = score_record.status;
--- 280,286 ----
  	return 0;
  
      sync_scoreboard_image();
!     for (i = 0; i<max_daemons_limit; ++i)
      {
          score_record = get_scoreboard_info(i);
          res = score_record.status;
***************
*** 398,404 ****
      else
          rputs("Scoreboard: ",r);
  
!     for (i = 0; i<HARD_SERVER_LIMIT; ++i)
      {
  	rputc(stat_buffer[i], r);
  	if((i%STATUS_MAXLINE == (STATUS_MAXLINE - 1))&&!short_report)
--- 399,405 ----
      else
          rputs("Scoreboard: ",r);
  
!     for (i = 0; i<max_daemons_limit; ++i)
      {
  	rputc(stat_buffer[i], r);
  	if((i%STATUS_MAXLINE == (STATUS_MAXLINE - 1))&&!short_report)
***************
*** 434,440 ****
  #endif
  
  
!     for (i = 0; i<HARD_SERVER_LIMIT; ++i)
      {
          score_record=get_scoreboard_info(i);
          lres = score_record.access_count;
--- 435,441 ----
  #endif
  
  
!     for (i = 0; i<max_daemons_limit; ++i)
      {
          score_record=get_scoreboard_info(i);
          lres = score_record.access_count;