diff -Naur apache_2.0a6/10xpatchlevel-orig apache_2.0a6/10xpatchlevel --- apache_2.0a6/10xpatchlevel-orig +++ apache_2.0a6/10xpatchlevel Wed Aug 23 14:14:54 2000 @@ -0,0 +1,5 @@ +This file contains the patch level for the Accelerating Apache patches +available from + http://oss.sgi.com/projects/apache/ + +10xpatchlevel=2.0a6-0 diff -Naur apache_2.0a6/conf/httpd.conf-dist-orig apache_2.0a6/conf/httpd.conf-dist --- apache_2.0a6/conf/httpd.conf-dist-orig Sun Apr 23 21:03:37 2000 +++ apache_2.0a6/conf/httpd.conf-dist Wed Aug 23 14:17:41 2000 @@ -72,16 +72,6 @@ PidFile logs/httpd.pid # -# ScoreBoardFile: File used to store internal server process information. -# Not all architectures require this. But if yours does (you'll know because -# this file will be created when you run Apache) then you *must* ensure that -# no two invocations of Apache share the same scoreboard file. -# - -ScoreBoardFile logs/apache_runtime_status - - -# # In the standard configuration, the server will process this file, # srm.conf, and access.conf in that order. The latter two files are # now distributed empty, as it is recommended that all directives @@ -121,6 +111,13 @@ ## Server-Pool Size Regulation (MPM specific) ## +# +# ScoreBoardFile: File used to store internal server process information. +# Not all architectures require this. But if yours does (you'll know because +# this file will be created when you run Apache) then you *must* ensure that +# no two invocations of Apache share the same scoreboard file. +# + # prefork MPM # StartServers ......... number of server processes to start # MinSpareServers ...... minimum number of server processes which are kept spare @@ -133,6 +130,7 @@ MaxSpareServers 10 MaxClients 20 MaxRequestsPerChild 0 +ScoreBoardFile logs/apache_runtime_status # pthread MPM @@ -149,6 +147,7 @@ MaxSpareThreads 10 ThreadsPerChild 20 MaxRequestsPerChild 0 +ScoreBoardFile logs/apache_runtime_status # dexter MPM @@ -167,6 +166,40 @@ MaxRequestsPerChild 0 +# STM MPM +# NumVPs .............. Number of virtual processors +# StartThreads ........ Initial number of state threads PER LISTEN SOCKET +# MinSpareThreads ..... Minumum number of spare state threads PER LISTEN SOCKET +# MaxSpareThreads ..... Maximum number of spare state threads PER LISTEN SOCKET +# MaxThreads .......... Maximum number of state threads per virtual processor +# StackSize ........... Size in bytes of each thread's stack +# VPConnections ....... Number of connections (not requests) each virtual +# processor should serve before exiting +# VPBind .............. Bind a virtual processor to a specific CPU +# VPListen ............ Make a virtual processor listen only to specific +# addresses. Note that Listen and VPListen cannot be +# mixed. +# ConnectionStatus .... Enables/disables connection status information in +# the scoreboards +# ScoreboardDir ....... Name of existing directory in which the server +# maintains scoreboard file(s) +# CoreDir ............. Name of existing directory in which the server should +# dump core if necessary (default is server root) + +NumVPs 4 +StartThreads 5 +MinSpareThreads 5 +MaxSpareThreads 10 +MaxThreads 64 +StackSize 65536 +VPConnections 0 +# VPBind +# VPListen [ ... ] +ConnectionStatus on +ScoreboardDir scoreboards +# CoreDir cores + + # # Listen: Allows you to bind Apache to specific IP addresses and/or # ports, in addition to the default. See also the @@ -245,7 +278,7 @@ # don't use Group #-1 on these systems! # User nobody -Group #-1 +Group nobody # # ServerAdmin: Your address, where problems with the server should be diff -Naur apache_2.0a6/htdocs/manual/mpm.html-orig apache_2.0a6/htdocs/manual/mpm.html --- apache_2.0a6/htdocs/manual/mpm.html-orig Fri Aug 18 10:42:46 2000 +++ apache_2.0a6/htdocs/manual/mpm.html Wed Aug 23 14:17:50 2000 @@ -45,6 +45,11 @@ This is Manoj's plaything. It has a number of hybrid features that Manoj has been looking at to improve performance. Manoj + +stm +State-threaded MPM. +Mike Abbott, Accelerating Apache Project +

Windows

diff -Naur apache_2.0a6/htdocs/manual/stm.html-orig apache_2.0a6/htdocs/manual/stm.html --- apache_2.0a6/htdocs/manual/stm.html-orig +++ apache_2.0a6/htdocs/manual/stm.html Wed Aug 23 14:14:59 2000 @@ -0,0 +1,1082 @@ + + + STM MPM + + + +

+STM: State-Threaded Multi-Processing Module for Apache/2.0 +

+

+Mike Abbott - mja@sgi.com
+Accelerating Apache Project +

+ +

Contents

+ + +

1. State Threads, Pthreads, and Apache/2.0

+ +

State +threads are simple, fast, and scalable threads ideally suited for +use in Apache/2.0. They combine the simplicity of the multithreaded +programming paradigm, in which one thread supports one client +connection, with the performance and scalability of an event-driven +state machine architecture such as the Zeus web server. In other words, state +threads offer a threading API for structuring an Internet application +like Apache/2.0 as a state machine. + +

Version 2.0 of the Apache HTTP Server introduces multi-processing modules (MPMs) which, on Unix-like +platforms, mix processes and threads. Processes provide fault +containment and recovery. An error, crash, or resource leak affects +only one process not the whole server, and processes are easy to replace +when they fault. Threads provide scalability and ease of programming. +A server can maintain a large number of threads, with one thread per +client connection. + +

The MPMs for Unix-like platforms use POSIX pthreads. Pthreads are +well-known, widely available, and more or less standardized, and they +perform and scale adequately for a large class of programming problems. +However, they are not the right choice for Apache/2.0. + +

Programming pthreads is complex. Not only Apache developers but also +independent module writers and patch contributors must design and code +around race conditions, deadlocks, and data corruption, and must use +only thread-safe (reentrant) library routines. Debugging pthreaded +applications can be tricky. Furthermore, pthreads implementations +differ sufficiently to complicate programming and hinder portability, +and pthreads are either available for a given platform or not. Very few +people are going to write a complete pthreads library for their system +if there isn't one already present. + +

Pthreads can be slow. They are preemptive and concurrent and always +share data, mandating cycle-sapping mutex locking. Libraries sometimes +use locks to protect hidden resources (such as malloc/free) +which can be difficult or inefficient to work around. + +

Pthreads scale poorly in the presence of blocking I/O, which +proliferates kernel execution vehicles and slows kernel-level +scheduling. (Non-blocking I/O with pthreads also suffers because +select still blocks the kernel thread.) + +

State threads are simple to use because they are non-preemptive and +non-concurrent. They need no mutual exclusion locking and can use all +the static variables and non-reentrant library functions they want. +Programmers need not worry about race conditions or deadlocks. State +threads are available for many Unix-like platforms and since the +complete library is open source it can support additional platforms +easily. + +

State threads are fast because they schedule entirely in user mode +and since mutex locking is unnecessary they waste no time contending for +locks. + +

State threads are scalable because they take advantage of hardware +concurrency (such as multiple CPUs) without interfering with each other +at all by running in separate processes -- which also provides fault +containment. + +

State threads are the best threads for Apache/2.0 because they +provide exactly the abstractions around which Apache/2.0 is designed -- +one lightweight scheduling entity per client connection, fault +containment and recovery, high performance and scalability -- and add +simplicity of programming, ease of debugging, and the promise of +extensive portability, without suffering from the complexity of more +traditional thread packages. + +

2. The STM and STIOL

+ +

The state-threaded multi-processing module (STM MPM) for Apache/2.0 +uses a multi-process, multi-threaded (MPMT) architecture similar to the +standard MPMT MPMs except that it uses state threads instead of +pthreads. The number of processes is constant and the number of threads +per process varies with the load against the server. In state thread +lingo, each process is a virtual processor (VP) managing its own +independent set of state threads. Each state thread listens to, and +processes connections from, exactly one listening socket. + +

The STM's one-thread-per-socket design is different from the standard +MPMs, in which all threads listen to all sockets simultaneously. This +subtly changes the meaning of some configuration +directives (such as MinSpareThreads). + +

In addition to MPMs Apache/2.0 introduces I/O layers. The STM +includes its own state-threaded I/O layer called STIOL, which simply +directs all I/O operations on sockets to the state thread library's I/O +routines, a crucial and required part of using state threads. + +

Informal performance measurements reveal that the STM is faster than +any other standard MPM on all the systems it currently supports. + +

It is a credit to the Apache developers that integrating state +threads into Apache/2.0 was easy and (mostly) clean. + +

2.1. VP Binding

+ +Virtual processors (processes) may be bound to particular CPUs to +improve cache utilization. Cache utilization is further improved when +VP binding combines with listen binding and the +interrupts from the network interfaces bound to a VP are bound to the +same CPU as the VP. + +

The STM's VPBind configuration +directive binds a VP to a specific CPU. + +

2.2. Listen Binding

+ +Virtual processors may listen to all or a subset of all the listen +sockets to reduce contention on each socket. Cache utilization also is +improved when listen binding combines with VP +binding and the interrupts from the network interfaces bound to a VP +are bound to the same CPU as the VP. + +

When only one VP listens to each socket, accept serialization is no +longer necessary, further improving performance. Note that this does +not imply that a VP must listen to only one socket. + +

The STM's VPListen configuration +directive binds listeners (also called accepting or listening +sockets) to a VP. + +

3. User's Guide

+ +

The STM comes in the form of one or more patches against Apache/2.0 +from SGI's open source Accelerating Apache +Project. It requires the state threads library, which comes in the +form of a complete distribution from SGI's open source State Threads +Project. First download all the software (Apache, patches, +library), apply the patches, and build and install the state threads +library. + +

It's a good idea to try out the STM with STM_DEBUG defined at first, to catch internal +inconsistencies, especially on new platforms. Please report errors +and contribute +fixes. + +

3.1. Compilation Options

+ +

The STM and STIOL support these compile-time configuration options. +To specify these options, define them with CPPFLAGS to +configure, like this: + +

+  % env CPPFLAGS="-DSTM_LISTENER_LIMIT=2 -DSTM_DEBUG -DIRIX=65" \
+  configure --with-mpm=stm ...
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ IRIX + + + The version of SGI's Irix operating system, encoded as a + two-digit number, to take advantage of certain + high-performance features. For example, for Irix 6.2 use + IRIX=62, and for Irix 6.5.x use + IRIX=65. Use only on Irix systems. + +

Note: The presence of this option does not mean that the + features it enables are available only on Irix, just that + the STM leverages them currently only on Irix. Other + operating systems could have their own similar options if + someone contributes + the code. + +

Default: IRIX is not defined. + +

+ STIOL_DEBUG + + + Enables internal consistency checks and debugging features within the STIOL. Only + the presence or absence of this token is meaningful; the + value is ignored. + +

Default: STIOL_DEBUG is not defined so + debugging is disabled. + +

+ STM_DEBUG + + + Enables internal consistency checks and debugging features within the STM. Only + the presence or absence of this token is meaningful; the + value is ignored. + +

Default: STM_DEBUG is not defined so + debugging is disabled. + +

+ STM_LISTENER_LIMIT + + + Maximum number of listeners (Listen or VPListen directives, also known + as accepting or listening sockets). + +

Default: 8 + +

+ STM_SCORE_KEY_SIZE + + + Maximum length in bytes of the key string in the scoreboards' connection status tables, + including the string-terminating null. Keys longer than + this will be truncated. It's probably a good idea to make + STM_SCORE_KEY_SIZE + STM_SCORE_VALUE_SIZE equal + to one or more times the size of your system's largest cache + line. + +

Default: 16 + +

+ STM_SCORE_LIMIT + + + Maximum number of key/value pairs in the scoreboards' connection status tables. + When the table fills additional insertions are ignored. + +

Default: 16 + +

+ STM_SCORE_VALUE_SIZE + + + Maximum length in bytes of the value string in the scoreboards' connection status tables, + including the string-terminating null. Values longer than + this will be truncated. It's probably a good idea to make + STM_SCORE_KEY_SIZE + STM_SCORE_VALUE_SIZE equal + to one or more times the size of your system's largest cache + line. + +

Default: 48 + +

+ STM_ST_LIMIT + + + Maximum number of state threads per virtual processor (upper + bound on MaxThreads). The server-status page reports the maximum + number of threads actually used at any time, which can be + used to tune this value optimally for your system and + workload. However, lingering closes can artificially + inflate the reported maximum. + +

Default: 512 + +

+ STM_VP_LIMIT + + + Maximum number of virtual processors. + +

Default: 8 + +

+ +

3.2. Configuration Directives

+ +

The STM supports these run-time configuration directives, specified +in the file conf/httpd.conf. To invoke these directives +edit the STM section of that file, like this: + +

+  <IfModule stm.c>
+  NumVPs                  4
+  StartThreads            8
+  MinSpareThreads         8
+  MaxSpareThreads         32
+  MaxThreads              64
+  StackSize               65536
+  VPConnections           0
+  VPBind                  0 0
+  VPListen                0 100.100.100.1:80
+  VPBind                  1 0
+  VPListen                1 100.100.101.1:80
+  VPBind                  2 1
+  VPListen                2 100.100.102.1:80
+  VPBind                  3 1
+  VPListen                3 100.100.103.1:80
+  ConnectionStatus        on
+  ScoreboardDir           scoreboards
+  # CoreDir               cores
+  </IfModule>
+
+ +

The above example is for a two-CPU system with four network +interfaces. On this system the interrupts from interface 100.100.100.1 +are bound to CPU 0, 100.100.102.1 to CPU 1, and so on. + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ConnectionStatus + + + Flag enabling or disabling per-connection status in scoreboards (they can be slow to + maintain). + +

Default: on + +

+ CoreDir + + + Path of a directory where Apache will dump core if + necessary. The directory should exist before Apache + startup. + +

Default: server root directory + +

+ MaxSpareThreads + + + Maximum number of spare state threads per socket -- + not per VP. Spare threads are threads awaiting a connection + (as opposed to processing requests on established + connections). Must be between 1 and + STM_ST_LIMIT inclusive. Should be between + 1 and MaxThreads divided by the + number of listen sockets inclusive. Making this too small + can cause long latency for clients; too big and you waste + CPU cycles on low-volume servers. + +

Default: 10 + +

+ MaxThreads + + + Maximum number of state threads -- spare or busy -- per VP + across all sockets. Must be between 1 and + STM_ST_LIMIT inclusive. Making this too small + can cause long latency for clients, especially if your site + is largely dynamic (like CGI); too big and you waste memory. + +

Default: 64 + +

+ MinSpareThreads + + + Minimum number of spare state threads per socket -- + not per VP. Spare threads are threads awaiting a connection + (as opposed to processing requests on established + connections). Must be between 1 and + STM_ST_LIMIT inclusive. Should be between + 1 and MaxThreads divided by the + number of listen sockets inclusive. Making this too small + can cause long latency for clients; too big and you waste + CPU cycles on low-volume servers. + +

Default: 5 + +

+ NumVPs + + + Number of virtual processors. Must be between + 1 and STM_VP_LIMIT inclusive. + Only one or a few are needed per CPU. The server starts a + replacement when a VP dies so the number running is + constant. Making this too small can cause long latency for + clients; too big and you waste CPU cycles and memory. + +

Default: 4 + +

+ PidFile + + + Path of the file where Apache writes the process ID (PID) of + the master process. The file may or may not exist before + startup. + +

Default: server-root-directory/logs/httpd.pid + +

+ ScoreboardDir + + + Path of the directory where the STM creates scoreboard files for each VP. The + directory must already exist before Apache startup. The + scoreboard files themselves may or may not pre-exist and + have names that are just the ordinal of the VP. + +

Default: server-root-directory/scoreboards + +

+ StackSize + + + Size in bytes of each thread's stack. Must be between the + system's page size and INT_MAX inclusive. + Making this too small can cause stack overflow errors; too + big and you waste memory. + +

Default: 65536 (64 KB) + +

+ StartThreads + + + Number of state threads each VP should start initially + per socket -- not per VP. Must be between + 1 and STM_ST_LIMIT inclusive. + Should be between 1 and MaxThreads + divided by the number of listen sockets inclusive. + Generally this value should be the same as + MinSpareThreads but it can be higher to + facilitate Apache restarts on high-volume servers. + +

Default: 5 + +

+ VPBind + + + Binding VPs to CPUs. + +

Syntax: VPBind VP-ID + CPU-ID + +
where VP-ID is the number of the + virtual processor, from 0 to NumVPs - + 1 inclusive, and CPU-ID is the + number of the CPU to which to bind that VP, from + 0 to the number of CPUs in your system - 1, + inclusive. You may have multiple VPBind + directives to bind some or all of the VPs. If more than one + VPBind directive specifies the same + VP-ID the last one overrides all the + previous ones. + +

Default: VPBind is not used so no binding is + in effect. + +

+ VPConnections + + + Number of connections (not requests) each VP should serve + before exiting gracefully and allowing the master process to + replace it to reclaim resources such as memory or file + descriptors leaked by faulty modules. Must be between + 0 and INT_MAX inclusive. The + value 0 means the number of connections is + unlimited and VPs should never voluntarily exit. + +

Default: 0 +

+ VPListen + + + Binding listeners to VPs. + +

Syntax: VPListen VP-ID + IP-address:port ... + +
where VP-ID is the number of the + virtual processor, from 0 to NumVPs - + 1 inclusive, and + IP-address:port specifies the + network address (in dot notation) and port number to which + that VP should listen, separated by a colon (:). You may + specify more than one address and port. You may have + multiple VPListen directives. If more than one + VPListen directive specifies the same + VP-ID they all take effect. + +

VPListen and Listen directives + cannot be mixed. If any VP has listeners bound to it, then + all such bindings must be explicit and every VP must have at + least one listener bound to it. + +

Default: VPListen is not used so no binding + is in effect. + +

+ +

3.3. Debugging Aids

+ +

Compiling the STM with STM_DEBUG defined enables +internal consistency checks and use of its debugging features. +Compiling with STIOL_DEBUG defined enables use of the +STIOL's debugging features. To activate these features, define the +variable in the environment in which Apache will run, like this: + +

+  % env STM_INTERACTIVE=1 STM_TRACE=1 bin/httpd ...
+
+ +

By default, all of the following are not defined and therefore their +debugging features are deactivated. + +

+ + + + + + + + + + + + + + + + + +
+ ONE_PROCESS + + + Limits the STM to use only one process, to simplify use of a + debugger. This feature overrides the NumVPs + configuration directive. Normally the STM uses one process + per VP plus one master process. + +

Use this feature only for debugging as it degrades + performance and disables fault recovery. + +

+ STIOL_TRACE + + + Causes the STIOL to print lots of debugging + information, including one line at the beginning of each + procedure call within the STIOL. + +

Use with STM_INTERACTIVE to see all the + output, otherwise output will stop when the server detaches + from the controlling terminal. + +

Use this feature only for debugging as it severely + degrades performance. + +

+ STM_INTERACTIVE + + + Forces the STM to run interactively, that is, attached to + the controlling terminal and session for normal standard I/O + and keyboard signalling, not in the background detached from + I/O with the terminal as during normal operation. + +
+ STM_TRACE + + + Causes the STM to print lots of debugging + information, including one line at the beginning of each + procedure call within the STM. + +

Use with STM_INTERACTIVE to see all the + output, otherwise output will stop when the server detaches + from the controlling terminal. + +

Use this feature only for debugging as it severely + degrades performance. + +

+ +

3.4. Monitoring

+ +Scoreboards contain status information and counters. Each virtual +processor maintains its own scoreboard. Scoreboards are kept in +memory-mapped disk files (as opposed to anonymous shared memory) so that +support tools such as stmstat (included with STM) can read +scoreboard data without causing Apache to serve a page. Most of the +scoreboard data is also available on the server-status page from mod_status. + +

Unfortunately, at this time (alpha 4) the programming interface to +mod_status is limited to key-value pairs about client connections +identified by a single number, and the page it generates has a format +fixed by this interface. For the STM to report information not directly +related to connections it resorts to using negative connection numbers +so the resulting page is ugly. The author hopes to extend this +interface someday. Or you could do it and contribute +your changes. + +

This is what the STM's status information looks like, +annotated. The output from the stmstat tool is +similar but less polished. The configuration file for this server +includes the example from the section on configuration directives. + +

+
+

Apache Server Status for myserver

+ + Server Version: Apache/2.0a6 (Unix) STM/1.0
+ Server Built: Aug 22 2000 17:15:14
+
+ Current Time: Wednesday, 23-Aug-2000 13:29:39 PDT
+ + 87 connections currently being processed +

Connection -1

+
Total connections served +
130573 +
Most threads used +
39 +
Total connections served for 100.100.100.1:80 +
31798 +
Total connections served for 100.100.101.1:80 +
32986 +
Total connections served for 100.100.102.1:80 +
33115 +
Total connections served for 100.100.103.1:80 +
32674 + +

Connection -1 is a summary of all STM activity across all virtual +processors and state threads. This section shows the total number of +client connections processed since server start and also the number per +listener (accepting socket), and the maximum number of threads ever used +on any single VP (useful for fine-tuning +STM_ST_LIMIT).

+ +

Connection -1000

+
Process ID +
7008 +
Start time +
Wed Aug 23 13:12:15 2000 +
Incarnations +
1 +
Thread starts +
39 +
Thread exits +
0 +
Most threads used at once +
39 +
Connections served, prior incarnations +
0 +
Connections served, this incarnation +
31798 +
Connections served for 100.100.100.1:80 +
31798 +
Connections served by thread 0 +
843 +
Connections served by thread 1 +
805 +
Connections served by thread 2 +
1085 + +

... threads 3-62 elided for brevity ...

+ +
Connections served by thread 63 +
0 + +

Connection -1000 is a summary of activity for virtual processor +#0. More generally, info for VP #n comes under the heading for +connection -1000 - n. The process ID and start time are +self-explanatory. Incarnations reports the number of different VP's +inhabiting this slot, that is, the number of times a VP has been started +to replace a dead one, including the initial VP. (The STM restarts VPs +when the die, which can be voluntarily by exceeding its +VPConnections limit or unexpectedly by crashing.) Next +comes thread activity and connection activity. Connections served is +split by incarnations so you can see how many connections remain before +the VP reaches its VPConnections limit. Connections served +also is broken down by listener (this VP happens to listen to only one +socket) and by thread, to give a really detailed understanding of server +activity.

+ +

Connection 0

+
Status +
Writing +
Method +
GET +
Protocol +
HTTP/1.0 +

Connection 4

+
Status +
Writing +
Method +
GET +
Protocol +
HTTP/1.0 +

Connection 8

+
Status +
Writing +
Method +
GET +
Protocol +
HTTP/1.0 + +

... a bunch of normal connection status elided for brevity ... +

+ +

Connection 35

+
Status +
Writing +
Method +
GET +
Protocol +
HTTP/1.0 + +

These are the connections that VP #0 is currently processing. +These are present only when ConnectionStatus is +on.

+ +

Connection -1001

+
Process ID +
7011 +
Start time +
Wed Aug 23 13:12:15 2000 +
Incarnations +
1 +
Thread starts +
39 +
Thread exits +
0 +
Most threads used at once +
39 +
Connections served, prior incarnations +
0 +
Connections served, this incarnation +
32986 +
Connections served for 100.100.101.1:80 +
32986 +
Connections served by thread 0 +
957 +
Connections served by thread 1 +
765 +
Connections served by thread 2 +
747 + +

... threads 3-62 elided for brevity ...

+ +
Connections served by thread 63 +
0 + +

Connection -1000 is a summary of activity for VP #1. This VP +happens to listen to a different address than VP #0.

+ +

Connection 64

+
Status +
Writing +
Method +
GET +
Protocol +
HTTP/1.0 +

Connection 66

+
Status +
Writing +
Method +
GET +
Protocol +
HTTP/1.0 + +

... a bunch of normal connection status elided for brevity ... +

+ +

Connection 102

+
Status +
Writing +
Method +
GET +
Protocol +
HTTP/1.0 + +

These are the connections that VP #1 is currently +processing.

+ +

Connection -1002

+
Process ID +
7009 +
Start time +
Wed Aug 23 13:12:15 2000 +
Incarnations +
1 +
Thread starts +
39 +
Thread exits +
0 +
Most threads used at once +
39 +
Connections served, prior incarnations +
0 +
Connections served, this incarnation +
33115 +
Connections served for 100.100.102.1:80 +
33115 +
Connections served by thread 0 +
674 +
Connections served by thread 1 +
821 +
Connections served by thread 2 +
923 + +

... threads 3-62 elided for brevity ...

+ +
Connections served by thread 63 +
0 +

Connection 128

+
Status +
Writing +
Method +
GET +
Protocol +
HTTP/1.0 +

Connection 130

+
Status +
Writing +
Method +
GET +
Protocol +
HTTP/1.0 + +

... a bunch of normal connection status elided for brevity ... +

+ +

Connection 164

+
Status +
Writing +
Method +
GET +
Protocol +
HTTP/1.0 + +

This is VP #2.

+ +

Connection -1003

+
Process ID +
7012 +
Start time +
Wed Aug 23 13:12:15 2000 +
Incarnations +
1 +
Thread starts +
41 +
Thread exits +
2 +
Most threads used at once +
39 +
Connections served, prior incarnations +
0 +
Connections served, this incarnation +
32674 +
Connections served for 100.100.103.1:80 +
32674 +
Connections served by thread 0 +
617 +
Connections served by thread 1 +
656 +
Connections served by thread 2 +
1111 + +

... threads 3-62 elided for brevity ...

+ +
Connections served by thread 63 +
0 +

Connection 193

+
Status +
Writing +
Method +
GET +
Protocol +
HTTP/1.0 +

Connection 194

+
Status +
Writing +
Method +
GET +
Protocol +
HTTP/1.0 + +

... a bunch of normal connection status elided for brevity ... +

+ +

Connection 227

+
Status +
Writing +
Method +
GET +
Protocol +
HTTP/1.0 + +

This is VP #3.

+ +
+ +

4. Further Information

+ +Additional information is available from these sources. + + + +
+ +

In the on-line version +of this document, links to other parts of the Apache server +documentation may not work. + +

Copyright © 2000 Silicon Graphics, Inc. +All rights reserved.
+ + + diff -Naur apache_2.0a6/src/Makefile.in-orig apache_2.0a6/src/Makefile.in --- apache_2.0a6/src/Makefile.in-orig Sun Aug 13 19:54:41 2000 +++ apache_2.0a6/src/Makefile.in Wed Aug 23 14:17:58 2000 @@ -46,7 +46,7 @@ done htdocs-srcdir = $(srcdir)/../htdocs - + docs: include/*.h mkdir -p ./docs/api lib/apr/helpers/scandoc -ihelpers/default.pl -p./docs/api/ ./include/*.h @@ -75,6 +75,7 @@ @cp -p $(builddir)/support/rotatelogs $(bindir) @cp -p $(builddir)/support/logresolve $(bindir) @cp -p $(builddir)/support/ab $(bindir) + @cp -p $(builddir)/support/stmstat $(bindir) install-other: @test -d $(logdir) || $(MKINSTALLDIRS) $(logdir) @@ -84,9 +85,9 @@ @test -d $(includedir) || $(MKINSTALLDIRS) $(includedir) @test -d $(includedir)/xml || $(MKINSTALLDIRS) $(includedir)/xml @test -d $(includedir)/apr || $(MKINSTALLDIRS) $(includedir)/apr - @cp -p include/*.h $(srcdir)/include/*.h $(includedir) + @cp -p $(srcdir)/include/*.h $(includedir) @cp -p $(srcdir)/os/$(OS_DIR)/os.h $(includedir) @cp -p $(srcdir)/os/$(OS_DIR)/os-inline.c $(includedir) @cp -p $(srcdir)/lib/expat-lite/*.h $(includedir)/xml - @cp -p lib/apr/include/*.h $(srcdir)/lib/apr/include/*.h $(includedir)/apr + @cp -p $(srcdir)/lib/apr/include/*.h $(includedir)/apr @chmod 644 $(includedir)/*.h $(includedir)/xml/*.h $(includedir)/apr/*.h diff -Naur apache_2.0a6/src/README.MPM-orig apache_2.0a6/src/README.MPM --- apache_2.0a6/src/README.MPM-orig Tue Jul 25 18:31:23 2000 +++ apache_2.0a6/src/README.MPM Wed Aug 23 14:18:32 2000 @@ -8,7 +8,7 @@ To build (from this directory): ./buildconf -./configure --with-mpm={dexter,mpmt_pthread,prefork,...} +./configure --with-mpm={dexter,stm,mpmt_pthread,prefork,...} make Read ./configure --help for more command line options diff -Naur apache_2.0a6/src/ap/ap_hooks.c-orig apache_2.0a6/src/ap/ap_hooks.c --- apache_2.0a6/src/ap/ap_hooks.c-orig Sun Aug 13 17:55:20 2000 +++ apache_2.0a6/src/ap/ap_hooks.c Wed Aug 23 14:18:32 2000 @@ -169,6 +169,51 @@ return pHead; } +#ifdef MJA_DEBUG_HOOKS +void +dumpsort(TSort *s, int n) +{ + int i, j; + + printf("0x%08x has %d elements <\n", s, n); + for (i = 0; i < n; i++) { + printf(" [%d] = 0x%08x -> (TSort) {\n", i, &s[i]); + printf(" pData = 0x%08x -> (TSortData) {\n", s[i].pData); + if (s[i].pData) { + printf(" dummy (pFunc) = 0x%08x\n", ((TSortData *) s[i].pData)->dummy); + printf(" szName = 0x%08x = \"%1$s\"\n", ((TSortData *) s[i].pData)->szName); + printf(" aszPredecessors = 0x%08x -> (char *) {\n", ((TSortData *) s[i].pData)->aszPredecessors); + if (((TSortData *) s[i].pData)->aszPredecessors) { + for (j = 0; ((TSortData *) s[i].pData)->aszPredecessors[j]; j++) + printf(" [%d] = 0x%08x = \"%2$s\"\n", j, ((TSortData *) s[i].pData)->aszPredecessors[j]); + printf(" NULL\n"); + } + printf(" }\n"); + printf(" aszSuccessors = 0x%08x -> (char *) {\n", ((TSortData *) s[i].pData)->aszSuccessors); + if (((TSortData *) s[i].pData)->aszSuccessors) { + for (j = 0; ((TSortData *) s[i].pData)->aszSuccessors[j]; j++) + printf(" [%d] = 0x%08x = \"%2$s\"\n", j, ((TSortData *) s[i].pData)->aszSuccessors[j]); + printf(" NULL\n"); + } + printf(" }\n"); + printf(" nOrder = %d\n", ((TSortData *) s[i].pData)->nOrder); + } + printf(" }\n"); + printf(" nPredecessors = %d\n", s[i].nPredecessors); + printf(" ppPredecessors = 0x%08x -> (TSort) {\n", s[i].ppPredecessors); + if (s[i].ppPredecessors) { + for (j = 0; s[i].ppPredecessors[j]; j++) + printf(" [%d] = 0x%08x\n", j, s[i].ppPredecessors[j]); + } + printf(" NULL\n"); + printf(" }\n"); + printf(" pNext = 0x%08x\n", s[i].pNext); + printf(" }\n"); + } + printf(">\n"); +} +#endif + static apr_array_header_t *sort_hook(apr_array_header_t *pHooks,const char *szName) { apr_pool_t *p; @@ -178,7 +223,19 @@ apr_create_pool(&p, ap_global_hook_pool); pSort=prepare(p,(TSortData *)pHooks->elts,pHooks->nelts); +#ifdef MJA_DEBUG_HOOKS + if (ap_debug_module_hooks) { + printf("pre-sort:\n"); + dumpsort(pSort, pHooks->nelts); + } +#endif pSort=tsort(pSort,pHooks->nelts); +#ifdef MJA_DEBUG_HOOKS + if (ap_debug_module_hooks) { + printf("post-sort:\n"); + dumpsort(pSort, pHooks->nelts); + } +#endif pNew=apr_make_array(ap_global_hook_pool,pHooks->nelts,sizeof(TSortData)); if(ap_debug_module_hooks) printf("Sorting %s:",szName); diff -Naur apache_2.0a6/src/build/rules.mk-orig apache_2.0a6/src/build/rules.mk --- apache_2.0a6/src/build/rules.mk-orig Sun Jul 30 16:06:57 2000 +++ apache_2.0a6/src/build/rules.mk Wed Aug 23 14:18:32 2000 @@ -84,10 +84,10 @@ DEFS = -I. -I$(srcdir) -I$(top_srcdir)/modules/mpm/$(MPM_NAME) # Suffixes - + CXX_SUFFIX = cpp SHLIB_SUFFIX = so - + .SUFFIXES: .SUFFIXES: .S .c .$(CXX_SUFFIX) .lo .o .s .y .l .slo diff -Naur apache_2.0a6/src/lib/apr/APRVARS.in-orig apache_2.0a6/src/lib/apr/APRVARS.in --- apache_2.0a6/src/lib/apr/APRVARS.in-orig Tue Jul 25 12:53:28 2000 +++ apache_2.0a6/src/lib/apr/APRVARS.in Wed Aug 23 14:18:32 2000 @@ -1 +1 @@ -EXTRA_LIBS="@LIBS@" +EXTRA_LIBS="$EXTRA_LIBS @LIBS@" diff -Naur apache_2.0a6/src/main/mpm_common.c-orig apache_2.0a6/src/main/mpm_common.c --- apache_2.0a6/src/main/mpm_common.c-orig Sat Aug 5 23:07:35 2000 +++ apache_2.0a6/src/main/mpm_common.c Wed Aug 23 14:18:32 2000 @@ -74,6 +74,8 @@ #include "mpm.h" #include "mpm_common.h" +#ifndef UNCOMMON_MPM + #if HAVE_SYS_TIME_H #include /* for timeval definitions */ #endif @@ -281,3 +283,5 @@ } } #endif + +#endif /* UNCOMMON_MPM */ diff -Naur apache_2.0a6/src/modules/mpm/MPM.NAMING-orig apache_2.0a6/src/modules/mpm/MPM.NAMING --- apache_2.0a6/src/modules/mpm/MPM.NAMING-orig Thu Aug 12 11:47:11 1999 +++ apache_2.0a6/src/modules/mpm/MPM.NAMING Wed Aug 23 14:18:32 2000 @@ -29,6 +29,7 @@ mpmt_pthread .. Multi Process Model with Threading via Pthreads Variable number of processes, constant number of threads/child (= Apache/pthread) + stm ........... State-threaded. See htdocs/manual/stm.html. spmt_os2 ...... Single Process Model with Threading on OS/2 winnt ......... Single Process Model with Threading on Windows NT diff -Naur apache_2.0a6/src/modules/mpm/config.m4-orig apache_2.0a6/src/modules/mpm/config.m4 --- apache_2.0a6/src/modules/mpm/config.m4-orig Wed Jul 26 17:16:31 2000 +++ apache_2.0a6/src/modules/mpm/config.m4 Wed Aug 23 14:18:32 2000 @@ -1,7 +1,7 @@ AC_MSG_CHECKING(which MPM to use) AC_ARG_WITH(mpm, [ --with-mpm=MPM Choose the process model for Apache to use. - MPM={dexter,mpmt_beos,mpmt_pthread,prefork,spmt_os2,perchild}],[ + MPM={dexter,stm,mpmt_beos,mpmt_pthread,prefork,spmt_os2,perchild}],[ APACHE_MPM=$withval ],[ APACHE_MPM=mpmt_pthread diff -Naur apache_2.0a6/src/modules/mpm/stm/Makefile.in-orig apache_2.0a6/src/modules/mpm/stm/Makefile.in --- apache_2.0a6/src/modules/mpm/stm/Makefile.in-orig +++ apache_2.0a6/src/modules/mpm/stm/Makefile.in Wed Aug 23 14:15:37 2000 @@ -0,0 +1,7 @@ +# Makefile.in for the STM MPM. +# Copyright (c) 2000 Silicon Graphics, Inc. All rights reserved. + +LTLIBRARY_NAME = libstm.la +LTLIBRARY_SOURCES = stm.c stiol.c + +include $(top_srcdir)/build/ltlib.mk diff -Naur apache_2.0a6/src/modules/mpm/stm/Makefile.libdir-orig apache_2.0a6/src/modules/mpm/stm/Makefile.libdir --- apache_2.0a6/src/modules/mpm/stm/Makefile.libdir-orig +++ apache_2.0a6/src/modules/mpm/stm/Makefile.libdir Wed Aug 23 14:15:37 2000 @@ -0,0 +1,4 @@ +This is a place-holder which indicates to Configure that it shouldn't +provide the default targets when building the Makefile in this directory. +Instead it'll just prepend all the important variable definitions, and +copy the Makefile.tmpl onto the end. diff -Naur apache_2.0a6/src/modules/mpm/stm/config.m4-orig apache_2.0a6/src/modules/mpm/stm/config.m4 --- apache_2.0a6/src/modules/mpm/stm/config.m4-orig +++ apache_2.0a6/src/modules/mpm/stm/config.m4 Wed Aug 23 14:15:37 2000 @@ -0,0 +1,22 @@ +dnl config snippet for the STM MPM. +dnl Copyright (c) 2000 Silicon Graphics, Inc. All rights reserved. + +dnl Check for state threads library +AC_DEFUN(APACHE_MPM_STATETHREAD, [ + AC_CHECK_HEADER(st.h, [ ],[ + AC_MSG_ERROR(This MPM requires state threads. Try --with-mpm=dexter.) + ]) + AC_CHECK_FUNC(st_thread_create, [ ],[ + AC_MSG_ERROR(Can't compile state-threaded code.) + ]) +]) + +if test "$MPM_NAME" = "stm" ; then + ac_cv_enable_threads="no" + AC_CACHE_SAVE + + LIBS="$LIBS -lst" + APACHE_FAST_OUTPUT(modules/mpm/$MPM_NAME/Makefile) + APACHE_MPM_STATETHREAD +fi + diff -Naur apache_2.0a6/src/modules/mpm/stm/mpm.h-orig apache_2.0a6/src/modules/mpm/stm/mpm.h --- apache_2.0a6/src/modules/mpm/stm/mpm.h-orig +++ apache_2.0a6/src/modules/mpm/stm/mpm.h Wed Aug 23 14:15:37 2000 @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2000 Silicon Graphics, Inc. All rights reserved. + */ + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + * Portions of this software are based upon public domain software + * originally written at the National Center for Supercomputing Applications, + * University of Illinois, Urbana-Champaign. + */ + +#ifndef MODULES_MPM_STM_MPM_H +#define MODULES_MPM_STM_MPM_H + +/* Disable all the stuff in mpm_common.c that STM doesn't need. */ +#define UNCOMMON_MPM + +#endif diff -Naur apache_2.0a6/src/modules/mpm/stm/stiol.c-orig apache_2.0a6/src/modules/mpm/stm/stiol.c --- apache_2.0a6/src/modules/mpm/stm/stiol.c-orig +++ apache_2.0a6/src/modules/mpm/stm/stiol.c Wed Aug 23 14:15:37 2000 @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2000 Silicon Graphics, Inc. All rights reserved. + */ + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + * Portions of this software are based upon public domain software + * originally written at the National Center for Supercomputing Applications, + * University of Illinois, Urbana-Champaign. + */ + +/* + * ST IOL - State-Threaded I/O Layer for the STM MPM. + * + * Mike Abbott - mja@sgi.com + * Accelerating Apache Project - http://oss.sgi.com/projects/apache/ + * + * Direct all I/O operations on sockets to the state thread library's I/O + * functions. Must not use APR's functions because only ST's I/O functions + * support state thread scheduling. + */ + +#include +#include "ap_config.h" +#include "httpd.h" +#include "ap_iol.h" +#include "stiol.h" + +#ifdef STIOL_DEBUG +# define STIOL_ASSERT(x) { \ + if (!(x)) { \ + fprintf(stderr, "STIOL assertion botched: \"%s\", %s line %d, pid %d\n", \ + #x, __FILE__, __LINE__, getpid()); \ + abort(); \ + } \ +} +# define STIOL_TRACE(x) if (stiol_trace > 0) printf x +# define HIDDEN /* nothing */ +HIDDEN int stiol_trace = -1; +#else +# define STIOL_ASSERT(x) /* nothing */ +# define STIOL_TRACE(x) /* nothing */ +# define HIDDEN static +#endif + +/* + * ST IOL file descriptor and methods. + */ +typedef struct stiol_fd { + ap_iol iol; /* required iol boilerplate */ + st_netfd_t fd; /* st library's file descriptor */ + st_utime_t timeout; /* I/O timeout in microseconds */ + struct stiol_fd *next; /* in free list */ +} stiol_fd; +HIDDEN apr_status_t stiol_close(ap_iol *); +HIDDEN apr_status_t stiol_write(ap_iol *, const char *, apr_size_t, + apr_ssize_t *); +HIDDEN apr_status_t stiol_writev(ap_iol *, const struct iovec *, apr_size_t, + apr_ssize_t *); +HIDDEN apr_status_t stiol_read(ap_iol *, char *, apr_size_t, apr_ssize_t *); +HIDDEN apr_status_t stiol_setopt(ap_iol *, ap_iol_option, const void *); +HIDDEN apr_status_t stiol_getopt(ap_iol *, ap_iol_option, void *); +HIDDEN apr_status_t stiol_sendfile(ap_iol *, apr_file_t *, apr_hdtr_t *, + apr_off_t *, apr_size_t *, apr_int32_t); +HIDDEN apr_status_t stiol_shutdown(ap_iol *, int); + +/* List of free fds */ +HIDDEN stiol_fd *stiol_fd_freelist; +#ifdef STIOL_DEBUG +HIDDEN int stiol_fd_freelist_length; +#endif + +/* Exported methods */ +HIDDEN const ap_iol_methods stiol_methods = { + stiol_close, /* close */ + stiol_write, /* write */ + stiol_writev, /* writev */ + stiol_read, /* read */ + stiol_setopt, /* setopt */ + stiol_getopt, /* getopt */ + stiol_sendfile, /* sendfile */ + stiol_shutdown /* shutdown */ +}; + +HIDDEN apr_status_t +stiol_close(ap_iol *iol) +{ + stiol_fd *sfp; + + STIOL_TRACE(("%d: stiol_close(iol=%p)\n", getpid(), iol)); + + sfp = (stiol_fd *) iol; + sfp->next = stiol_fd_freelist; + stiol_fd_freelist = sfp; +#ifdef STIOL_DEBUG + stiol_fd_freelist_length++; +#endif + return (st_netfd_close(sfp->fd) == 0) ? APR_SUCCESS : errno; +} + +HIDDEN apr_status_t +stiol_write(ap_iol *iol, const char *buf, apr_size_t nbytes, + apr_ssize_t *writtenp) +{ + stiol_fd *sfp; + + STIOL_TRACE(("%d: stiol_write(iol=%p, buf=%p, nbytes=%lu)\n", getpid(), + iol, buf, (unsigned long) nbytes)); + + sfp = (stiol_fd *) iol; + *writtenp = st_write(sfp->fd, buf, nbytes, sfp->timeout); + return (*writtenp >= 0) ? APR_SUCCESS : errno; +} + +HIDDEN apr_status_t +stiol_writev(ap_iol *iol, const struct iovec *vec, apr_size_t nvec, + apr_ssize_t *writtenp) +{ + stiol_fd *sfp; + + STIOL_TRACE(("%d: stiol_writev(iol=%p, vec=%p, nvec=%lu)\n", getpid(), + iol, vec, (unsigned long) nvec)); + + sfp = (stiol_fd *) iol; + *writtenp = st_writev(sfp->fd, vec, nvec, sfp->timeout); + return (*writtenp >= 0) ? APR_SUCCESS : errno; +} + +HIDDEN apr_status_t +stiol_read(ap_iol *iol, char *buf, apr_size_t nbytes, apr_ssize_t *readp) +{ + stiol_fd *sfp; + + STIOL_TRACE(("%d: stiol_read(iol=%p, buf=%p, nbytes=%lu)\n", getpid(), + iol, buf, (unsigned long) nbytes)); + + sfp = (stiol_fd *) iol; + *readp = st_read(sfp->fd, buf, nbytes, sfp->timeout); + return (*readp >= 0) ? APR_SUCCESS : errno; +} + +HIDDEN apr_status_t +stiol_setopt(ap_iol *iol, ap_iol_option opt, const void *value) +{ + stiol_fd *sfp; + + STIOL_TRACE(("%d: stiol_setopt(iol=%p, opt=%d, value=%p)\n", getpid(), + iol, opt, value)); + + sfp = (stiol_fd *) iol; + switch (opt) { + case AP_IOL_TIMEOUT: + STIOL_TRACE(("\tAP_IOL_TIMEOUT = %d\n", *(const int *) value)); + sfp->timeout = (*(const int *) value) * 1000000; + return APR_SUCCESS; + } + return APR_EINVAL; +} + +HIDDEN apr_status_t +stiol_getopt(ap_iol *iol, ap_iol_option opt, void *value) +{ + stiol_fd *sfp; + + STIOL_TRACE(("%d: stiol_getopt(iol=%p, opt=%d)\n", getpid(), + iol, opt)); + + sfp = (stiol_fd *) iol; + switch (opt) { + case AP_IOL_TIMEOUT: + *(int *) value = (sfp->timeout != -1) ? sfp->timeout / 1000000 : -1; + return APR_SUCCESS; + } + return APR_EINVAL; +} + +HIDDEN apr_status_t +stiol_sendfile(ap_iol *iol, apr_file_t *file, apr_hdtr_t *hdtr, apr_off_t *off, + apr_size_t *len, apr_int32_t flags) +{ + STIOL_TRACE(("%d: stiol_sendfile(iol=%p, file=%p, hdtr=%p, off=%p, len=%p, flags=%#x)\n", + getpid(), iol, file, hdtr, off, len, flags)); + + return APR_ENOTIMPL; +} + +HIDDEN apr_status_t +stiol_shutdown(ap_iol *iol, int how) +{ + stiol_fd *sfp; + + STIOL_TRACE(("%d: stiol_shutdown(iol=%p, how=%d)\n", getpid(), iol, how)); + + sfp = (stiol_fd *) iol; + return (shutdown(st_netfd_fileno(sfp->fd), how) == 0) ? + APR_SUCCESS : errno; +} + +/* + * Wrap the given st fd in the stiol layer. Returns an stiol fd. + * The caller should not close the given naked fd, but let it happen + * naturally via stiol_close(). + */ +ap_iol * +stiol_attach(st_netfd_t *fd) +{ + stiol_fd *sfp; + +#ifdef STIOL_DEBUG + if (stiol_trace < 0) + stiol_trace = getenv("STIOL_TRACE") != NULL; +#endif + STIOL_TRACE(("%d: stiol_attach(fd=%p)\n", getpid(), fd)); + + /* recycle old fds for speed */ + if (stiol_fd_freelist) { + sfp = stiol_fd_freelist; + stiol_fd_freelist = stiol_fd_freelist->next; +#ifdef STIOL_DEBUG + stiol_fd_freelist_length--; +#endif + STIOL_TRACE(("\trecycled iol = %p\n", sfp)); + } else { + sfp = (stiol_fd *) calloc(1, sizeof *sfp); + sfp->iol.methods = &stiol_methods; + STIOL_TRACE(("\tnew iol = %p\n", sfp)); + } + sfp->fd = fd; + sfp->timeout = -1; + sfp->next = NULL; + + STIOL_ASSERT((void *) sfp == (void *) &sfp->iol); + return &sfp->iol; +} diff -Naur apache_2.0a6/src/modules/mpm/stm/stiol.h-orig apache_2.0a6/src/modules/mpm/stm/stiol.h --- apache_2.0a6/src/modules/mpm/stm/stiol.h-orig +++ apache_2.0a6/src/modules/mpm/stm/stiol.h Wed Aug 23 14:15:37 2000 @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000 Silicon Graphics, Inc. All rights reserved. + */ + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + * Portions of this software are based upon public domain software + * originally written at the National Center for Supercomputing Applications, + * University of Illinois, Urbana-Champaign. + */ + +#ifndef MODULES_MPM_STM_STIOL_H +#define MODULES_MPM_STM_STIOL_H + +ap_iol *stiol_attach(st_netfd_t *sock); + +#endif diff -Naur apache_2.0a6/src/modules/mpm/stm/stm.c-orig apache_2.0a6/src/modules/mpm/stm/stm.c --- apache_2.0a6/src/modules/mpm/stm/stm.c-orig +++ apache_2.0a6/src/modules/mpm/stm/stm.c Wed Aug 23 14:15:37 2000 @@ -0,0 +1,3227 @@ +/* + * Copyright (c) 2000 Silicon Graphics, Inc. All rights reserved. + */ + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + * Portions of this software are based upon public domain software + * originally written at the National Center for Supercomputing Applications, + * University of Illinois, Urbana-Champaign. + */ + +/* + * STM - State-Threaded Multi-Processing Module for Apache/2.0 + * + * Mike Abbott - mja@sgi.com + * Accelerating Apache Project - http://oss.sgi.com/projects/apache/ + * State Threads Project - http://oss.sgi.com/projects/state-threads/ + * + * State threads are non-preemptive non-concurrent threads that run on + * virtual processors which do not share data. Performance exceeds that + * of concurrent threads such as pthreads because there is no mutual + * exclusion locking and no proliferation of kernel execution vehicles + * from use of blocking I/O system calls. State threads are extremely + * lightweight, scheduling entirely in user mode. Hardware concurrency + * is available by forking multiple virtual processors. + * + * The state-threaded multi-processing module (STM MPM) for Apache/2.0 + * creates a constant number of virtual processors (VPs) and replaces + * them when they die. Each virtual processor manages its own + * independent set of state threads (STs), the number of which varies + * with load against the server. Each state thread listens to, and + * processes connections from, exactly one listening socket. + * + * When the main Apache server calls STM's ap_mpm_run() the initial + * process becomes the watchdog, waiting for children (VPs) to die or + * for a signal requesting shutdown or restart. (See stm_main().) This + * is the master process. The master process mostly sits blocked in + * wait(). It does not wake up periodically to perform maintenance such + * as the scoreboard updates other MPMs do every second. Passive idling + * via wait() is friendly to both low-volume and high-volume servers. + * Low-volume servers benefit from passive idling because they can + * completely page out Apache when it is idle for long periods and won't + * have some CPU cache lines always containing Apache data. High-volume + * servers benefit because there are no extraneous scheduling events and + * the CPU caches remain hot with data useful to serving pages quickly. + * + * The master process forks off children which become virtual + * processors. Each VP starts a number of state threads and then + * becomes the primordial state thread which blocks waiting for input + * from a pipe. (See stm_vp_main().) Only the VP itself writes to this + * pipe, and only to begin graceful shutdown of the VP and all its + * threads. This happens when a VP receives SIGHUP from the master + * process or a VP has processed all of the connections it is allowed + * and terminates itself (to reclaim resources leaked by faulty + * modules). This signal is the only way the master process and the VPs + * communicate (except for SIGTERM, upon receipt of which the VPs exit + * immediately because they set its disposition to SIG_DFL). A pipe is + * used because using write() on a pipe is guaranteed to be safe inside + * a signal handler and no ST function -- such as st_cond_signal() -- is + * safe to use inside a signal handler. + * + * Each state thread loops processing connections from a single + * listening socket. (See stm_thread_main().) (Threads could span + * multiple sockets but doing so would cloud the code a bit. There + * would be no extra system calls since everything boils down to + * select() in the state thread library anyway.) Only one ST runs on a + * VP at a time, and VPs do not share memory (except for the + * scoreboards, discussed below), so no mutual exclusion locking is + * necessary on any data, and the entire server is free to use all the + * static variables and non-reentrant library functions it wants, + * greatly simplifying programming and debugging and increasing + * performance. The current thread on each VP maintains equilibrium on + * that VP, starting a new thread or terminating itself if the number of + * spare threads exceeds the lower or upper limit, and also updates + * counters and the scoreboard for that VP. + * + * Each VP has its own scoreboard. The scoreboards are memory-mapped + * disk files (as opposed to anonymous shared memory) so that tools can + * read scoreboard data without causing Apache to serve a page. Of + * course, most of the scoreboard data is also available on the + * server-status page from mod_status. VPs have read/write access to + * all VPs' scoreboards but in practice read/write their own and only + * read others'. + * + * All I/O operations on sockets must use the state thread library's I/O + * functions because only those functions prevent blocking of the entire + * VP process and perform state thread scheduling. No APR I/O function + * must ever be used. The STM relies on the state thread I/O layer (ST + * IOL) to direct I/O operations to ST I/O functions. + * + * Virtual processors may be bound to particular CPUs to improve cache + * utilization, and may listen to all or a subset of the listen sockets. + * Cache utilization is further improved when network interface + * interrupts are bound to the same CPUs as the VPs listening to those + * interfaces. Unfortunately intercepting Listen directives to support + * this optimization introcudes some really ugly code. + * + * See htdocs/manual/stm.html for further documentation. + */ + +#include + +#define CORE_PRIVATE +#include "ap_config.h" +#include "apr_portable.h" +#include "apr_strings.h" +#include "httpd.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" +#include "http_connection.h" +#include "ap_listen.h" +#include "ap_mpm.h" +#include "mpm_status.h" +#include "unixd.h" +#include "stiol.h" + +#include +#include +#include +#include + +#if defined(IRIX) && _MIPS_SIM != _ABIO32 +/* disable warnings about unused formal parameters */ +#pragma set woff 1174 +#endif + +/* STM version identifier included in Server response header. */ +static const char stm_version[] = "STM/1.0"; + +/* + * Maximum number of listeners (Listen/VPListen directives, aka + * accept()ing sockets). The default value is arbitrary but should be + * kept small. Feel free to override the default with any value though. + */ +#ifndef STM_LISTENER_LIMIT +#define STM_LISTENER_LIMIT 8 +#endif + +/* + * Maximum number of virtual processors. The default value is arbitrary + * but should be kept small. Feel free to override the default with any + * value though. + */ +#ifndef STM_VP_LIMIT +#define STM_VP_LIMIT 8 +#endif + +/* + * Maximum number of state threads per virtual processor (upper bound on + * MaxThreads). The default value is arbitrary but should be kept + * moderate: not so small as to hinder performance, not so big as to + * waste memory. Feel free to override the default with any value + * though. The server-status page (see mod_status) reports the maximum + * number of threads actually used at any time, which can be used to + * tune this value optimally for your system and workload. + */ +#ifndef STM_ST_LIMIT +#define STM_ST_LIMIT 512 +#endif + +/* + * Maximum length in bytes of the key string in the scoreboards' + * connection status tables, including the string-terminating null. + * Keys longer than this will be truncated. The default value is + * arbitrary but should be kept small. Feel free to override the + * default with any value though. It's probably a good idea to keep + * STM_SCORE_KEY_SIZE + STM_SCORE_VALUE_SIZE one or more times the size + * of your system's largest cache line. + */ +#ifndef STM_SCORE_KEY_SIZE +#define STM_SCORE_KEY_SIZE 16 +#endif + +/* + * Maximum length in bytes of the value string in the scoreboards' + * connection status tables, including the string-terminating null. + * Values longer than this will be truncated. The default value is + * arbitrary but should be kept moderate. Feel free to override the + * default with any value though. It's probably a good idea to keep + * STM_SCORE_KEY_SIZE + STM_SCORE_VALUE_SIZE one or more times the size + * of your system's largest cache line. + */ +#ifndef STM_SCORE_VALUE_SIZE +#define STM_SCORE_VALUE_SIZE 48 +#endif + +/* + * Maximum number of key/value pairs in the scoreboards' connection + * status tables. When the table fills additional insertions are + * ignored. The default value is arbitrary but should be kept small. + * Feel free to override the default with any value though. + */ +#ifndef STM_SCORE_LIMIT +#define STM_SCORE_LIMIT 16 +#endif + +/* Debugging aids. */ +#ifdef STM_DEBUG +# define STM_ASSERT(x) { \ + if (!(x)) { \ + fprintf(stderr, "STM assertion botched: \"%s\", %s line %d, pid %d, thread %p\n", \ + #x, __FILE__, __LINE__, getpid(), stm_thread_self()); \ + abort(); \ + } \ +} +# define STM_TRACE(x) if (stm.debug.trace) printf x +# define HIDDEN /* nothing */ +#else +# define STM_ASSERT(x) /* nothing */ +# define STM_TRACE(x) /* nothing */ +# define HIDDEN static +#endif + +/* Configured VPListen directives. */ +typedef struct stm_config_vp_listen { + int num_listeners; /* number of listeners */ + struct stm_listener *listeners[STM_LISTENER_LIMIT]; /* ptrs to stm.listeners */ +} stm_config_vp_listen; + +/* + * Configuration, defaults, and methods. All STM configuration settings + * are kept in this structure. + */ +typedef struct stm_config { + /* + * Number of virtual processors. Must be between 1 and STM_VP_LIMIT + * inclusive. Only one or a few are needed per CPU. The server + * starts a replacement when a VP dies so the number running is + * constant. Making this too small can cause long latency for + * clients; too big and you waste CPU cycles and memory. + */ + int num_vps; +#define STM_CMD_NUM_VPS "NumVPs" + + /* + * Number of state threads each VP should start initially PER SOCKET + * -- not per VP. Must be between 1 and STM_ST_LIMIT inclusive. + * Should be between 1 and max_threads/num_listeners inclusive. + * Generally this value should be the same as min_spare_threads but + * it can be higher to facilitate Apache restarts on high-volume + * servers. + */ + int start_threads; +#define STM_CMD_START_THREADS "StartThreads" + + /* + * Minimum number of spare state threads PER SOCKET -- not per VP. + * Spare threads are threads awaiting a connection (as opposed to + * processing requests on established connections). Must be between + * 1 and STM_ST_LIMIT inclusive. Should be between 1 and + * max_threads/num_listeners inclusive. Making this too small can + * cause long latency for clients; too big and you waste CPU cycles + * on low-volume servers. + */ + int min_spare_threads; +#define STM_CMD_MIN_SPARE_THREADS "MinSpareThreads" + + /* + * Maximum number of spare state threads PER SOCKET -- not per VP. + * See min_spare_threads. + */ + int max_spare_threads; +#define STM_CMD_MAX_SPARE_THREADS "MaxSpareThreads" + + /* + * Maximum number of state threads -- spare or busy -- per VP across + * all sockets. Must be between 1 and STM_ST_LIMIT inclusive. + * Making this too small can cause long latency for clients, + * especially if your site is largely dynamic (e.g. CGI); too big + * and you waste memory. + */ + int max_threads; +#define STM_CMD_MAX_THREADS "MaxThreads" + + /* + * Size in bytes of each thread's stack. Must be between the + * system's page size and INT_MAX inclusive. Making this too small + * can cause stack overflow errors; too big and you waste memory. + */ + int stack_size; +#define STM_CMD_STACK_SIZE "StackSize" + + /* + * Number of connections (not requests) each VP should serve before + * exiting gracefully and allowing the master process to replace it + * to reclaim resources such as memory or file descriptors leaked by + * faulty modules. Must be between 0 and INT_MAX inclusive. The + * value 0 means the number of connections is unlimited and VPs + * should never voluntarily exit. + */ + int vp_connections; +#define STM_CMD_VP_CONNECTIONS "VPConnections" + + /* + * Binding VPs to CPUs. If vp_bind is non-null, vp_bind[n] is the + * CPU number to which VP n should be bound. + */ + int *vp_bind; +#define STM_CMD_VP_BIND "VPBind" + + /* + * Binding listeners to VPs. If vp_listen is non-null, vp_listen[n] + * is a vector of IP addresses to which VP n should listen. + */ + stm_config_vp_listen *vp_listen; +#define STM_CMD_VP_LISTEN "VPListen" + + /* + * Path of the file where Apache writes the PID of the master + * process. The file may or may not exist before startup. Except + * for the defaults structure this is always an absolute path name. + */ + const char *pid_file; +#define STM_CMD_PID_FILE "PidFile" + + /* + * Flag enabling or disabling connection status in scoreboards (they + * can be slow to maintain). Zero means disabled, nonzero means + * enabled. + */ + int connection_status; +#define STM_CMD_CONNECTION_STATUS "ConnectionStatus" + + /* + * Path of the directory where the STM creates scoreboard files for + * each VP. The directory must already exist before Apache startup. + * The scoreboard files themselves may or may not pre-exist, have + * names that are just the ordinal of the VP, and have a data format + * described in the scoreboard section below. Except for the + * defaults structure this is always an absolute path name. + */ + const char *scoreboard_dir; +#define STM_CMD_SCOREBOARD_DIR "ScoreboardDir" + + /* + * Path of a directory where Apache will dump core if necessary. + * The directory should exist before Apache startup. Except for the + * defaults structure this is always an absolute path name. A NULL + * value means Apache should dump core in whatever directory it + * happens to be in at the time, generally the server root directory. + */ + const char *core_dir; +#define STM_CMD_CORE_DIR "CoreDir" +} stm_config; +HIDDEN const stm_config stm_config_defaults = { + 4, /* num_vps */ + 5, /* start_threads */ + 5, /* min_spare_threads */ + 10, /* max_spare_threads */ + 64, /* max_threads */ + 64 * 1024, /* stack_size */ + 0, /* vp_connections */ + NULL, /* vp_bind */ + NULL, /* vp_listen */ + "logs/httpd.pid", /* pid_file */ + 1, /* connection_status */ + "scoreboards", /* scoreboard_dir */ + NULL /* core_dir */ +}; +HIDDEN const char *stm_config_set_num(cmd_parms *, const char *, int *, int, + int, const char *); +HIDDEN const char *stm_config_set_num_vps(cmd_parms *, void *, + const char *); +HIDDEN const char *stm_config_set_start_threads(cmd_parms *, void *, + const char *); +HIDDEN const char *stm_config_set_min_spare_threads(cmd_parms *, void *, + const char *); +HIDDEN const char *stm_config_set_max_spare_threads(cmd_parms *, void *, + const char *); +HIDDEN const char *stm_config_set_max_threads(cmd_parms *, void *, + const char *); +HIDDEN const char *stm_config_set_stack_size(cmd_parms *, void *, + const char *); +HIDDEN const char *stm_config_set_vp_connections(cmd_parms *, void *, + const char *); +HIDDEN const char *stm_config_set_vp_bind(cmd_parms *, void *, + const char *, const char *); +HIDDEN const char *stm_config_set_vp_listen(cmd_parms *, void *, + const char *, const char *); +HIDDEN const char *stm_config_set_ap_listen(cmd_parms *, void *, + const char *); +HIDDEN const char *stm_config_set_pid_file(cmd_parms *, void *, + const char *); +HIDDEN const char *stm_config_set_connection_status(cmd_parms *, void *, + int); +HIDDEN const char *stm_config_set_scoreboard_dir(cmd_parms *, void *, + const char *); +HIDDEN const char *stm_config_set_core_dir(cmd_parms *, void *, + const char *); +HIDDEN const command_rec stm_cmds[] = { + AP_INIT_TAKE1( + STM_CMD_NUM_VPS, + stm_config_set_num_vps, + NULL, RSRC_CONF, + "Number of virtual processors" + ), + AP_INIT_TAKE1( + STM_CMD_START_THREADS, + stm_config_set_start_threads, + NULL, RSRC_CONF, + "Initial number of state threads per listen socket" + ), + AP_INIT_TAKE1( + STM_CMD_MIN_SPARE_THREADS, + stm_config_set_min_spare_threads, + NULL, RSRC_CONF, + "Minumum number of spare state threads per listen socket" + ), + AP_INIT_TAKE1( + STM_CMD_MAX_SPARE_THREADS, + stm_config_set_max_spare_threads, + NULL, RSRC_CONF, + "Maxumum number of spare state threads per listen socket" + ), + AP_INIT_TAKE1( + STM_CMD_MAX_THREADS, + stm_config_set_max_threads, + NULL, RSRC_CONF, + "Maximum number of state threads per virtual processor" + ), + AP_INIT_TAKE1( + STM_CMD_STACK_SIZE, + stm_config_set_stack_size, + NULL, RSRC_CONF, + "Size in bytes of each thread's stack" + ), + AP_INIT_TAKE1( + STM_CMD_VP_CONNECTIONS, + stm_config_set_vp_connections, + NULL, RSRC_CONF, + "Number of connections each virtual processor should serve before exiting" + ), + AP_INIT_TAKE2( + STM_CMD_VP_BIND, + stm_config_set_vp_bind, + NULL, RSRC_CONF, + "Bind a virtual processor to a specific CPU" + ), + AP_INIT_ITERATE2( + STM_CMD_VP_LISTEN, + stm_config_set_vp_listen, + NULL, RSRC_CONF, + "List of ip-address:port-numbers to which a virtual processor should listen" + ), + AP_INIT_TAKE1( + STM_CMD_PID_FILE, + stm_config_set_pid_file, + NULL, RSRC_CONF, + "Name of file in which the server records its PID upon startup" + ), + AP_INIT_FLAG( + STM_CMD_CONNECTION_STATUS, + stm_config_set_connection_status, + NULL, RSRC_CONF, + "Enables/disables connection status information in the scoreboards" + ), + AP_INIT_TAKE1( + STM_CMD_SCOREBOARD_DIR, + stm_config_set_scoreboard_dir, + NULL, RSRC_CONF, + "Name of existing directory in which the server maintains scoreboard file(s)" + ), + AP_INIT_TAKE1( + STM_CMD_CORE_DIR, + stm_config_set_core_dir, + NULL, RSRC_CONF, + "Name of existing directory in which the server should dump core if necessary" + ), + UNIX_DAEMON_COMMANDS + + /* + * Redirect Listen directives to our own handler. This is a gross + * kludge but so is the existence of, definition of, and MPMs' + * dependence on the LISTEN_COMMANDS macro. + */ +#define ap_set_listener stm_config_set_ap_listen + LISTEN_COMMANDS +#undef ap_set_listener + + { NULL } +}; +HIDDEN void stm_pre_config(apr_pool_t *, apr_pool_t *, apr_pool_t *); +HIDDEN void stm_post_config(apr_pool_t *, apr_pool_t *, apr_pool_t *, + server_rec *); +HIDDEN void stm_hooks(void); + +/* VPs and STs health indicators. */ +typedef enum stm_health { + STM_HEALTH_DEAD, /* entity is not running */ + STM_HEALTH_DYING, /* entity has been asked to terminate */ + STM_HEALTH_ALIVE /* entity is running */ +} stm_health; + +/* Master process state. */ +typedef enum stm_state { + STM_STATE_SHUTDOWN, /* shutdown requested or in progress */ + STM_STATE_RUN, /* running */ + STM_STATE_GRACEFUL, /* graceful restart requested or in progress */ + STM_STATE_RESTART /* restart requested or in progress */ +} stm_state; + +/* Evaluation of seriousness of child process's death. */ +typedef enum stm_grief { + STM_GRIEF_NORMAL, /* normal and/or expected */ + STM_GRIEF_FATAL, /* catastrophic */ + STM_GRIEF_SURPRISE /* unexpected but not tragic */ +} stm_grief; + +#ifdef STM_DEBUG +/* Debugging aids. */ +typedef struct stm_debug { + int interactive; /* when nonzero don't detach */ + int one_process; /* when nonzero restrict to a single process */ + int trace; /* when nonzero trace function calls */ +} stm_debug; +#endif + +/* Datum about a connection. Key/value type mandated by mod_status. */ +typedef struct stm_score { + char key[STM_SCORE_KEY_SIZE]; + char value[STM_SCORE_VALUE_SIZE]; +} stm_score; + +/* + * Per-VP scoreboard and methods. Fields are explicitly sized because + * this is also the on-disk file format. When changing this structure + * also change the version string so tools can correctly parse the data + * and also update the stmstat tool. The version string should be + * exactly STM_SCOREBOARD_VERSION_LENGTH bytes long excluding the null. + * Do not change STM_SCOREBOARD_VERSION_LENGTH. + */ +#define STM_SCOREBOARD_VERSION_LENGTH 16 +#define STM_SCOREBOARD_VERSION "STM-MPM-sb-0001\n" +typedef struct stm_scoreboard { + char version[STM_SCOREBOARD_VERSION_LENGTH]; /* see above */ + apr_int32_t st_limit; /* size of arrays below */ + apr_int32_t listener_limit; /* size of arrays below */ + apr_int32_t score_limit; /* size of arrays below */ + apr_int32_t key_size; /* size of arrays below */ + apr_int32_t value_size; /* size of arrays below */ + apr_int32_t pid; /* this VP's PID */ + apr_time_t start_time; /* server's start time */ + apr_time_t vp_start_time; /* this VP's start time */ + apr_int32_t incarnations; /* # of different VPs in this slot */ + apr_int32_t thread_starts; /* # of thread start events */ + apr_int32_t thread_exits; /* # of thread exit events */ + apr_int32_t most_threads; /* most threads ever used */ + apr_int32_t historic_vp_connections;/* # of conns, prior incarnations */ + apr_int32_t vp_connections; /* # of conns, this incarnation */ + apr_int32_t thread_connections[STM_ST_LIMIT]; /* per thread */ + apr_int32_t listener_connections[STM_LISTENER_LIMIT]; /* per listener */ + char listeners[STM_LISTENER_LIMIT][24]; /* IP address:port */ + stm_score scores[STM_ST_LIMIT][STM_SCORE_LIMIT]; /* per-conn data */ +} stm_scoreboard; +HIDDEN stm_scoreboard *stm_scoreboard_new(int); +HIDDEN void stm_scoreboard_init(stm_scoreboard *); + +/* + * Listener and methods. These listeners are wrappers around the more + * traditional ap_listen_rec so we can accept on an st_netfd_t rather + * than an apr_socket_t. + */ +typedef apr_uint32_t stm_port; /* simplify profusion of IP port types */ +typedef apr_uint32_t stm_addr; /* simplify profusion of IP address types */ +typedef struct stm_listener { + int id; /* == (int) (this - stm.listeners) */ + const char *addr_string; /* inet_ntoa(addr.sin_addr) */ + struct sockaddr_in addr; /* IP addr and port in network order */ + int unique; /* is addr unique (not INADDR_ANY)? */ + int share_count; /* number of VPs using this listener */ + ap_listen_rec *alr; /* standard listener */ + st_netfd_t sd; /* ST version of alr->sd */ + int spare_threads; /* # of spare threads right now */ +} stm_listener; +HIDDEN void stm_listener_init(stm_listener *); +HIDDEN stm_listener *stm_listener_add(const char *, stm_addr, stm_port, int *); +HIDDEN int stm_listener_usurp(stm_listener *, ap_listen_rec *); +HIDDEN void stm_listener_revert(stm_listener *); + +/* + * Thread and methods. + */ +typedef struct stm_thread { + int id; /* == (int) (this - vp->threads) */ + stm_health health; /* how is this thread doing? */ + st_thread_t st; /* ST for this thread */ + struct stm_vp *vp; /* VP on which this thread runs */ + stm_listener *listener; /* listener on which to accept() */ + int connected; /* connected to a client right now? */ +} stm_thread; +HIDDEN void stm_thread_init(stm_thread *, struct stm_vp *); +HIDDEN int stm_thread_start(stm_thread *, stm_listener *); +HIDDEN stm_thread *stm_thread_self(void); +HIDDEN void *stm_thread_main(void *); +HIDDEN void stm_thread_process_connection(const stm_listener *, apr_pool_t *, + st_netfd_t, const struct sockaddr_in *, long); +HIDDEN void stm_thread_stop(stm_thread *); +HIDDEN int stm_thread_stopped(stm_thread *); + +/* + * Virtual processor and methods. + */ +typedef struct stm_vp { + int id; /* == (int) (this - stm.vps) */ + pid_t pid; /* PID of VP process */ + stm_health health; /* how is this VP doing? */ + apr_pool_t *pool; /* pool for lifetime of VP */ + int stop_pipe[2]; /* for asynchronous-safe termination */ + int num_listeners; /* number of listeners for this VP */ + stm_listener *listeners[STM_LISTENER_LIMIT]; /* ptrs to stm.listeners */ + int connections; /* conns remaining before suicide */ + int max_threads_hit; /* reported hitting thread ceiling? */ + int num_threads; /* number of non-dead threads */ + stm_thread threads[STM_ST_LIMIT]; /* threads */ + stm_thread *dead_threads[STM_ST_LIMIT];/* to recycle them quickly */ + stm_scoreboard *scoreboard; /* scoreboard in shared memory */ +} stm_vp; +HIDDEN int stm_vp_init(stm_vp *); +HIDDEN int stm_vp_listen(stm_vp *); +HIDDEN int stm_vp_start(stm_vp *); +HIDDEN void stm_vp_bind(stm_vp *); +HIDDEN void stm_vp_signals(void); +HIDDEN void stm_vp_main(stm_vp *); +HIDDEN void stm_vp_stop(stm_vp *); +HIDDEN void stm_vp_stop_self(stm_vp *); +HIDDEN void stm_vp_onsig_stop(int); +HIDDEN void stm_vp_onexit(void); +HIDDEN apr_status_t stm_vp_cleanup(void *); + +/* + * Global data and methods. All STM data is in this one place for easy + * debugging. + */ +HIDDEN struct stm { + apr_time_t start_time; /* server start time */ + apr_pool_t *pool; /* main pool */ + server_rec *server; /* for logging */ + stm_state state; /* run state */ + int initialized; /* stm_init() ever called? */ + int num_restarts; /* number of server restarts */ + pid_t pid; /* pid of master process */ + stm_config config; /* config parameters */ + int num_listeners; /* number of listeners */ + stm_listener listeners[STM_LISTENER_LIMIT]; + int num_vps; /* number of VPs */ + stm_vp vps[STM_VP_LIMIT]; /* table of VP info */ + int self_key; /* for stm_thread_self() */ +#ifdef STM_DEBUG + stm_debug debug; /* debugging parameters */ +#endif +} stm; +HIDDEN int stm_init(apr_pool_t *, server_rec *); +HIDDEN void stm_fini(void); +HIDDEN void stm_signals(void); +HIDDEN void stm_main(void); +HIDDEN void stm_reap(pid_t, int); +HIDDEN stm_grief stm_mourn(pid_t, int); +HIDDEN void stm_doomsday(void); + +/* Signal helper routines */ +HIDDEN void stm_signal_catch(int, void (*)(int)); +HIDDEN void stm_signal_default(int); +HIDDEN void stm_signal_ignore(int); +HIDDEN void stm_signal_core(int); + +/* Master process's signal handlers */ +HIDDEN void stm_onsig_core(int); +HIDDEN void stm_onsig_restart(int); +HIDDEN void stm_onsig_shutdown(int); + +/* Miscellany */ +HIDDEN long stm_id_make(int, int); +HIDDEN int stm_id_break(long, int *, int *); +HIDDEN void stm_disable_nagle(st_netfd_t); + +/* Exported API routines */ +void ap_start_shutdown(void); +void ap_start_restart(int); +int ap_graceful_stop_signalled(void); +void ap_reset_connection_status(long); +void ap_update_connection_status(long, const char *, const char *); +apr_array_header_t *ap_get_status_table(apr_pool_t *); +int ap_mpm_run(apr_pool_t *, apr_pool_t *, server_rec *); + +/* Module definition */ +module MODULE_VAR_EXPORT mpm_stm_module = { + MPM20_MODULE_STUFF, + NULL, /* run before apache parses argv */ + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + stm_cmds, /* command table */ + NULL, /* handlers */ + stm_hooks /* register_hooks */ +}; + + +/*********************************************************************** + * Configuration + */ + +/* + * Standard MPM pre-config hook: initializes/resets all configuration + * parameters to their default values. + */ +HIDDEN void +stm_pre_config(apr_pool_t *conf_pool, apr_pool_t *log_pool, + apr_pool_t *tmp_pool) +{ + int i; + +#ifdef STM_DEBUG + if (stm.pid == 0) { + /* set these just once */ + stm.debug.interactive = getenv("STM_INTERACTIVE") != NULL; + stm.debug.one_process = getenv("ONE_PROCESS") != NULL; + stm.debug.trace = getenv("STM_TRACE") != NULL; + } +#endif + + STM_TRACE(("%d: stm_pre_config() #%d\n", getpid(), stm.num_restarts)); + + /* it would be bad to reset stm.config accidentally while running */ + STM_ASSERT(stm.state != STM_STATE_RUN); + + /* called twice on normal startup; detach only on second one */ + if (stm.num_restarts++ == 1) { +#ifdef STM_DEBUG + setlinebuf(stdout); + if (!stm.debug.interactive && !stm.debug.one_process) +#endif + if (apr_detach() != APR_SUCCESS) + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, + errno, NULL, + "stm_pre_config: could not detach from controlling terminal: apr_detach"); + + stm.pid = getpid(); + } + + unixd_pre_config(); + ap_listen_pre_config(); + + stm.num_listeners = 0; + for (i = 0; i < STM_LISTENER_LIMIT; i++) + stm_listener_init(&stm.listeners[i]); + + if (stm.config.vp_bind) { + free(stm.config.vp_bind); + stm.config.vp_bind = NULL; + } + if (stm.config.vp_listen) { + free(stm.config.vp_listen); + stm.config.vp_listen = NULL; + } + + stm.config = stm_config_defaults; + stm.config.pid_file = ap_server_root_relative(conf_pool, + stm_config_defaults.pid_file); + stm.config.scoreboard_dir = ap_server_root_relative(conf_pool, + stm_config_defaults.scoreboard_dir); + stm.config.core_dir = apr_pstrdup(conf_pool, ap_server_root); +} + +/* + * Standard MPM post-config hook. + */ +HIDDEN void +stm_post_config(apr_pool_t *conf_pool, apr_pool_t *log_pool, + apr_pool_t *tmp_pool, server_rec *server) +{ + STM_TRACE(("%d: stm_post_config()\n", getpid())); + + ap_add_version_component(conf_pool, stm_version); +} + +/* + * Standard module hook registration. + */ +HIDDEN void +stm_hooks(void) +{ + STM_TRACE(("%d: stm_hooks()\n", getpid())); + + INIT_SIGLIST() + + ap_hook_pre_config(stm_pre_config, NULL, NULL, AP_HOOK_MIDDLE); + ap_hook_post_config(stm_post_config, NULL, NULL, AP_HOOK_MIDDLE); +} + +/* + * Utility function to set an integer configuration parameter if the new + * value is in bounds: + * max_value >= 0 min_value <= atoi(value_string) <= max_value + * max_value < 0 min_value <= atoi(value_string) + * Returns an error string, or NULL on success. + */ +HIDDEN const char * +stm_config_set_num(cmd_parms *cmd, const char *value_string, int *paramp, + int min_value, int max_value, const char *max_name) +{ + const char *err; + int value; + + STM_TRACE(("%d: stm_config_set_num(cmd->\"%s\", value_string=\"%s\", min_value=%d, max_value=%d, max_name=\"%s\")\n", + getpid(), cmd->cmd->name, value_string, min_value, max_value, max_name)); + + err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err == NULL) { + value = atoi(value_string); + if (value >= min_value) { + if (max_value < 0 || value <= max_value) + *paramp = value; + else { + ap_log_error(APLOG_MARK, + APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, NULL, + "value %d for %s is above maximum %d, using maximum" + "; re-compile with larger %s if desired", + value, cmd->cmd->name, max_value, max_name); + *paramp = max_value; + } + } else { + ap_log_error(APLOG_MARK, + APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, NULL, + "value %d for %s is below minimum %d, using minimum", + value, cmd->cmd->name, min_value); + *paramp = min_value; + } + } + + return err; +} + +/* + * Set the num_vps configuration parameter if the new value is in + * bounds. Returns error string, or NULL on success. + */ +HIDDEN const char * +stm_config_set_num_vps(cmd_parms *cmd, void *dummy, const char *num) +{ + return stm_config_set_num(cmd, num, &stm.config.num_vps, + 1, STM_VP_LIMIT, "STM_VP_LIMIT"); +} + +/* + * Set the start_threads configuration parameter if the new value is in + * bounds. Returns error string, or NULL on success. + */ +HIDDEN const char * +stm_config_set_start_threads(cmd_parms *cmd, void *dummy, const char *num) +{ + return stm_config_set_num(cmd, num, &stm.config.start_threads, + 1, STM_ST_LIMIT, "STM_ST_LIMIT"); +} + +/* + * Set the min_spare_threads configuration parameter if the new value is + * in bounds. Returns error string, or NULL on success. + */ +HIDDEN const char * +stm_config_set_min_spare_threads(cmd_parms *cmd, void *dummy, const char *num) +{ + return stm_config_set_num(cmd, num, &stm.config.min_spare_threads, + 1, STM_ST_LIMIT, "STM_ST_LIMIT"); +} + +/* + * Set the max_spare_threads configuration parameter if the new value is + * in bounds. Returns error string, or NULL on success. + */ +HIDDEN const char * +stm_config_set_max_spare_threads(cmd_parms *cmd, void *dummy, const char *num) +{ + return stm_config_set_num(cmd, num, &stm.config.max_spare_threads, + 1, STM_ST_LIMIT, "STM_ST_LIMIT"); +} + +/* + * Set the max_threads configuration parameter if the new value is in + * bounds. Returns error string, or NULL on success. + */ +HIDDEN const char * +stm_config_set_max_threads(cmd_parms *cmd, void *dummy, const char *num) +{ + return stm_config_set_num(cmd, num, &stm.config.max_threads, + 1, STM_ST_LIMIT, "STM_ST_LIMIT"); +} + +/* + * Set the stack_size configuration parameter if the new value is in + * bounds. Returns error string, or NULL on success. + */ +HIDDEN const char * +stm_config_set_stack_size(cmd_parms *cmd, void *dummy, const char *num) +{ + return stm_config_set_num(cmd, num, &stm.config.stack_size, + getpagesize(), -1, NULL); +} + +/* + * Set the vp_connections configuration parameter if the new value is in + * bounds. Returns error string, or NULL on success. + */ +HIDDEN const char * +stm_config_set_vp_connections(cmd_parms *cmd, void *dummy, const char *num) +{ + return stm_config_set_num(cmd, num, &stm.config.vp_connections, + 0, -1, NULL); +} + +/* + * Add to the vp_bind configuration parameter if the new values are in + * bounds. Returns error string, or NULL on success. + */ +HIDDEN const char * +stm_config_set_vp_bind(cmd_parms *cmd, void *dummy, const char *vp_string, + const char *cpu_string) +{ + const char *err; + + STM_TRACE(("%d: stm_config_set_vp_bind(vp_string=\"%s\", cpu_string=\"%s\")\n", + getpid(), vp_string, cpu_string)); + + err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err == NULL) { + int v; + char *stop; + + v = (int) strtol(vp_string, &stop, 0); + if (stop != vp_string && *stop == 0) { + /* + * Compare against STM_VP_LIMIT here because num_vps may not + * have been set yet (could appear later in config file). + * stm_init() warns about bindings for unused VPs. + */ + if (v >= 0 && v < STM_VP_LIMIT) { + int c; + + c = (int) strtol(cpu_string, &stop, 0); + if (stop != cpu_string && *stop == 0 && c >= 0) { + if (stm.config.vp_bind == NULL) { + int i; + + stm.config.vp_bind = (int *) malloc(STM_VP_LIMIT * + sizeof *stm.config.vp_bind); + for (i = 0; i < STM_VP_LIMIT; i++) + stm.config.vp_bind[i] = -1; + } + stm.config.vp_bind[v] = c; + } else + err = "CPU identifier must be a non-negative number"; + } else + err = apr_psprintf(cmd->pool, + "value %d for %s is out of range, must be between 0 and %d inclusive" + "; re-compile with larger STM_VP_LIMIT if desired", + v, cmd->cmd->name, STM_VP_LIMIT - 1); + } else + err = "virtual processor identifier must be a number"; + } + + return err; +} + +/* + * Add to the vp_listen configuration parameter if the new values are in + * bounds. Returns error string, or NULL on success. + */ +HIDDEN const char * +stm_config_set_vp_listen(cmd_parms *cmd, void *dummy, const char *vp_string, + const char *listen_string) +{ + const char *err; + + STM_TRACE(("%d: stm_config_set_vp_listen(vp_string=\"%s\", listen_string=\"%s\")\n", + getpid(), vp_string, listen_string)); + + err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err == NULL && ap_listeners && stm.config.vp_listen == NULL) + err = "cannot mix Listen and " STM_CMD_VP_LISTEN " directives."; + if (err == NULL) { + int v; + char *stop; + + v = (int) strtol(vp_string, &stop, 0); + if (stop != vp_string && *stop == 0) { + /* + * Compare against STM_VP_LIMIT here because num_vps may + * not have been set yet (could appear later in config + * file). stm_init() warns about bindings for unused + * VPs. + */ + if (v >= 0 && v < STM_VP_LIMIT) { + char *listen_copy; + char *port_string; + + listen_copy = apr_pstrdup(cmd->pool, listen_string); + port_string = strchr(listen_copy, ':'); + if (port_string) { + stm_port port; + + *port_string++ = 0; + + /* should be strtoul() but can't use it, oh well */ + port = (stm_port) strtol(port_string, &stop, 0); + if (stop != port_string && *stop == 0) { + int addr_ok; + struct in_addr addr; + + port = htons(port); + + addr_ok = inet_aton(listen_copy, &addr); + if (!addr_ok && !strcmp(listen_copy, "*")) { + addr.s_addr = htonl(INADDR_ANY); + addr_ok = 1; + } + + if (addr_ok) { + stm_config_vp_listen *vlp; + int new; + stm_listener *lp; + + if (stm.config.vp_listen == NULL) + stm.config.vp_listen = (stm_config_vp_listen *) + calloc(STM_VP_LIMIT, + sizeof *stm.config.vp_listen); + + vlp = &stm.config.vp_listen[v]; + new = 0; + lp = (vlp->num_listeners < STM_LISTENER_LIMIT) ? + stm_listener_add(listen_copy, addr.s_addr, + port, &new) : NULL; + if (lp) { + vlp->listeners[vlp->num_listeners++] = lp; + if (new) { + /* + * Call the regular Listen directive + * handler to add a standard + * listener to ap_listeners. This + * is a gross kludge but so is the + * way Apache manages listeners. + */ + err = ap_set_listener(cmd, dummy, + (addr.s_addr == htonl(INADDR_ANY)) ? + port_string : listen_string); + + /* + * An error from ap_set_listener() + * means our call to it is wrong and + * should be fixed. + */ + STM_ASSERT(err == NULL); + } + } else + err = "too many listeners; re-compile with larger STM_LISTENER_LIMIT if desired"; + } else + err = "invalid IP address"; + } else + err = "invalid port number"; + } else + err = "listen address must be of the form: ip-address:port-number or *:port-number"; + } else + err = apr_psprintf(cmd->pool, + "value %d for %s is out of range, must be between 0 and %d inclusive" + "; re-compile with larger STM_VP_LIMIT if desired", + v, cmd->cmd->name, STM_VP_LIMIT - 1); + } else + err = "virtual processor identifier must be a number"; + } + + return err; +} + +/* + * Intercept standard Listen directives, saving information that is not + * readily available later. Returns error string, or NULL on success. + */ +HIDDEN const char * +stm_config_set_ap_listen(cmd_parms *cmd, void *dummy, + const char *listen_string) +{ + const char *err; + + STM_TRACE(("%d: stm_config_set_ap_listen(listen_string=\"%s\")\n", + getpid(), listen_string)); + + err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err == NULL && stm.config.vp_listen) + err = "cannot mix Listen and " STM_CMD_VP_LISTEN " directives"; + if (err == NULL) { + char *ips, *ports; + stm_port port; + struct in_addr addr; + + /* + * Parse the Listen directive's argument so we can fill in the + * address and port number in the stm_listener. Then call the + * regular Listen directive handler since we can't call + * alloc_listener() directly. + */ + + ips = apr_pstrdup(cmd->pool, listen_string); + + /* + ***************************************** + * begin copied code from ap_set_listener() + */ + ports = strchr(ips, ':'); + if (ports != NULL) { + if (ports == ips) { + return "Missing IP address"; + } + else if (ports[1] == '\0') { + return "Address must end in :"; + } + *(ports++) = '\0'; + } + else { + ports = ips; + } + + port = atoi(ports); + if (!port) { + return "Port must be numeric"; + } + + if (ports == ips) { /* no address */ + ips = APR_ANYADDR; + } + else { + ips[(ports - ips) - 1] = '\0'; + } + /* + * end copied code from ap_set_listener() + ***************************************** + */ + + if (inet_aton(ips, &addr)) { + int new; + + if (stm_listener_add(ips, addr.s_addr, htons(port), &new)) { + if (new) + err = ap_set_listener(cmd, dummy, listen_string); + /* else ignore it */ + } else + err = "too many listeners; re-compile with larger STM_LISTENER_LIMIT if desired"; + } else + err = "invalid IP address"; + } + + return err; +} + +/* + * Set the pid_file configuration parameter. Returns error string, or + * NULL on success. + */ +HIDDEN const char * +stm_config_set_pid_file(cmd_parms *cmd, void *dummy, const char *path) +{ + const char *err; + + STM_TRACE(("%d: stm_config_set_pid_file(path=\"%s\")\n", getpid(), path)); + + err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err == NULL) + stm.config.pid_file = ap_server_root_relative(stm.pool, path); + + return err; +} + +/* + * Set the connection_status configuration parameter. Returns error + * string, or NULL on success. + */ +HIDDEN const char * +stm_config_set_connection_status(cmd_parms *cmd, void *dummy, int flag) +{ + const char *err; + + STM_TRACE(("%d: stm_config_set_connection_status(flag=%d)\n", getpid(), + flag)); + + err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err == NULL) + stm.config.connection_status = flag; + + return err; +} + +/* + * Set the scoreboard_dir configuration parameter if the directory + * exists. Returns error string, or NULL on success. + */ +HIDDEN const char * +stm_config_set_scoreboard_dir(cmd_parms *cmd, void *dummy, const char *path) +{ + const char *err; + + STM_TRACE(("%d: stm_config_set_scoreboard_dir(path=\"%s\")\n", getpid(), + path)); + + err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err == NULL) { + const char *rpath; + struct stat stbuf; + + rpath = ap_server_root_relative(stm.pool, path); + if (stat(rpath, &stbuf) == 0 && S_ISDIR(stbuf.st_mode)) + stm.config.scoreboard_dir = rpath; + else + err = apr_psprintf(cmd->pool, + "%s \"%s\" does not exist or is not a directory", + cmd->cmd->name, rpath); + } + + return err; +} + +/* + * Set the core_dir configuration parameter if the directory exists. + * Returns error string, or NULL on success. + */ +HIDDEN const char * +stm_config_set_core_dir(cmd_parms *cmd, void *dummy, const char *path) +{ + const char *err; + + STM_TRACE(("%d: stm_config_set_core_dir(path=\"%s\")\n", getpid(), path)); + + err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err == NULL) { + const char *rpath; + struct stat stbuf; + + rpath = ap_server_root_relative(stm.pool, path); + if (stat(rpath, &stbuf) == 0 && S_ISDIR(stbuf.st_mode)) + stm.config.core_dir = rpath; + else + err = apr_psprintf(cmd->pool, + "%s \"%s\" does not exist or is not a directory", + cmd->cmd->name, rpath); + } + + return err; +} + + +/*********************************************************************** + * Scoreboard + */ + +/* + * Create and return a pointer to a new scoreboard in shared memory for + * VP slot n, or NULL on failure. + */ +HIDDEN stm_scoreboard * +stm_scoreboard_new(int n) +{ + stm_scoreboard *sbp; + char *path; + int fd; + + STM_TRACE(("%d: stm_scoreboard_new(n=%d)\n", getpid(), n)); + + sbp = NULL; + + path = apr_psprintf(stm.pool, "%s/%d", stm.config.scoreboard_dir, n); + fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0600); + if (fd >= 0) { + stm_scoreboard zsc; + + /* zero-fill the new file to exactly the right length */ + memset(&zsc, 0, sizeof zsc); + if (write(fd, &zsc, sizeof zsc) == sizeof zsc) { + void *vp; + + vp = mmap(0, sizeof *sbp, PROT_READ | PROT_WRITE, MAP_SHARED, fd, + 0); + if (vp != (void *) -1) + sbp = (stm_scoreboard *) vp; + else + ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, + "stm_scoreboard_new: cannot map %ld bytes read-write in \"%s\": mmap", + (long) sizeof *sbp, path); + } else + ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, + "stm_scoreboard_new: cannot zero-fill %ld bytes in \"%s\": write", + (long) sizeof zsc, path); + + close(fd); + } else + ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, + "stm_scoreboard_new: cannot create \"%s\" read-write: open", path); + + return sbp; +} + +/* + * Initialize/reset the given scoreboard. + */ +HIDDEN void +stm_scoreboard_init(stm_scoreboard *sb) +{ + int i, j; + + STM_TRACE(("%d: stm_scoreboard_init(sb=%p)\n", getpid(), sb)); + + /* + * All the values start out as zero but this function is used for + * both init and reset. Resetting a field to zero here makes it + * keep track of only one incarnation of a VP. Not resetting it + * makes it keep track since server start. + */ + + memcpy(sb->version, STM_SCOREBOARD_VERSION, sizeof sb->version); + sb->st_limit = STM_ST_LIMIT; + sb->listener_limit = STM_LISTENER_LIMIT; + sb->score_limit = STM_SCORE_LIMIT; + sb->key_size = STM_SCORE_KEY_SIZE; + sb->value_size = STM_SCORE_VALUE_SIZE; + sb->pid = -1; + sb->start_time = stm.start_time; + sb->vp_start_time = 0; + /* leave sb->incarnations alone */ + sb->thread_starts = 0; + sb->thread_exits = 0; + /* leave sb->most_threads alone */ + sb->historic_vp_connections += sb->vp_connections; + sb->vp_connections = 0; + for (i = 0; i < STM_ST_LIMIT; i++) { + sb->thread_connections[i] = 0; + for (j = 0; j < STM_SCORE_LIMIT; j++) + sb->scores[i][j].key[0] = 0; + } + for (i = 0; i < STM_LISTENER_LIMIT; i++) { + sb->listener_connections[i] = 0; + sb->listeners[i][0] = 0; + } +} + + +/*********************************************************************** + * Listeners + */ + +/* + * Initialize/reset the given listener. + */ +HIDDEN void +stm_listener_init(stm_listener *listener) +{ + STM_TRACE(("%d: stm_listener_init(listener=%p,#%d)\n", getpid(), + listener, (int) (listener - stm.listeners))); + + listener->id = (int) (listener - stm.listeners); + STM_ASSERT(listener->id >= 0 && listener->id < STM_LISTENER_LIMIT); + listener->addr_string = NULL; + memset(&listener->addr, 0, sizeof listener->addr); + listener->unique = 0; + listener->share_count = 0; + listener->alr = NULL; + STM_ASSERT(listener->sd == NULL); /* init or stm_listener_revert() */ + listener->sd = NULL; + listener->spare_threads = 0; +} + +/* + * Add the given IP address and port number (both in network byte order) + * to the list of listeners if not already there. Sets *new to 0 if + * already there or to 1 if not. Returns the new listener, or NULL if + * the listener table already contains the maximum number + * (STM_LISTENER_LIMIT) of listeners. + */ +HIDDEN stm_listener * +stm_listener_add(const char *addr_string, stm_addr addr, stm_port port, + int *new) +{ + stm_listener *lp, *ep; + + STM_TRACE(("%d: stm_listener_add(addr_string=\"%s\", addr=%#x, port=%u)\n", + getpid(), addr_string, ntohl(addr), ntohs(port))); + + *new = 0; + for (lp = stm.listeners, ep = lp + stm.num_listeners; lp < ep; lp++) + if (lp->addr.sin_addr.s_addr == addr && lp->addr.sin_port == port) + return lp; + if (stm.num_listeners < STM_LISTENER_LIMIT) { + lp->addr_string = apr_pstrdup(stm.pool, addr_string); + lp->addr.sin_family = AF_INET; + lp->addr.sin_port = port; + lp->addr.sin_addr.s_addr = addr; + lp->unique = addr != htonl(INADDR_ANY); + stm.num_listeners++; + *new = 1; + return lp; + } + + return NULL; +} + +/* + * Adapt the given ap-listener so that the given stm-listener can be + * used instead. Returns nonzero on success, zero on failure. + */ +HIDDEN int +stm_listener_usurp(stm_listener *listener, ap_listen_rec *alr) +{ + int sd; + + STM_TRACE(("%d: stm_listener_usurp(listener=%p,#%d, alr=%p) share_count=%d\n", + getpid(), listener, listener->id, alr, listener->share_count)); + + /* the listener must have a valid address at this point */ + STM_ASSERT(listener->addr_string); + STM_ASSERT(listener->share_count > 0); + + listener->alr = alr; + if (apr_get_os_sock(&sd, alr->sd) == APR_SUCCESS) { + listener->sd = st_netfd_open_socket(sd); + if (listener->sd != NULL) { + /* serialize accepts only on shared listeners */ + if (listener->share_count == 1 || + st_netfd_serialize_accept(listener->sd) == 0) + return 1; + else + ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, + "stm_listener_usurp: st_netfd_serialize_accept"); + } else + ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, + "stm_listener_usurp: st_netfd_open_socket"); + } else + ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, + "stm_listener_usurp: apr_get_os_sock"); + + return 0; +} + +/* + * Revert the given listener from STM control. + */ +HIDDEN void +stm_listener_revert(stm_listener *listener) +{ + STM_TRACE(("%d: stm_listener_revert(listener=%p,#%d)\n", getpid(), + listener, listener->id)); + + st_netfd_free(listener->sd); + listener->sd = NULL; +} + + +/*********************************************************************** + * Threads + */ + +/* + * Initialize/reset the given thread which is bound to the given VP. + */ +HIDDEN void +stm_thread_init(stm_thread *thread, stm_vp *vp) +{ + STM_TRACE(("%d: stm_thread_init(thread=%p,#%d, vp=%p,#%d)\n", getpid(), + thread, (int) (thread - vp->threads), vp, vp->id)); + + thread->id = (int) (thread - vp->threads); + STM_ASSERT(thread->id >= 0 && thread->id < STM_ST_LIMIT); + thread->health = STM_HEALTH_DEAD; + thread->st = 0; + thread->vp = vp; + thread->listener = NULL; + thread->connected = 0; +} + +/* + * Attach the given listener to the given thread and start the given + * thread running. Returns nonzero on success, zero on failure. + */ +HIDDEN int +stm_thread_start(stm_thread *thread, stm_listener *listener) +{ + STM_TRACE(("%d: stm_thread_start(thread=%p,#%d, listener=%p,#%d)\n", + getpid(), thread, thread->id, listener, listener->id)); + + /* it would be bad to clobber a running thread */ + STM_ASSERT(thread->health == STM_HEALTH_DEAD); + + thread->listener = listener; + thread->st = st_thread_create(stm_thread_main, thread, 0, + stm.config.stack_size); + if (thread->st) { + /* + * Note it is impossible for the new thread to run until this + * thread blocks. No race conditions on startup. + */ + thread->health = STM_HEALTH_ALIVE; + return 1; + } + + ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, + "stm_thread_start: st_thread_create"); + return 0; +} + +/* + * Return the thread currently running, or NULL if called by a + * non-thread entity (e.g., the master process or the primordial VP + * thread). + */ +HIDDEN stm_thread * +stm_thread_self(void) +{ + STM_TRACE(("%d: stm_thread_self()\n", getpid())); + + /* + * We could walk through all of STs on all of the VPs in stm looking + * for a match for st_thread_self() but a faster way is to use + * thread-specific data. The catch with TSD is that the key must be + * the same on all VPs, since we don't have a VP reference either. + * Therefore the key must be allocated before any VP. + */ + return (stm_thread *) st_thread_getspecific(stm.self_key); +} + +/* + * Thread main function. Always returns NULL. arg is a pointer to the + * thread. + */ +HIDDEN void * +stm_thread_main(void *arg) +{ + stm_thread *thread; + stm_vp *vp; + stm_listener *lp; + long id; + stm_scoreboard *sbp; + apr_int32_t *sbtcp, *sblcp; + apr_pool_t *conn_pool; + + STM_TRACE(("%d: => stm_thread_main(arg=%p)\n", getpid(), arg)); + + thread = (stm_thread *) arg; + st_thread_setspecific(stm.self_key, thread); + vp = thread->vp; + lp = thread->listener; + id = stm_id_make(vp->id, thread->id); + sbp = vp->scoreboard; + sbtcp = &sbp->thread_connections[thread->id]; + sblcp = &sbp->listener_connections[lp->id]; + sbp->thread_starts++; + + /* + * Check health once before entering st_accept() to catch early stop + * requests (see stm_thread_stop()). Even if this ST has been + * st_interrupt()ed st_accept() will return EINTR only if there is + * no connection pending. (ST I/O functions check for interrupts + * only when about to block the thread.) + */ + if (thread->health == STM_HEALTH_ALIVE && + apr_create_pool(&conn_pool, stm.pool) == APR_SUCCESS) { + do { + struct sockaddr_in addr; + int addrlen; + st_netfd_t nsd; + + /* get a connection */ + addrlen = sizeof addr; + nsd = st_accept(lp->sd, (struct sockaddr *) &addr, &addrlen, -1); + if (nsd == NULL) { + if (errno == EINTR) + break; /* stm_thread_stop() called; terminate now */ + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server, + "stm_thread_main: st_accept"); + if (st_sleep(1) == -1 && errno == EINTR) + break; + continue; + } + + /* + * I have a connection, so keep stm_thread_stop() from + * st_thread_interrupt()ing me now. + */ + thread->connected = 1; + + /* + * This thread is no longer "spare." Start a replacement + * spare thread if necessary and allowed. + */ + if (--lp->spare_threads < stm.config.min_spare_threads) { + if (vp->num_threads < stm.config.max_threads) { + if (stm_thread_start(vp->dead_threads[vp->num_threads], + lp)) { + vp->num_threads++; + if (sbp->most_threads < vp->num_threads) + sbp->most_threads = vp->num_threads; + lp->spare_threads++; + } + } else if (!vp->max_threads_hit) { + vp->max_threads_hit = 1; /* report once per VP */ + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, + 0, stm.server, "ran out of threads on VP %d; " + "increase " STM_CMD_MAX_THREADS " and/or " STM_CMD_NUM_VPS, + vp->pid); + } + } + + /* process the connection */ + stm_thread_process_connection(lp, conn_pool, nsd, &addr, id); + + /* + * Connection has been closed. Clean up and return thread + * to spare state. + */ + apr_clear_pool(conn_pool); + thread->connected = 0; + lp->spare_threads++; + + /* update counters */ + sbp->vp_connections++; /* per VP */ + (*sbtcp)++; /* per thread */ + (*sblcp)++; /* per listener */ + + if (vp->connections > 0 && --vp->connections == 0) { + /* + * This VP has processed as many connections as it's + * allowed. Terminate the whole VP, including this + * thread, immediately and gently. + */ + stm_vp_stop_self(vp); + break; + } + + /* check for stop requests, and for too many spare threads */ + } while (thread->health == STM_HEALTH_ALIVE && + lp->spare_threads <= stm.config.max_spare_threads); + + apr_destroy_pool(conn_pool); + } + + /* this thread is dying */ + lp->spare_threads--; + vp->num_threads--; + vp->dead_threads[vp->num_threads] = thread; + sbp->thread_exits++; + + /* this thread is dead */ + stm_thread_init(thread, vp); + + STM_TRACE(("%d: <= stm_thread_main(arg=%p)\n", getpid(), arg)); + + return NULL; +} + +/* + * Process a connection with a client. Calls back into the main server + * to process requests, etc. On return, sd has been closed. + */ +HIDDEN void +stm_thread_process_connection(const stm_listener *listener, + apr_pool_t *conn_pool, st_netfd_t sd, const struct sockaddr_in *raddr, + long id) +{ + struct sockaddr_in laddr; + const struct sockaddr_in *laddrp; + + STM_TRACE(("%d: stm_thread_process_connection(listener=%p,#%d, conn_pool=%p, sd=%p(%d), raddr=%s:%hu, id=%ld)\n", + getpid(), listener, listener->id, conn_pool, sd, st_netfd_fileno(sd), + inet_ntoa(raddr->sin_addr), ntohs(raddr->sin_port), id)); + + stm_disable_nagle(sd); + + if (listener->unique) + laddrp = &listener->addr; + else { + int addrlen; + + addrlen = sizeof laddr; + if (getsockname(st_netfd_fileno(sd), &laddr, &addrlen) == 0) + laddrp = &laddr; + else { + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server, + "stm_thread_process_connection: getsockname"); + laddrp = NULL; + } + } + + if (laddrp) { + ap_iol *iolp; + + /* make all I/O on this socket use ST's I/O functions */ + iolp = stiol_attach(sd); + if (iolp) { + BUFF *bp; + conn_rec *conn; + + bp = ap_bcreate(conn_pool, B_RDWR); + ap_bpush_iol(bp, iolp); + conn = ap_new_connection(conn_pool, stm.server, bp, raddr, + laddrp, id); + ap_process_connection(conn); + ap_lingering_close(conn); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server, + "stm_thread_process_connection: stiol_attach"); + st_netfd_close(sd); + } + } else + st_netfd_close(sd); +} + +/* + * Ask the given thread to terminate. Must be called only from within + * the VP of the given thread. + */ +HIDDEN void +stm_thread_stop(stm_thread *thread) +{ + STM_TRACE(("%d: stm_thread_stop(thread=%p,#%d)\n", getpid(), thread, + thread->id)); + STM_ASSERT(getpid() == thread->vp->pid); + + if (thread->health == STM_HEALTH_ALIVE) { + thread->health = STM_HEALTH_DYING; + + /* + * break the thread out of st_accept() if necessary, and no + * other I/O function + */ + if (!thread->connected) + st_thread_interrupt(thread->st); + } +} + +/* + * Return nonzero if the given thread has been terminated or asked to + * terminate. + */ +HIDDEN int +stm_thread_stopped(stm_thread *thread) +{ + STM_TRACE(("%d: stm_thread_stopped(thread=%p,#%d)\n", getpid(), thread, + thread->id)); + + return thread->health != STM_HEALTH_ALIVE; +} + + +/*********************************************************************** + * VPs + */ + +/* + * Initialize/reset the given VP. Returns nonzero on success, zero on + * failure. + */ +HIDDEN int +stm_vp_init(stm_vp *vp) +{ + int i; + + STM_TRACE(("%d: stm_vp_init(vp=%p,#%d)\n", getpid(), vp, + (int) (vp - stm.vps))); + + vp->id = (int) (vp - stm.vps); + STM_ASSERT(vp->id >= 0 && vp->id < STM_VP_LIMIT); + vp->pid = -1; + vp->health = STM_HEALTH_DEAD; + vp->pool = NULL; + vp->stop_pipe[0] = -1; + vp->stop_pipe[1] = -1; + /* leave vp->num_listeners and vp->listeners alone */ + vp->connections = stm.config.vp_connections; + vp->max_threads_hit = 0; + vp->num_threads = 0; + for (i = 0; i < STM_ST_LIMIT; i++) { + stm_thread_init(&vp->threads[i], vp); + vp->dead_threads[i] = &vp->threads[i]; + } + + if (vp->scoreboard == NULL) { + /* + * create scoreboard even if !stm.config.connection_status + * because we may want them after a restart and we keep other + * stats in there + */ + vp->scoreboard = stm_scoreboard_new(vp->id); + if (vp->scoreboard == NULL) + return 0; + } + stm_scoreboard_init(vp->scoreboard); + + return 1; +} + +/* + * Initialize the listeners for the given VP from the stm.listeners and + * stm.config.vp_listen tables. Returns nonzero on success, zero on + * failure. + */ +HIDDEN int +stm_vp_listen(stm_vp *vp) +{ + int i; + + STM_TRACE(("%d: stm_vp_listen(vp=%p,#%d)\n", getpid(), vp, vp->id)); + + if (stm.config.vp_listen +#ifdef STM_DEBUG + && !stm.debug.one_process +#endif + ) { + const stm_config_vp_listen *vlp; + + /* + * VPListen directives were used. Each VP listens only to its + * designated listeners. + */ + + vlp = &stm.config.vp_listen[vp->id]; + for (i = 0; i < vlp->num_listeners; i++) { + vp->listeners[i] = vlp->listeners[i]; + vp->listeners[i]->share_count++; + } + vp->num_listeners = i; + while (i < STM_LISTENER_LIMIT) + vp->listeners[i++] = NULL; + + if (vp->num_listeners < 1) { + ap_log_error(APLOG_MARK, + APLOG_CRIT | APLOG_STARTUP | APLOG_NOERRNO, 0, stm.server, + "no listeners for virtual processor %d", vp->id); + return 0; + } + } else { + /* + * Listen directives were used, or no Listen or VPListen + * directives were used at all. Each VP listens to all + * listeners. + */ + + vp->num_listeners = stm.num_listeners; + for (i = 0; i < vp->num_listeners; i++) { + vp->listeners[i] = &stm.listeners[i]; + vp->listeners[i]->share_count++; + } + while (i < STM_LISTENER_LIMIT) + vp->listeners[i++] = NULL; + } + + /* this VP must have at least one listener at this point */ + STM_ASSERT(vp->num_listeners > 0); + + return 1; +} + +/* + * Start the given VP running. Returns nonzero on success, zero on + * failure. + */ +HIDDEN int +stm_vp_start(stm_vp *vp) +{ + pid_t pid; + + STM_TRACE(("%d: stm_vp_start(vp=%p,#%d)\n", getpid(), vp, vp->id)); + + /* it would be bad to clobber a running VP */ + STM_ASSERT(vp->health == STM_HEALTH_DEAD); + +#ifdef STM_DEBUG + if (stm.debug.one_process) { + vp->pid = getpid(); + stm_vp_bind(vp); + stm_vp_main(vp); + /*NOTREACHED*/ + return 0; + } +#endif + + pid = fork(); + switch (pid) { + case -1: + ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, + "stm_vp_start: fork"); + sleep(10); /* avoid thrashing the system */ + return 0; + case 0: + vp->pid = getpid(); + stm_vp_bind(vp); /* in case child runs first */ + stm_vp_main(vp); + /*NOTREACHED*/ + exit(APEXIT_CHILDFATAL); + break; + default: + vp->pid = pid; + stm_vp_bind(vp); /* in case parent runs first */ + vp->health = STM_HEALTH_ALIVE; + break; + } + + return 1; +} + +/* + * Bind the given VP to the CPU to which it should be bound, if any. + */ +#if defined(IRIX) && IRIX >= 50 +# include +#endif +HIDDEN void +stm_vp_bind(stm_vp *vp) +{ + STM_TRACE(("%d: stm_vp_bind(vp=%p,#%d)\n", getpid(), vp, vp->id)); + + /* we need the VP's pid to bind it */ + STM_ASSERT(vp->pid > 0); + + if (stm.config.vp_bind) { + int cpu; + + cpu = stm.config.vp_bind[vp->id]; + if (cpu >= 0) { +#if defined(IRIX) && IRIX >= 50 + /* only Irix 5.0 and beyond have sysmp(MP_MUSTRUN_PID) */ + if (sysmp(MP_MUSTRUN_PID, cpu, vp->pid) == -1) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server, + "cannot bind process %d to cpu %d: sysmp(MP_MUSTRUN_PID)", + vp->pid, cpu); +#else + ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, + stm.server, "don't know how to bind process %d to cpu %d", + vp->pid, cpu); +#endif + } + } +} + +/* + * Establish signal dispositions for VP processes. + */ +HIDDEN void +stm_vp_signals(void) +{ + STM_TRACE(("%d: stm_vp_signals()\n", getpid())); + + /* adjust signal dispositions this VP inherited from the master process */ + + stm_signal_default(SIGTERM); +#ifdef SIGINT + stm_signal_default(SIGINT); +#endif + stm_signal_catch(SIGHUP, stm_vp_onsig_stop); + stm_signal_ignore(SIGWINCH); +} + +/* + * VP main function. Does not return. + */ +HIDDEN void +stm_vp_main(stm_vp *vp) +{ + st_netfd_t stop_fd; + int i; + + STM_TRACE(("%d: => stm_vp_main(vp=%p,#%d)\n", getpid(), vp, vp->id)); + + /* + * Set stm.num_vps to our VP index so asynchronous global functions + * like stm_vp_onsig_stop() and stm_vp_onexit() have a handle on the + * current VP. + */ + stm.num_vps = vp->id; + + vp->health = STM_HEALTH_ALIVE; + vp->scoreboard->pid = vp->pid; + vp->scoreboard->vp_start_time = apr_now(); + vp->scoreboard->incarnations++; + + if (unixd_setup_child() != 0) + exit(APEXIT_CHILDINIT); + + /* + * Create the stop pipe, a safe means for this VP to signal + * asynchronous events. + */ + if (pipe(vp->stop_pipe) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, + "stm_vp_main: pipe"); + exit(APEXIT_CHILDINIT); + } + stop_fd = st_netfd_open(vp->stop_pipe[0]); + if (stop_fd == NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, + "stm_vp_main: st_netfd_open"); + exit(APEXIT_CHILDINIT); + } + + /* + * Create a pool that endures for the life of this VP, run child + * init hooks on it, and use it to close both ends of the stop_pipe + * when execing another program. + */ + apr_create_pool(&vp->pool, stm.pool); + ap_child_init_hook(vp->pool, stm.server); + apr_register_cleanup(vp->pool, vp, apr_null_cleanup, stm_vp_cleanup); + atexit(stm_vp_onexit); + + /* Start the requested number of spare threads per listener. */ + for (i = 0; i < vp->num_listeners && stm.state == STM_STATE_RUN; i++) { + stm_listener *lp; + int start_error; + + lp = vp->listeners[i]; + start_error = 0; + + apr_snprintf(vp->scoreboard->listeners[lp->id], + sizeof vp->scoreboard->listeners[lp->id], "%s:%u", + lp->addr_string, ntohs(lp->addr.sin_port)); + + while (lp->spare_threads < stm.config.start_threads && + vp->num_threads < stm.config.max_threads) { + if (stm_thread_start(&vp->threads[vp->num_threads], lp)) { + vp->num_threads++; + lp->spare_threads++; + } else { + start_error = 1; + break; + } + } + if (vp->scoreboard->most_threads < vp->num_threads) + vp->scoreboard->most_threads = vp->num_threads; + if (lp->spare_threads < stm.config.start_threads) { + /* + * We didn't make all the threads we wanted to. Either we + * exceeded the total number of threads per VP or a thread + * failed to start. Warn, once, about the former condition. + */ + if (!start_error && !vp->max_threads_hit) { + STM_ASSERT(vp->num_threads >= stm.config.max_threads); + vp->max_threads_hit = 1; /* report once per VP */ + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, + stm.server, "ran out of threads on VP %d; " + "increase " STM_CMD_MAX_THREADS, + vp->pid); + } + + /* Also warn about too-few threads per listener. */ + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, + stm.server, + "started only %d out of %d thread(s) listening to %s on virtual processor %d", + lp->spare_threads, stm.config.start_threads, + vp->scoreboard->listeners[lp->id], vp->pid); + } + } + + stm_vp_signals(); + if (stm.state != STM_STATE_RUN) { + /* caught signal on parent's handler: do the right thing */ + exit(APEXIT_CHILDINIT); + } + + if (vp->num_threads > 0) { + char c; + + /* + * Block here for data on the pipe. This st_read() returns only + * when the pipe has data on it (see stm_vp_stop_self()) and + * that means it's time to shut down all the threads. + */ + st_read(stop_fd, &c, sizeof c, -1); + for (i = 0; i < STM_ST_LIMIT; i++) + stm_thread_stop(&vp->threads[i]); + } + + /* this VP is dying */ + st_netfd_close(stop_fd); + close(vp->stop_pipe[1]); + STM_TRACE(("%d: <= stm_vp_main(vp=%p,#%d)\n", getpid(), vp, vp->id)); + st_thread_exit(NULL); + /*NOTREACHED*/ +} + +/* + * Ask the given VP to terminate. Must be called only by the master + * process. + */ +HIDDEN void +stm_vp_stop(stm_vp *vp) +{ + STM_TRACE(("%d: stm_vp_stop(vp=%p,#%d)\n", getpid(), vp, vp->id)); + STM_ASSERT(stm.pid == getpid()); + + if (vp->health == STM_HEALTH_ALIVE) { + vp->health = STM_HEALTH_DYING; + STM_ASSERT(vp->pid > 0); + kill(vp->pid, SIGHUP); + } +} + +/* + * Ask the given VP to terminate. Must be called only from within the + * given VP. + */ +HIDDEN void +stm_vp_stop_self(stm_vp *vp) +{ + char c; + + STM_TRACE(("%d: stm_vp_stop_self(vp=%p,#%d)\n", getpid(), vp, vp->id)); + STM_ASSERT(vp->pid == getpid()); + + if (vp->health == STM_HEALTH_ALIVE) { + vp->health = STM_HEALTH_DYING; + write(vp->stop_pipe[1], &c, sizeof c); + } +} + +/* + * Signal handler for a VP process: Terminate this VP. + */ +HIDDEN void +stm_vp_onsig_stop(int sig) +{ + STM_TRACE(("%d: stm_vp_onsig_stop(sig=%d)\n", getpid(), sig)); + + stm_vp_stop_self(&stm.vps[stm.num_vps]); +} + +/* + * Atexit handler for a VP process: Destroy the VP's pool. + */ +HIDDEN void +stm_vp_onexit(void) +{ + STM_TRACE(("%d: stm_vp_onexit()\n", getpid())); + + apr_destroy_pool(stm.vps[stm.num_vps].pool); +} + +/* + * Clean up the VP to prepare to exec another program. + */ +HIDDEN apr_status_t +stm_vp_cleanup(void *arg) +{ + stm_vp *vp; + + vp = (stm_vp *) arg; + STM_TRACE(("%d: stm_vp_cleanup(vp=%p,#%d)\n", getpid(), vp, vp->id)); + + close(vp->stop_pipe[0]); + close(vp->stop_pipe[1]); + + return APR_SUCCESS; +} + + +/*********************************************************************** + * Main STM + */ + +/* + * Initialize/reset the STM MPM. Returns nonzero on success, zero on + * failure. + */ +HIDDEN int +stm_init(apr_pool_t *pool, server_rec *server) +{ + int first; + int i; + ap_listen_rec *alp; + + STM_TRACE(("%d: stm_init(pool=%p, server=%p) #%d\n", getpid(), pool, + server, stm.initialized)); + + /* verify all configuration parameters' bounds checks */ + STM_ASSERT(stm.config.num_vps > 0 && stm.config.num_vps <= STM_VP_LIMIT); + STM_ASSERT(stm.config.start_threads > 0 && + stm.config.start_threads <= STM_ST_LIMIT); + STM_ASSERT(stm.config.min_spare_threads > 0 && + stm.config.min_spare_threads <= STM_ST_LIMIT); + STM_ASSERT(stm.config.max_spare_threads > 0 && + stm.config.max_spare_threads <= STM_ST_LIMIT); + STM_ASSERT(stm.config.max_threads > 0 && + stm.config.max_threads <= STM_ST_LIMIT); + + /* first is nonzero to initialize, zero to reset */ + first = stm.initialized++ == 0; + + if (first) { + int maxfds; + + stm.start_time = apr_now(); + + /* initialize the state threads library */ + if (st_init() != 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, errno, server, + "cannot initialize state threads library"); + return 0; + } + + /* + * Are there enough file descriptors for max_threads connections + * per VP? + */ + maxfds = st_getfdlimit() - 10; /* or so */ + if (stm.config.max_threads > maxfds) { + ap_log_error(APLOG_MARK, APLOG_CRIT | APLOG_NOERRNO, 0, server, + STM_CMD_MAX_THREADS " %d exceeds " + "number of file descriptors per process %d; " + "decrease " STM_CMD_MAX_THREADS " and compensate " + "by increasing " STM_CMD_NUM_VPS, + stm.config.max_threads, maxfds); + return 0; + } + + st_timecache_set(1); + } + + /* + * Initialize/reset each member of stm not already initialized/reset + * by stm_pre_config() and the config routines. + */ + stm.pool = pool; + stm.server = server; + stm.state = STM_STATE_RUN; + /* + * leave alone: + * stm.initialized (incremented above) + * stm.num_restarts (stm_pre_config()) + * stm.pid (stm_pre_config()) + * stm.config (stm_pre_config()) + * stm.num_listeners (stm_pre_config()) + * stm.listeners (stm_pre_config()) + */ + stm.num_vps = 0; + for (i = 0; i < STM_VP_LIMIT; i++) + if (!stm_vp_init(&stm.vps[i])) + return 0; + if (first && st_key_create(&stm.self_key, NULL) == -1) { + ap_log_error(APLOG_MARK, APLOG_CRIT, errno, server, + "stm_init: st_key_create"); + return 0; + } + /* leave stm.debug alone (stm_pre_config()) */ + + /* establish AP listeners and convert them to STM listeners */ + if (ap_listeners == NULL) { + int new; + + /* + * There were no Listen or VPListen directives so + * ap_setup_listeners() will add a default AP listener (yecch). + * Add a matching STM listener. + */ + STM_ASSERT(stm.num_listeners == 0); + stm_listener_add(APR_ANYADDR, htonl(INADDR_ANY), htons(server->port), + &new); + STM_ASSERT(new); + } + STM_ASSERT(stm.num_listeners > 0); + for (i = 0; i < stm.config.num_vps; i++) + if (!stm_vp_listen(&stm.vps[i])) + return 0; + if (ap_setup_listeners(server) > 0) { + for (alp = ap_listeners; alp; alp = alp->next) { + char *addr_string; + struct in_addr addr; + stm_port port; + stm_listener *lp, *ep; + + /* find the STM listener for the AP listener */ + + if (apr_get_local_ipaddr(&addr_string, alp->sd) != APR_SUCCESS || + !inet_aton(addr_string, &addr)) + addr.s_addr = htonl(INADDR_ANY); + if (apr_get_local_port(&port, alp->sd) != APR_SUCCESS) + port = server->port; + port = htons(port); + + for (lp = stm.listeners, ep = lp + stm.num_listeners; lp < ep; + lp++) { + if (lp->addr.sin_addr.s_addr == addr.s_addr && + lp->addr.sin_port == port) { + if (stm_listener_usurp(lp, alp)) + break; + else { + ap_log_error(APLOG_MARK, + APLOG_CRIT | APLOG_STARTUP | APLOG_NOERRNO, + 0, server, + "cannot initialize listen socket for %s:%u", + addr_string, ntohs(port)); + return 0; + } + } + } + + /* + * Every AP listener must have an STM listener (and vice + * versa, which we check below). + */ + STM_ASSERT(lp < ep); + } + } else { + ap_log_error(APLOG_MARK, APLOG_CRIT | APLOG_STARTUP | APLOG_NOERRNO, + 0, server, "no listening sockets available"); + return 0; + } + +#ifdef STM_DEBUG + for (i = 0; i < stm.num_listeners; i++) + STM_ASSERT(stm.listeners[i].share_count > 0 && + stm.listeners[i].alr && stm.listeners[i].sd); +#endif + + if (stm.config.min_spare_threads > stm.config.max_spare_threads) + stm.config.min_spare_threads = stm.config.max_spare_threads; + + /* + * Warn if the number of threads per VP needed to satisfy all the + * listeners exceeds the maximum. (Other MPM's measure + * start/min-spare/max-spare threads per child process but STM + * measures them per listener so these warnings may catch + * configuration mistakes caused by the different semantics.) + */ + for (i = 0; i < stm.config.num_vps; i++) { + int nl = stm.vps[i].num_listeners; + + if (nl * stm.config.start_threads > stm.config.max_threads) + ap_log_error(APLOG_MARK, + APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, server, + "too few threads: %d " STM_CMD_START_THREADS + " for each of %d listener(s) on virtual processor %d exceeds limit of %d " + STM_CMD_MAX_THREADS "; decrease " STM_CMD_START_THREADS + " or increase " STM_CMD_MAX_THREADS, + stm.config.start_threads, nl, i, stm.config.max_threads); + if (nl * stm.config.min_spare_threads > stm.config.max_threads) + ap_log_error(APLOG_MARK, + APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, server, + "too few threads: %d " STM_CMD_MIN_SPARE_THREADS + " for each of %d listener(s) on virtual processor %d exceeds limit of %d " + STM_CMD_MAX_THREADS "; decrease " STM_CMD_MIN_SPARE_THREADS + " or increase " STM_CMD_MAX_THREADS, + stm.config.min_spare_threads, nl, i, stm.config.max_threads); + if (nl * stm.config.max_spare_threads > stm.config.max_threads) + ap_log_error(APLOG_MARK, + APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, server, + "too few threads: %d " STM_CMD_MAX_SPARE_THREADS + " for each of %d listener(s) on virtual processor %d exceeds limit of %d " + STM_CMD_MAX_THREADS "; decrease " STM_CMD_MAX_SPARE_THREADS + " or increase " STM_CMD_MAX_THREADS, + stm.config.max_spare_threads, nl, i, stm.config.max_threads); + } + + /* Warn about CPU- or listen-binding for unused VPs */ + for (i = stm.config.num_vps; i < STM_VP_LIMIT; i++) { + if (stm.config.vp_bind && stm.config.vp_bind[i] != -1) + ap_log_error(APLOG_MARK, + APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, server, + STM_CMD_VP_BIND " directive(s) for unused virtual processor %d ignored: " + STM_CMD_NUM_VPS " is %d", + i, stm.config.num_vps); + if (stm.config.vp_listen && stm.config.vp_listen[i].num_listeners > 0) + ap_log_error(APLOG_MARK, + APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, server, + STM_CMD_VP_LISTEN " directive(s) for unused virtual processor %d ignored: " + STM_CMD_NUM_VPS " is %d", + i, stm.config.num_vps); + } + + return 1; +} + +/* + * Finalize the STM in preparation for server restart or shutdown. + */ +HIDDEN void +stm_fini(void) +{ + int i; + + STM_TRACE(("%d: stm_fini()\n", getpid())); + + for (i = 0; i < stm.num_listeners; i++) + stm_listener_revert(&stm.listeners[i]); +} + +/* + * Establish signal dispositions for the master process (and embryonic + * VP processes). + */ +HIDDEN void +stm_signals(void) +{ + STM_TRACE(("%d: stm_signals()\n", getpid())); + +#ifdef SIGSEGV + stm_signal_core(SIGSEGV); +#endif +#ifdef SIGBUS + stm_signal_core(SIGBUS); +#endif +#ifdef SIGABORT + stm_signal_core(SIGABORT); +#endif +#ifdef SIGABRT + stm_signal_core(SIGABRT); +#endif +#ifdef SIGILL + stm_signal_core(SIGILL); +#endif + + stm_signal_catch(SIGTERM, stm_onsig_shutdown); +#ifdef SIGINT + stm_signal_catch(SIGINT, stm_onsig_shutdown); +#endif +#ifdef SIGXCPU + stm_signal_default(SIGXCPU); +#endif +#ifdef SIGXFSZ + stm_signal_default(SIGXFSZ); +#endif +#ifdef SIGPIPE + stm_signal_ignore(SIGPIPE); +#endif + + stm_signal_catch(SIGHUP, stm_onsig_restart); + stm_signal_catch(SIGWINCH, stm_onsig_restart); +} + +/* + * Master process main function. Returns when server restart or + * shutdown has been requested. + */ +HIDDEN void +stm_main(void) +{ + STM_TRACE(("%d: => stm_main()\n", getpid())); + + stm_signals(); + + /* start VPs */ + while (stm.state == STM_STATE_RUN && + stm.num_vps < stm.config.num_vps && + stm_vp_start(&stm.vps[stm.num_vps])) + stm.num_vps++; + if (stm.state == STM_STATE_RUN && stm.num_vps < stm.config.num_vps) + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, + 0, stm.server, "started only %d out of %d virtual processor(s)", + stm.num_vps, stm.config.num_vps); + + /* wait for shutdown or restart, restarting VPs as needed */ + if (stm.num_vps > 0) { + while (stm.state == STM_STATE_RUN) { + pid_t pid; + int status; + + pid = wait(&status); + if (pid > 0) + stm_reap(pid, status); + } + } else + stm.state = STM_STATE_SHUTDOWN; + + STM_TRACE(("%d: <= stm_main(), state %d\n", getpid(), stm.state)); +} + +/* + * Reap a child process. If the process was a VP, start a replacement VP. + */ +HIDDEN void +stm_reap(pid_t pid, int status) +{ + stm_grief grief; + + STM_TRACE(("%d: stm_reap(pid=%d, status=%#x)\n", getpid(), pid, status)); + + grief = stm_mourn(pid, status); + if (grief != STM_GRIEF_FATAL) { + int i; + + for (i = 0; i < STM_VP_LIMIT; i++) + if (stm.vps[i].pid == pid) + break; + if (i < STM_VP_LIMIT) { + /* this was a VP; finish killing it */ + stm.num_vps--; + stm_vp_init(&stm.vps[i]); + + /* replace it */ + if (stm.num_vps < stm.config.num_vps && + stm_vp_start(&stm.vps[i])) { + stm.num_vps++; + if (grief != STM_GRIEF_NORMAL) + sleep(5); /* avoid thrashing */ + } + } + } else + stm.state = STM_STATE_SHUTDOWN; +} + +/* + * Evaluate the seriousness of a child process's death and log a warning if + * appropriate. Returns: + * STM_GRIEF_NORMAL if the death was expected (e.g., voluntary VP death + * after processing its allowed number of connections) or irrelevent + * (e.g., not a VP) + * STM_GRIEF_FATAL if the death was catastrophic (e.g., VP botched an + * assertion) and the server should terminate too + * STM_GRIEF_SURPRISE if the death was neither expected nor fatal (e.g., + * VP failed to initialize) + */ +HIDDEN stm_grief +stm_mourn(pid_t pid, int status) +{ + stm_grief grief; + + STM_TRACE(("%d: stm_mourn(pid=%d, status=%#x)\n", getpid(), pid, status)); + + grief = STM_GRIEF_NORMAL; + if (WIFEXITED(status)) { + int ev = WEXITSTATUS(status); + + switch (ev) { + case 0: + break; + case APEXIT_CHILDFATAL: + ap_log_error(APLOG_MARK, APLOG_CRIT | APLOG_NOERRNO, 0, stm.server, + "child %d returned a fatal error; shutting down", pid); + grief = STM_GRIEF_FATAL; + break; + case APEXIT_CHILDINIT: + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, stm.server, + "child %d failed to initialize", pid); + grief = STM_GRIEF_SURPRISE; + break; + default: + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, stm.server, + "child %d exited with unexpected status %d", pid, ev); + grief = STM_GRIEF_SURPRISE; + break; + } + } else if (WIFSIGNALED(status)) { + int sig = WTERMSIG(status); + + switch (sig) { +#ifdef STM_DEBUG + case SIGABRT: + ap_log_error(APLOG_MARK, APLOG_CRIT | APLOG_NOERRNO, 0, stm.server, + "child %d aborted, possible assertion botch; shutting down", + pid); + grief = STM_GRIEF_FATAL; + break; +#endif + case SIGTERM: + case SIGHUP: + case SIGUSR1: + case SIGKILL: + break; + default: + ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, + stm.server, + "child pid %d exit signal %d" +#ifdef SYS_SIGLIST + " (%s)" +#endif + , pid, sig +#ifdef SYS_SIGLIST + , (sig < NumSIG) ? SYS_SIGLIST[sig] : "?" +#endif + ); + if (WCOREDUMP(status)) + ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, + stm.server, "-- possible core dump in %s", + stm.config.core_dir); +#ifdef STM_DEBUG + grief = STM_GRIEF_FATAL; +#else + grief = STM_GRIEF_SURPRISE; +#endif + break; + } + } + + return grief; +} + +/* + * Wait a limited amount of time for all VPs to terminate. Kill them if + * they take too long to terminate themselves. The caller must have + * already commenced their termination. + */ +HIDDEN void +stm_doomsday(void) +{ + int tries; + st_utime_t waittime; + + STM_TRACE(("%d: stm_doomsday()\n", getpid())); + + /* wait increasing intervals but not forever */ + for (tries = 0, waittime = 10000; + stm.num_vps > 0 && tries <= 5; + tries++, waittime *= 4) { + stm_vp *vp; + + st_usleep(waittime); + + /* see who's dead */ + for (vp = stm.vps; vp < &stm.vps[STM_VP_LIMIT]; vp++) { + if (vp->health != STM_HEALTH_DEAD) { + pid_t pid; + int status; + + STM_ASSERT(vp->pid > 0); + pid = waitpid(vp->pid, &status, WNOHANG); + if (pid == vp->pid || pid == -1) { + if (pid > 0) + stm_mourn(pid, status); + vp->health = STM_HEALTH_DEAD; + stm.num_vps--; + /* + * don't need to stm_vp_init(vp) because mpm is + * exiting and will be reinitialized later if + * necessary + */ + } else { + switch (tries) { + case 0: /* 10 ms since doomsday */ + case 1: /* 50 ms since doomsday */ + break; + case 2: /* 210 ms since doomsday */ + case 3: /* 850 ms since doomsday */ + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, + 0, stm.server, + "waiting for process %d to die, trying SIGTERM", + vp->pid); + kill(vp->pid, SIGTERM); + break; + case 4: /* 3.4 sec since doomsday */ + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, + 0, stm.server, + "waiting for process %d to die, trying SIGKILL", + vp->pid); + kill(vp->pid, SIGKILL); + break; + default: /* >= 14 sec since doomsday */ + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, + 0, stm.server, + "could not make process %d die, giving up", + vp->pid); + break; + } + } + } + } + } +} + + +/*********************************************************************** + * Signal helper routines + */ + +/* + * Direct the given signal to the given handler in a platform-neutral + * way. + */ +HIDDEN void +stm_signal_catch(int sig, void (*handler)(int)) +{ +#ifdef NO_USE_SIGACTION + STM_TRACE(("%d: stm_signal_catch(sig=%d, handler=%p)\n", getpid(), sig, + handler)); + + signal(sig, handler); +#else + struct sigaction sa; + + STM_TRACE(("%d: stm_signal_catch(sig=%d, handler=%p)\n", getpid(), sig, + handler)); + + sa.sa_flags = 0; + sa.sa_handler = handler; + sigemptyset(&sa.sa_mask); + if (sigaction(sig, &sa, NULL) != 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server, + "stm_signal_catch: sigaction(%d)", sig); +#endif +} + +/* + * Set the given signal to its default disposition. + */ +HIDDEN void +stm_signal_default(int sig) +{ + stm_signal_catch(sig, SIG_DFL); +} + +/* + * Set the given signal to be ignored. + */ +HIDDEN void +stm_signal_ignore(int sig) +{ + stm_signal_catch(sig, SIG_IGN); +} + +/* + * Set the given signal to dump core in the designated core dump + * directory. + */ +HIDDEN void +stm_signal_core(int sig) +{ +#ifdef NO_USE_SIGACTION + STM_TRACE(("%d: stm_signal_core(sig=%d)\n", getpid(), sig)); + + signal(sig, stm_onsig_core); +#else + struct sigaction sa; + + STM_TRACE(("%d: stm_signal_core(sig=%d)\n", getpid(), sig)); + +# if defined(SA_ONESHOT) + sa.sa_flags = SA_ONESHOT; +# elif defined(SA_RESETHAND) + sa.sa_flags = SA_RESETHAND; +# else + sa.sa_flags = 0; +# endif + sigemptyset(&sa.sa_mask); + sa.sa_handler = stm_onsig_core; + if (sigaction(sig, &sa, NULL) != 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server, + "stm_signal_core: sigaction(%d)", sig); +#endif +} + + +/*********************************************************************** + * Signal handlers + */ + +/* + * Dump core in the designated core dump directory. + */ +HIDDEN void +stm_onsig_core(int sig) +{ + STM_TRACE(("%d: stm_onsig_core(sig=%d)\n", getpid(), sig)); + + if (stm.config.core_dir) + chdir(stm.config.core_dir); + stm_signal_default(sig); + kill(getpid(), sig); +} + +/* + * Begin server restart. Only SIGWINCH uses graceful restart. + */ +HIDDEN void +stm_onsig_restart(int sig) +{ + STM_TRACE(("%d: stm_onsig_restart(sig=%d)\n", getpid(), sig)); + + stm.state = (sig == SIGWINCH) ? STM_STATE_GRACEFUL : STM_STATE_RESTART; +} + +/* + * Begin server shutdown. + */ +HIDDEN void +stm_onsig_shutdown(int sig) +{ + STM_TRACE(("%d: stm_onsig_shutdown(sig=%d)\n", getpid(), sig)); + + stm.state = STM_STATE_SHUTDOWN; +} + + +/*********************************************************************** + * Miscellany + */ + +/* + * Convert the given VP and thread indices to a "connection identifier" + * for mod_status. + */ +HIDDEN long +stm_id_make(int v, int t) +{ + STM_TRACE(("%d: stm_id_make(v=%d, t=%d)\n", getpid(), v, t)); + + STM_ASSERT(v >= 0 && v < STM_VP_LIMIT); + STM_ASSERT(t >= 0 && t < STM_ST_LIMIT); + + return (long) v * STM_ST_LIMIT + t; +} + +/* + * Convert the given connection identifier into VP and thread indices, + * if within bounds. Returns nonzero on success, zero on failure. + */ +HIDDEN int +stm_id_break(long id, int *vp, int *tp) +{ + int v, t; + + STM_TRACE(("%d: stm_id_break(id=%ld)\n", getpid(), id)); + + v = id / STM_ST_LIMIT; + t = id % STM_ST_LIMIT; + if (v >= 0 && v < STM_VP_LIMIT && t >= 0 && t < STM_ST_LIMIT) { + *vp = v; + *tp = t; + return 1; + } + return 0; +} + +/* + * Disable the Nagle algorithm on an accepted TCP socket. + */ +HIDDEN void +stm_disable_nagle(st_netfd_t fd) +{ + STM_TRACE(("%d: stm_disable_nagle(fd=%p(%d))\n", getpid(), fd, + st_netfd_fileno(fd))); + +#if !defined(IRIX) || IRIX < 65 + { + int on = 1; + + if (setsockopt(st_netfd_fileno(fd), IPPROTO_TCP, TCP_NODELAY, + (char *) &on, sizeof on) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server, + "stm_disable_nagle: setsockopt(%d, TCP_NODELAY)", + st_netfd_fileno(fd)); + } +#endif +} + + +/*********************************************************************** + * Exported API routines + */ + +/* + * Begin server shutdown. + */ +void +ap_start_shutdown(void) +{ + STM_TRACE(("%d: ap_start_shutdown() STM\n", getpid())); + + kill(stm.pid, SIGTERM); +} + +/* + * Begin server restart. + */ +void +ap_start_restart(int graceful) +{ + STM_TRACE(("%d: ap_start_restart(graceful=%d) STM\n", getpid(), graceful)); + + kill(stm.pid, graceful ? SIGWINCH : SIGHUP); +} + +/* + * Returns nonzero if the loop processing requests on a connection + * should prematurely terminate. This is the actual usage which belies + * the name of this predicate function. + */ +int +ap_graceful_stop_signalled(void) +{ + STM_TRACE(("%d: ap_graceful_stop_signalled() STM\n", getpid())); + + /* + * This is called from a ST on a VP via ap_process_http_connection() + * so we must ask our thread if it has been requested to stop. + * Doesn't matter if the whole server is going down, just that this + * thread is, so the server should abort the connection ASAP. + */ + return stm_thread_stopped(stm_thread_self()); +} + +/* + * Clear all state about the connection with the given ID. + */ +void +ap_reset_connection_status(long id) +{ + int v, t; + + STM_TRACE(("%d: ap_reset_connection_status(id=%ld) STM\n", getpid(), id)); + + if (!stm.config.connection_status) + return; + + if (stm_id_break(id, &v, &t)) { + stm_score *sp, *ep; + + /* We should be running on VP v but it's not strictly necessary */ + + for (sp = stm.vps[v].scoreboard->scores[t], + ep = sp + STM_SCORE_LIMIT; sp < ep; sp++) + sp->key[0] = 0; + } else + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, + stm.server, "ap_reset_connection_status (STM): bogus id %ld", id); +} + +/* + * Associate the given key/value state with the connection with the + * given ID. Keys and values may be silently truncated if they exceed + * STM_SCORE_KEY_SIZE or STM_SCORE_VALUE_SIZE respectively. If state + * with the given key already exists for this connection the new value + * replaces the old. + */ +void +ap_update_connection_status(long id, const char *key, const char *value) +{ + int v, t; + + STM_TRACE(("%d: ap_update_connection_status(id=%ld, key=\"%s\", value=\"%s\") STM\n", + getpid(), id, key, value)); + + if (!stm.config.connection_status) + return; + + if (stm_id_break(id, &v, &t)) { + stm_score *sp, *ep; + + /* We should be running on VP v but it's not strictly necessary */ + + for (sp = stm.vps[v].scoreboard->scores[t], + ep = sp + STM_SCORE_LIMIT; sp < ep; sp++) { + if (sp->key[0] == 0) { + apr_cpystrn(sp->key, key, sizeof sp->key); + apr_cpystrn(sp->value, value, sizeof sp->value); + break; + } + if (!strcmp(sp->key, key)) { + apr_cpystrn(sp->value, value, sizeof sp->value); + break; + } + } + } else + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, stm.server, + "ap_update_connection_status (STM): bogus id %ld", id); +} + +/* + * Return an array of status information. + */ +apr_array_header_t * +ap_get_status_table(apr_pool_t *pool) +{ + apr_array_header_t *ap; + int asize, tsize, i; + ap_status_table_row_t *srp; + int total_connections, most_threads, + listener_connections[STM_LISTENER_LIMIT]; + + STM_TRACE(("%d: ap_get_status_table(pool=%p)\n", getpid(), pool)); + + /* + * Create an array large enough to hold: + * - one entry per VP containing VP-specific status + * - one entry per thread per VP containing connection-specific status + * - one entry containing general status + */ + asize = 1; + if (stm.config.connection_status) + asize += stm.config.max_threads; + asize *= stm.config.num_vps; + asize++; + ap = apr_make_array(pool, asize, sizeof (ap_status_table_row_t)); + + /* put the general status table first */ + srp = apr_push_array(ap); + + total_connections = 0; + most_threads = 0; + memset(listener_connections, 0, sizeof listener_connections); + for (i = 0; i < stm.config.num_vps; i++) { + const stm_vp *vp; + const stm_scoreboard *sbp; + ap_status_table_row_t *vrp; + char timebuf[32]; + int j; + + vp = &stm.vps[i]; + sbp = vp->scoreboard; + + /* + * Create tables large enough to hold: + * - eight data about VP-specific status + * - one datum per listener about listener-specific status + * - one datum per thread about connection-specific status + */ + tsize = 8 + vp->num_listeners + stm.config.max_threads; + + vrp = apr_push_array(ap); + vrp->conn_id = -1000 - i; + vrp->data = apr_make_table(pool, tsize); + apr_ctime(timebuf, sbp->vp_start_time); + apr_table_addn(vrp->data, "Process ID", + apr_psprintf(pool, "%d", sbp->pid)); + apr_table_addn(vrp->data, "Start time", + apr_pstrdup(pool, timebuf)); + apr_table_addn(vrp->data, "Incarnations", + apr_psprintf(pool, "%d", sbp->incarnations)); + apr_table_addn(vrp->data, "Thread starts", + apr_psprintf(pool, "%d", sbp->thread_starts)); + apr_table_addn(vrp->data, "Thread exits", + apr_psprintf(pool, "%d", sbp->thread_exits)); + apr_table_addn(vrp->data, "Most threads used at once", + apr_psprintf(pool, "%d", sbp->most_threads)); + apr_table_addn(vrp->data, "Connections served, prior incarnations", + apr_psprintf(pool, "%d", sbp->historic_vp_connections)); + apr_table_addn(vrp->data, "Connections served, this incarnation", + apr_psprintf(pool, "%d", sbp->vp_connections)); + + total_connections += sbp->historic_vp_connections + + sbp->vp_connections; + if (most_threads < sbp->most_threads) + most_threads = sbp->most_threads; + + for (j = 0; j < vp->num_listeners; j++) { + const stm_listener *lp; + int lc; + + lp = vp->listeners[j]; + STM_ASSERT(lp->id >= 0 && lp->id < STM_LISTENER_LIMIT); + lc = sbp->listener_connections[lp->id]; + + apr_table_addn(vrp->data, + apr_psprintf(pool, "Connections served for %s", + sbp->listeners[lp->id]), + apr_psprintf(pool, "%d", lc)); + listener_connections[lp->id] += lc; + } + + for (j = 0; j < stm.config.max_threads; j++) { + const stm_score *sp; + + apr_table_addn(vrp->data, + apr_psprintf(pool, "Connections served by thread %d", j), + apr_psprintf(pool, "%d", sbp->thread_connections[j])); + + sp = sbp->scores[j]; + if (stm.config.connection_status && sp->key[0]) { + const stm_score *lep; + ap_status_table_row_t *rp; + + for (lep = sp + STM_SCORE_LIMIT; --lep >= sp; ) + if (lep->key[0]) + break; + + rp = apr_push_array(ap); + rp->conn_id = stm_id_make(i, j); + rp->data = apr_make_table(pool, lep - sp + 1); + + while (sp <= lep) { + apr_table_add(rp->data, sp->key, sp->value); + sp++; + } + } + } + + /* see below */ + STM_ASSERT(apr_table_elts(vrp->data)->nelts <= tsize); + } + + /* + * Create a table large enough to hold: + * - two data about general server status + * - one datum per listener about listener-specific status + */ + tsize = 2 + stm.num_listeners; + srp->conn_id = -1; + srp->data = apr_make_table(pool, tsize); + apr_table_addn(srp->data, "Total connections served", + apr_psprintf(pool, "%d", total_connections)); + apr_table_addn(srp->data, "Most threads used", + apr_psprintf(pool, "%d", most_threads)); + for (i = 0; i < stm.num_listeners; i++) + apr_table_addn(srp->data, + apr_psprintf(pool, "Total connections served for %s:%u", + stm.listeners[i].addr_string, + ntohs(stm.listeners[i].addr.sin_port)), + apr_psprintf(pool, "%d", listener_connections[i])); + STM_ASSERT(apr_table_elts(srp->data)->nelts <= tsize); /* see below */ + + /* + * Not really worthy of an abort if botched, because arrays and + * tables grow automatically, but good to check that we're + * preallocating enough space, for speed. + */ + STM_ASSERT(ap->nelts <= asize); + + return ap; +} + +/* + * Run the already-configured STM MPM. Returns nonzero if the server + * should exit, zero if it should reconfigure and restart. + */ +int +ap_mpm_run(apr_pool_t *conf_pool, apr_pool_t *log_pool, server_rec *server) +{ + int i; + + STM_TRACE(("%d: => ap_mpm_run(conf_pool=%p, log_pool=%p, server=%p) STM\n", + getpid(), conf_pool, log_pool, server)); + + if (!stm_init(conf_pool, server)) { + ap_log_error(APLOG_MARK, APLOG_CRIT | APLOG_NOERRNO, errno, server, + "STM MPM failed to initialize, shutting down"); + return -1; + } + + ap_log_pid(conf_pool, stm.config.pid_file); + + ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, server, + "%s configured -- resuming normal operations", + ap_get_server_version()); + ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, 0, server, + "State Threaded MPM: %d virtual processors with up to %d threads each", + stm.config.num_vps, stm.config.max_threads); + ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, 0, server, + "Server built: %s", ap_get_server_built()); + + stm_main(); + + STM_ASSERT(stm.state != STM_STATE_RUN); + switch (stm.state) { + case STM_STATE_SHUTDOWN: + ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, server, + "shutting down by request from signal"); + for (i = 0; i < STM_VP_LIMIT; i++) + stm_vp_stop(&stm.vps[i]); + stm_doomsday(); + if (unlink(stm.config.pid_file) == 0) + ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, 0, stm.server, + "removed PID file %s (pid=%d)", stm.config.pid_file, getpid()); + break; + case STM_STATE_RESTART: + ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, server, + "restarting by request from signal"); + stm_signal_ignore(SIGTERM); + if (unixd_killpg(getpgrp(), SIGTERM) == -1) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, server, + "ap_mpm_run(STM): killpg"); + stm_doomsday(); + break; + case STM_STATE_GRACEFUL: + ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, server, + "gracefully restarting by request from signal"); + for (i = 0; i < STM_VP_LIMIT; i++) + stm_vp_stop(&stm.vps[i]); + stm_doomsday(); + break; + } + + stm_fini(); + + STM_TRACE(("%d: <= ap_mpm_run(conf_pool=%p, log_pool=%p, server=%p) STM, shutdown=%d\n", + getpid(), conf_pool, log_pool, server, stm.state == STM_STATE_SHUTDOWN)); + return stm.state == STM_STATE_SHUTDOWN; +} diff -Naur apache_2.0a6/src/support/Makefile.in-orig apache_2.0a6/src/support/Makefile.in --- apache_2.0a6/src/support/Makefile.in-orig Mon May 15 17:44:47 2000 +++ apache_2.0a6/src/support/Makefile.in Wed Aug 23 14:18:32 2000 @@ -1,5 +1,5 @@ -PROGRAMS = htpasswd htdigest rotatelogs logresolve ab +PROGRAMS = htpasswd htdigest rotatelogs logresolve ab stmstat targets = $(PROGRAMS) PROGRAM_LDADD = $(EXTRA_LDFLAGS) $(PROGRAM_DEPENDENCIES) $(EXTRA_LIBS) @@ -28,3 +28,6 @@ ab: $(ab_OBJECTS) $(LINK) $(ab_OBJECTS) $(PROGRAM_LDADD) +stmstat_OBJECTS = stmstat.lo +stmstat: $(stmstat_OBJECTS) + $(LINK) $(stmstat_OBJECTS) $(PROGRAM_LDADD) diff -Naur apache_2.0a6/src/support/stmstat.c-orig apache_2.0a6/src/support/stmstat.c --- apache_2.0a6/src/support/stmstat.c-orig +++ apache_2.0a6/src/support/stmstat.c Wed Aug 23 14:16:06 2000 @@ -0,0 +1,415 @@ +/* + * Copyright (c) 2000 Silicon Graphics, Inc. All rights reserved. + */ + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + * Portions of this software are based upon public domain software + * originally written at the National Center for Supercomputing Applications, + * University of Illinois, Urbana-Champaign. + */ + +/* + * stmstat - Status reporting tool for the STM + * + * Mike Abbott - mja@sgi.com + * Accelerating Apache Project - http://oss.sgi.com/projects/apache/ + * + * The state-threaded multi-processing module (STM MPM) for Apache/2.0 + * maintains status information in disk files. Although all the + * information is available via the server's server-status page, + * gathering it this way causes the server to serve a page which might + * be undesirable in high load situations. The stmstat tool reports the + * same information without interfering with the web server. + * + * This tool is designed to be extended and not modified. Unless you're + * fixing a bug in an existing version, do not modify the routines that + * are already here. When the on-disk scoreboard format changes, the + * version number the STM stores in the file must also change. Add new + * parsing/printing routines for that version to this tool, taking care + * not to depend on or modify any part of any other version. Preserving + * backward compatibility is crucial. This tool must always be able to + * report any new or old version of the status information. + * + * This tool uses read instead of mmap to get the data, for no good + * reason. Read is more portable, but the STM already requires mmap. + * Whatever. + * + * See htdocs/manual/stm.html for further documentation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +int verbose = 0; + +/* + * Gcc needs these to be happy in maintainer-mode. Doesn't make sense, + * since they're defined before use. Oh well. + */ +void readn(int, void *, size_t, const char *, const char *); +void padto(int, size_t, const char *); +void eof(int, const char *); +int show(const char *, int, int); + +/* + * Read nbytes of data, described by desc, into buf from fd (which is + * from fn). Exits on any error. + */ +void +readn(int fd, void *buf, size_t nbytes, const char *desc, const char *fn) +{ + ssize_t n; + + n = read(fd, buf, nbytes); + if (n == -1) { + fprintf(stderr, "cannot read %s from %s: %s\n", desc, fn, + strerror(errno)); + exit(1); + } else if (n != nbytes) { + fprintf(stderr, "short read (%d/%d at %ld) of %s from %s\n", + n, nbytes, (long) lseek(fd, -n, SEEK_CUR), desc, fn); + exit(1); + } +} + +/* + * Read and discard zero to pad-1 bytes of padding from the given file + * (which is from the given file name). Exits if the padding is + * missing. + */ +void +padto(int fd, size_t pad, const char *fn) +{ + off_t o; + + o = lseek(fd, 0, SEEK_CUR) & (pad - 1); + if (o) { + char junk[15]; + + if (o > sizeof junk) + abort(); + readn(fd, junk, o, "padding", fn); + } +} + +/* + * Verify that there is no more data in the given file (which is from + * the given file name). Exits on failure. + */ +void +eof(int fd, const char *fn) +{ + char c; + + if (read(fd, &c, 1) == 1) { + fprintf(stderr, "data beyond expected eof (at %ld) in %s\n", + (long) lseek(fd, -1, SEEK_CUR), fn); + exit(1); + } +} + +/* The scoreboard version size must never change. */ +#define STM_SCOREBOARD_VERSION_LENGTH 16 + +typedef int ap_int32_t; +typedef long long ap_time_t; + + +/* insert new scoreboard version data and procedures here */ + + +/***************************************** + * Scoreboard version 0001 + */ +#define STM_SCOREBOARD_VERSION_0001 "STM-MPM-sb-0001\n" +typedef struct { + ap_int32_t st_limit; /* size of arrays below */ + ap_int32_t listener_limit; /* size of arrays below */ + ap_int32_t score_limit; /* size of arrays below */ + ap_int32_t key_size; /* size of arrays below */ + ap_int32_t value_size; /* size of arrays below */ + ap_int32_t pid; /* this VP's PID */ + ap_time_t start_time; /* server's start time */ + ap_time_t vp_start_time; /* this VP's start time */ + ap_int32_t incarnations; /* # of different VPs in this slot */ + ap_int32_t thread_starts; /* # of thread start events */ + ap_int32_t thread_exits; /* # of thread exit events */ + ap_int32_t most_threads; /* most threads ever used */ + ap_int32_t historic_vp_connections;/* # of conns, prior incarnations */ + ap_int32_t vp_connections; /* # of conns, this incarnation */ +} scoreboard_0001_fixed; /* fixed-size portion of the scoreboard */ +typedef struct { + char *key; + char *value; +} score_0001; /* scores are key-value pairs */ +typedef struct { + ap_int32_t *thread_connections; /* per thread */ + ap_int32_t *listener_connections; /* per listener */ + char **listeners; /* IP address:port */ + score_0001 **scores; /* per-conn data */ +} scoreboard_0001_vary; /* varying-size portion of the scoreboard */ + +/* for gcc in maintainer-mode */ +const char *ttoa_0001(ap_time_t); +void show_0001(int, const char *); + +/* + * Convert the given time (in microseconds since the epoch) to a string. + * Returns a static buffer. + */ +const char * +ttoa_0001(ap_time_t t) +{ + static char buf[64]; + time_t s; + int u; + struct tm *tp; + + s = (time_t) (t / 1000000); + u = t % 1000000; + tp = localtime(&s); + sprintf(&buf[strftime(buf, sizeof buf, "%Y-%b-%d %T", tp)], ".%06d", u); + + return buf; +} + +/* + * Read the scoreboard data from the given file descriptor (which is from + * the given file name) and parse and print all the information. + */ +void +show_0001(int fd, const char *fn) +{ + scoreboard_0001_fixed fixed; + scoreboard_0001_vary vary; + size_t size; + int i, j; + + /* + * The output could be prettier. I mostly wanted to write this as a + * proof of concept. + */ + + printf("%s\n", fn); + + /* first read and sanity check the fixed-size portion */ + readn(fd, &fixed, sizeof fixed, "fixed-size portion", fn); + if (verbose) { + printf("st_limit %-5d\n", fixed.st_limit); + printf("listener_limit %-5d\n", fixed.listener_limit); + printf("score_limit %-5d\n", fixed.score_limit); + printf("key_size %-5d\n", fixed.key_size); + printf("value_size %-5d\n", fixed.value_size); + } + if (fixed.st_limit < 1 || fixed.st_limit > 1000000 || + fixed.listener_limit < 1 || fixed.listener_limit > 1000000 || + fixed.score_limit < 1 || fixed.score_limit > 1000000 || + fixed.key_size < 1 || fixed.key_size > 1000000 || + fixed.value_size < 1 || fixed.value_size > 1000000) { + fprintf(stderr, "invalid sizes in %s\n", fn); + exit(1); + } + + /* dead VP? */ + if (fixed.pid == -1) { + printf("blank\n"); + return; + } + + /* then read the varying-size portion in chunks */ + + size = fixed.st_limit * sizeof *vary.thread_connections; + vary.thread_connections = (ap_int32_t *) malloc(size); + readn(fd, vary.thread_connections, size, "thread_connections", fn); + + size = fixed.listener_limit * sizeof *vary.listener_connections; + vary.listener_connections = (ap_int32_t *) malloc(size); + readn(fd, vary.listener_connections, size, "listener_connections", fn); + + size = fixed.listener_limit * sizeof *vary.listeners; + vary.listeners = (char **) malloc(size); + for (i = 0; i < fixed.listener_limit; i++) { + size = 24; + vary.listeners[i] = (char *) malloc(size); + readn(fd, vary.listeners[i], size, "listeners", fn); + } + + size = fixed.st_limit * sizeof *vary.scores; + vary.scores = (score_0001 **) malloc(size); + for (i = 0; i < fixed.st_limit; i++) { + size = fixed.score_limit * sizeof *vary.scores[i]; + vary.scores[i] = (score_0001 *) malloc(size); + + for (j = 0; j < fixed.score_limit; j++) { + size = fixed.key_size * sizeof *vary.scores[i][j].key; + vary.scores[i][j].key = (char *) malloc(size); + readn(fd, vary.scores[i][j].key, size, "scores", fn); + + size = fixed.value_size * sizeof *vary.scores[i][j].value; + vary.scores[i][j].value = (char *) malloc(size); + readn(fd, vary.scores[i][j].value, size, "scores", fn); + } + } + +#if defined(_MIPS_SIM) && _MIPS_SIM != _ABIO32 + /* + * SGI's MIPSpro compilers pad the struct out to an 8-byte boundary + * in the N32 and 64-bit ABIs. + */ + padto(fd, 8, fn); +#endif + eof(fd, fn); + + /* now report */ + + printf("Server started at %s\n", ttoa_0001(fixed.start_time)); + printf("VP started at %s\n", ttoa_0001(fixed.vp_start_time)); + printf("VP pid %d\n", fixed.pid); + printf("incarnations %d\n", fixed.incarnations); + printf("thread_starts %d\n", fixed.thread_starts); + printf("thread_exits %d\n", fixed.thread_exits); + printf("most_threads %d\n", fixed.most_threads); + printf("historic_vp_connections %d\n", fixed.historic_vp_connections); + printf("vp_connections %d\n", fixed.vp_connections); + for (i = 0; i < fixed.st_limit; i++) + printf("thread_connections[%d] %d\n", i, vary.thread_connections[i]); + for (i = 0; i < fixed.listener_limit; i++) { + printf("listeners[%d] = %.*s\n", i, 24, vary.listeners[i]); + printf("listener_connections[%d] %d\n", i, vary.listener_connections[i]); + } + for (i = 0; i < fixed.st_limit; i++) + for (j = 0; j < fixed.score_limit; j++) + if (vary.scores[i][j].key[0]) + printf("score[%d][%d] %.*s = %.*s\n", i, j, + fixed.key_size, vary.scores[i][j].key, + fixed.value_size, vary.scores[i][j].value); +} + + +/* + * Read and report status for VP #n from its scoreboard file in the + * given directory. If must is non-zero, report open errors on the + * file, otherwise we're just scanning so keep quiet. Returns nonzero + * on success, zero on failure. + */ +int +show(const char *dir, int n, int must) +{ + char fn[PATH_MAX], version[STM_SCOREBOARD_VERSION_LENGTH]; + int fd; + + sprintf(fn, "%s/%d", dir, n); + fd = open(fn, O_RDONLY); + if (fd > 0) { + readn(fd, version, sizeof version, "version", fn); + + /* insert new version checks here */ + + if (!memcmp(version, STM_SCOREBOARD_VERSION_0001, sizeof version)) { + show_0001(fd, fn); + return 1; + } else + fprintf(stderr, "unknown scoreboard version \"%.*s\" in %s\n", + (int) sizeof version, version, fn); + + close(fd); + } else if (must) + perror(fn); + + return 0; +} + +int +main(int argc, char **argv) +{ + int n, c; + + n = -1; + while ((c = getopt(argc, argv, "n:v")) != -1) { + switch (c) { + case 'n': + n = atoi(optarg); + break; + case 'v': + verbose = 1; + break; + default: +usage: fprintf(stderr, "Usage: %s" + " [-n number]" + " [-v]" + " scoreboards-directory\n", argv[0]); + exit(1); + } + } + + if (argc - optind != 1) + goto usage; + + if (n >= 0) + show(argv[optind], n, 1); + else { + c = 0; + while (show(argv[optind], c, 0)) + c++; + } + + return 0; +}