1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 /*
7  *
8  * Notes:
9  * [1] lth. The call to Sleep() is a hack to get the test case to run
10  * on Windows 95. Without it, the test case fails with an error
11  * WSAECONNRESET following a recv() call. The error is caused by the
12  * server side thread termination without a shutdown() or closesocket()
13  * call. Windows docmunentation suggests that this is predicted
14  * behavior; that other platforms get away with it is ... serindipity.
15  * The test case should shutdown() or closesocket() before
16  * thread termination. I didn't have time to figure out where or how
17  * to do it. The Sleep() call inserts enough delay to allow the
18  * client side to recv() all his data before the server side thread
19  * terminates. Whew! ...
20  *
21  ** Modification History:
22  * 14-May-97 AGarcia- Converted the test to accomodate the debug_mode flag.
23  *             The debug mode will print all of the printfs associated with this test.
24  *             The regress mode will be the default mode. Since the regress tool limits
25  *           the output to a one line status:PASS or FAIL,all of the printf statements
26  *             have been handled with an if (debug_mode) statement.
27  */
28 
29 #include "prclist.h"
30 #include "prcvar.h"
31 #include "prerror.h"
32 #include "prinit.h"
33 #include "prinrval.h"
34 #include "prio.h"
35 #include "prlock.h"
36 #include "prlog.h"
37 #include "prtime.h"
38 #include "prmem.h"
39 #include "prnetdb.h"
40 #include "prprf.h"
41 #include "prthread.h"
42 
43 #include "pprio.h"
44 #include "primpl.h"
45 
46 #include "plstr.h"
47 #include "plerror.h"
48 #include "plgetopt.h"
49 
50 #include <stdlib.h>
51 #include <string.h>
52 
53 #if defined(XP_UNIX)
54 #include <math.h>
55 #endif
56 
57 /*
58 ** This is the beginning of the test
59 */
60 
61 #define RECV_FLAGS 0
62 #define SEND_FLAGS 0
63 #define DEFAULT_LOW 0
64 #define DEFAULT_HIGH 0
65 #define BUFFER_SIZE 1024
66 #define DEFAULT_BACKLOG 5
67 
68 #ifdef DEBUG
69 #define PORT_INC_DO +100
70 #else
71 #define PORT_INC_DO
72 #endif
73 #ifdef IS_64
74 #define PORT_INC_3264 +200
75 #else
76 #define PORT_INC_3264
77 #endif
78 
79 #define DEFAULT_PORT 12849 PORT_INC_DO PORT_INC_3264
80 
81 #define DEFAULT_CLIENTS 1
82 #define ALLOWED_IN_ACCEPT 1
83 #define DEFAULT_CLIPPING 1000
84 #define DEFAULT_WORKERS_MIN 1
85 #define DEFAULT_WORKERS_MAX 1
86 #define DEFAULT_SERVER "localhost"
87 #define DEFAULT_EXECUTION_TIME 10
88 #define DEFAULT_CLIENT_TIMEOUT 4000
89 #define DEFAULT_SERVER_TIMEOUT 4000
90 #define DEFAULT_SERVER_PRIORITY PR_PRIORITY_HIGH
91 
92 typedef enum CSState_e {cs_init, cs_run, cs_stop, cs_exit} CSState_t;
93 
94 static void PR_CALLBACK Worker(void *arg);
95 typedef struct CSPool_s CSPool_t;
96 typedef struct CSWorker_s CSWorker_t;
97 typedef struct CSServer_s CSServer_t;
98 typedef enum Verbosity
99 {
100     TEST_LOG_ALWAYS,
101     TEST_LOG_ERROR,
102     TEST_LOG_WARNING,
103     TEST_LOG_NOTICE,
104     TEST_LOG_INFO,
105     TEST_LOG_STATUS,
106     TEST_LOG_VERBOSE
107 } Verbosity;
108 
109 static PRInt32 domain = AF_INET;
110 static PRInt32 protocol = 6;  /* TCP */
111 static PRFileDesc *debug_out = NULL;
112 static PRBool debug_mode = PR_FALSE;
113 static PRBool pthread_stats = PR_FALSE;
114 static Verbosity verbosity = TEST_LOG_ALWAYS;
115 static PRThreadScope thread_scope = PR_LOCAL_THREAD;
116 
117 struct CSWorker_s
118 {
119     PRCList element;        /* list of the server's workers */
120 
121     PRThread *thread;       /* this worker objects thread */
122     CSServer_t *server;     /* back pointer to server structure */
123 };
124 
125 struct CSPool_s
126 {
127     PRCondVar *exiting;
128     PRCondVar *acceptComplete;
129     PRUint32 accepting, active, workers;
130 };
131 
132 struct CSServer_s
133 {
134     PRCList list;           /* head of worker list */
135 
136     PRLock *ml;
137     PRThread *thread;       /* the main server thread */
138     PRCondVar *stateChange;
139 
140     PRUint16 port;          /* port we're listening on */
141     PRUint32 backlog;       /* size of our listener backlog */
142     PRFileDesc *listener;   /* the fd accepting connections */
143 
144     CSPool_t pool;          /* statistics on worker threads */
145     CSState_t state;        /* the server's state */
146     struct                  /* controlling worker counts */
147     {
148         PRUint32 minimum, maximum, accepting;
149     } workers;
150 
151     /* statistics */
152     PRIntervalTime started, stopped;
153     PRUint32 operations, bytesTransferred;
154 };
155 
156 typedef struct CSDescriptor_s
157 {
158     PRInt32 size;       /* size of transfer */
159     char filename[60];  /* filename, null padded */
160 } CSDescriptor_t;
161 
162 typedef struct CSClient_s
163 {
164     PRLock *ml;
165     PRThread *thread;
166     PRCondVar *stateChange;
167     PRNetAddr serverAddress;
168 
169     CSState_t state;
170 
171     /* statistics */
172     PRIntervalTime started, stopped;
173     PRUint32 operations, bytesTransferred;
174 } CSClient_t;
175 
176 #define TEST_LOG(l, p, a) \
177     do { \
178         if (debug_mode || (p <= verbosity)) printf a; \
179     } while (0)
180 
181 PRLogModuleInfo *cltsrv_log_file = NULL;
182 
183 #define MY_ASSERT(_expr) \
184     ((_expr)?((void)0):_MY_Assert(# _expr,__FILE__,__LINE__))
185 
186 #define TEST_ASSERT(_expr) \
187     ((_expr)?((void)0):_MY_Assert(# _expr,__FILE__,__LINE__))
188 
_MY_Assert(const char * s,const char * file,PRIntn ln)189 static void _MY_Assert(const char *s, const char *file, PRIntn ln)
190 {
191     PL_PrintError(NULL);
192     PR_Assert(s, file, ln);
193 }  /* _MY_Assert */
194 
Aborted(PRStatus rv)195 static PRBool Aborted(PRStatus rv)
196 {
197     return ((PR_FAILURE == rv) && (PR_PENDING_INTERRUPT_ERROR == PR_GetError())) ?
198            PR_TRUE : PR_FALSE;
199 }
200 
TimeOfDayMessage(const char * msg,PRThread * me)201 static void TimeOfDayMessage(const char *msg, PRThread* me)
202 {
203     char buffer[100];
204     PRExplodedTime tod;
205     PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &tod);
206     (void)PR_FormatTime(buffer, sizeof(buffer), "%H:%M:%S", &tod);
207 
208     TEST_LOG(
209         cltsrv_log_file, TEST_LOG_ALWAYS,
210         ("%s(0x%p): %s\n", msg, me, buffer));
211 }  /* TimeOfDayMessage */
212 
213 
Client(void * arg)214 static void PR_CALLBACK Client(void *arg)
215 {
216     PRStatus rv;
217     PRIntn index;
218     char buffer[1024];
219     PRFileDesc *fd = NULL;
220     PRUintn clipping = DEFAULT_CLIPPING;
221     PRThread *me = PR_GetCurrentThread();
222     CSClient_t *client = (CSClient_t*)arg;
223     CSDescriptor_t *descriptor = PR_NEW(CSDescriptor_t);
224     PRIntervalTime timeout = PR_MillisecondsToInterval(DEFAULT_CLIENT_TIMEOUT);
225 
226 
227     for (index = 0; index < sizeof(buffer); ++index) {
228         buffer[index] = (char)index;
229     }
230 
231     client->started = PR_IntervalNow();
232 
233     PR_Lock(client->ml);
234     client->state = cs_run;
235     PR_NotifyCondVar(client->stateChange);
236     PR_Unlock(client->ml);
237 
238     TimeOfDayMessage("Client started at", me);
239 
240     while (cs_run == client->state)
241     {
242         PRInt32 bytes, descbytes, filebytes, netbytes;
243 
244         (void)PR_NetAddrToString(&client->serverAddress, buffer, sizeof(buffer));
245         TEST_LOG(cltsrv_log_file, TEST_LOG_INFO,
246                  ("\tClient(0x%p): connecting to server at %s\n", me, buffer));
247 
248         fd = PR_Socket(domain, SOCK_STREAM, protocol);
249         TEST_ASSERT(NULL != fd);
250         rv = PR_Connect(fd, &client->serverAddress, timeout);
251         if (PR_FAILURE == rv)
252         {
253             TEST_LOG(
254                 cltsrv_log_file, TEST_LOG_ERROR,
255                 ("\tClient(0x%p): conection failed (%d, %d)\n",
256                  me, PR_GetError(), PR_GetOSError()));
257             goto aborted;
258         }
259 
260         memset(descriptor, 0, sizeof(*descriptor));
261         descriptor->size = PR_htonl(descbytes = rand() % clipping);
262         PR_snprintf(
263             descriptor->filename, sizeof(descriptor->filename),
264             "CS%p%p-%p.dat", client->started, me, client->operations);
265         TEST_LOG(
266             cltsrv_log_file, TEST_LOG_VERBOSE,
267             ("\tClient(0x%p): sending descriptor for %u bytes\n", me, descbytes));
268         bytes = PR_Send(
269                     fd, descriptor, sizeof(*descriptor), SEND_FLAGS, timeout);
270         if (sizeof(CSDescriptor_t) != bytes)
271         {
272             if (Aborted(PR_FAILURE)) {
273                 goto aborted;
274             }
275             if (PR_IO_TIMEOUT_ERROR == PR_GetError())
276             {
277                 TEST_LOG(
278                     cltsrv_log_file, TEST_LOG_ERROR,
279                     ("\tClient(0x%p): send descriptor timeout\n", me));
280                 goto retry;
281             }
282         }
283         TEST_ASSERT(sizeof(*descriptor) == bytes);
284 
285         netbytes = 0;
286         while (netbytes < descbytes)
287         {
288             filebytes = sizeof(buffer);
289             if ((descbytes - netbytes) < filebytes) {
290                 filebytes = descbytes - netbytes;
291             }
292             TEST_LOG(
293                 cltsrv_log_file, TEST_LOG_VERBOSE,
294                 ("\tClient(0x%p): sending %d bytes\n", me, filebytes));
295             bytes = PR_Send(fd, buffer, filebytes, SEND_FLAGS, timeout);
296             if (filebytes != bytes)
297             {
298                 if (Aborted(PR_FAILURE)) {
299                     goto aborted;
300                 }
301                 if (PR_IO_TIMEOUT_ERROR == PR_GetError())
302                 {
303                     TEST_LOG(
304                         cltsrv_log_file, TEST_LOG_ERROR,
305                         ("\tClient(0x%p): send data timeout\n", me));
306                     goto retry;
307                 }
308             }
309             TEST_ASSERT(bytes == filebytes);
310             netbytes += bytes;
311         }
312         filebytes = 0;
313         while (filebytes < descbytes)
314         {
315             netbytes = sizeof(buffer);
316             if ((descbytes - filebytes) < netbytes) {
317                 netbytes = descbytes - filebytes;
318             }
319             TEST_LOG(
320                 cltsrv_log_file, TEST_LOG_VERBOSE,
321                 ("\tClient(0x%p): receiving %d bytes\n", me, netbytes));
322             bytes = PR_Recv(fd, buffer, netbytes, RECV_FLAGS, timeout);
323             if (-1 == bytes)
324             {
325                 if (Aborted(PR_FAILURE))
326                 {
327                     TEST_LOG(
328                         cltsrv_log_file, TEST_LOG_ERROR,
329                         ("\tClient(0x%p): receive data aborted\n", me));
330                     goto aborted;
331                 }
332                 else if (PR_IO_TIMEOUT_ERROR == PR_GetError())
333                     TEST_LOG(
334                         cltsrv_log_file, TEST_LOG_ERROR,
335                         ("\tClient(0x%p): receive data timeout\n", me));
336                 else
337                     TEST_LOG(
338                         cltsrv_log_file, TEST_LOG_ERROR,
339                         ("\tClient(0x%p): receive error (%d, %d)\n",
340                          me, PR_GetError(), PR_GetOSError()));
341                 goto retry;
342             }
343             if (0 == bytes)
344             {
345                 TEST_LOG(
346                     cltsrv_log_file, TEST_LOG_ERROR,
347                     ("\t\tClient(0x%p): unexpected end of stream\n",
348                      PR_GetCurrentThread()));
349                 break;
350             }
351             filebytes += bytes;
352         }
353 
354         rv = PR_Shutdown(fd, PR_SHUTDOWN_BOTH);
355         if (Aborted(rv)) {
356             goto aborted;
357         }
358         TEST_ASSERT(PR_SUCCESS == rv);
359 retry:
360         (void)PR_Close(fd); fd = NULL;
361         TEST_LOG(
362             cltsrv_log_file, TEST_LOG_INFO,
363             ("\tClient(0x%p): disconnected from server\n", me));
364 
365         PR_Lock(client->ml);
366         client->operations += 1;
367         client->bytesTransferred += 2 * descbytes;
368         rv = PR_WaitCondVar(client->stateChange, rand() % clipping);
369         PR_Unlock(client->ml);
370         if (Aborted(rv)) {
371             break;
372         }
373     }
374 
375 aborted:
376     client->stopped = PR_IntervalNow();
377 
378     PR_ClearInterrupt();
379     if (NULL != fd) {
380         rv = PR_Close(fd);
381     }
382 
383     PR_Lock(client->ml);
384     client->state = cs_exit;
385     PR_NotifyCondVar(client->stateChange);
386     PR_Unlock(client->ml);
387     PR_DELETE(descriptor);
388     TEST_LOG(
389         cltsrv_log_file, TEST_LOG_ALWAYS,
390         ("\tClient(0x%p): stopped after %u operations and %u bytes\n",
391          PR_GetCurrentThread(), client->operations, client->bytesTransferred));
392 
393 }  /* Client */
394 
ProcessRequest(PRFileDesc * fd,CSServer_t * server)395 static PRStatus ProcessRequest(PRFileDesc *fd, CSServer_t *server)
396 {
397     PRStatus drv, rv;
398     char buffer[1024];
399     PRFileDesc *file = NULL;
400     PRThread * me = PR_GetCurrentThread();
401     PRInt32 bytes, descbytes, netbytes, filebytes = 0;
402     CSDescriptor_t *descriptor = PR_NEW(CSDescriptor_t);
403     PRIntervalTime timeout = PR_MillisecondsToInterval(DEFAULT_SERVER_TIMEOUT);
404 
405     TEST_LOG(
406         cltsrv_log_file, TEST_LOG_VERBOSE,
407         ("\tProcessRequest(0x%p): receiving desciptor\n", me));
408     bytes = PR_Recv(
409                 fd, descriptor, sizeof(*descriptor), RECV_FLAGS, timeout);
410     if (-1 == bytes)
411     {
412         rv = PR_FAILURE;
413         if (Aborted(rv)) {
414             goto exit;
415         }
416         if (PR_IO_TIMEOUT_ERROR == PR_GetError())
417         {
418             TEST_LOG(
419                 cltsrv_log_file, TEST_LOG_ERROR,
420                 ("\tProcessRequest(0x%p): receive timeout\n", me));
421         }
422         goto exit;
423     }
424     if (0 == bytes)
425     {
426         rv = PR_FAILURE;
427         TEST_LOG(
428             cltsrv_log_file, TEST_LOG_ERROR,
429             ("\tProcessRequest(0x%p): unexpected end of file\n", me));
430         goto exit;
431     }
432     descbytes = PR_ntohl(descriptor->size);
433     TEST_ASSERT(sizeof(*descriptor) == bytes);
434 
435     TEST_LOG(
436         cltsrv_log_file, TEST_LOG_VERBOSE,
437         ("\t\tProcessRequest(0x%p): read descriptor {%d, %s}\n",
438          me, descbytes, descriptor->filename));
439 
440     file = PR_Open(
441                descriptor->filename, (PR_CREATE_FILE | PR_WRONLY), 0666);
442     if (NULL == file)
443     {
444         rv = PR_FAILURE;
445         if (Aborted(rv)) {
446             goto aborted;
447         }
448         if (PR_IO_TIMEOUT_ERROR == PR_GetError())
449         {
450             TEST_LOG(
451                 cltsrv_log_file, TEST_LOG_ERROR,
452                 ("\tProcessRequest(0x%p): open file timeout\n", me));
453             goto aborted;
454         }
455     }
456     TEST_ASSERT(NULL != file);
457 
458     filebytes = 0;
459     while (filebytes < descbytes)
460     {
461         netbytes = sizeof(buffer);
462         if ((descbytes - filebytes) < netbytes) {
463             netbytes = descbytes - filebytes;
464         }
465         TEST_LOG(
466             cltsrv_log_file, TEST_LOG_VERBOSE,
467             ("\tProcessRequest(0x%p): receive %d bytes\n", me, netbytes));
468         bytes = PR_Recv(fd, buffer, netbytes, RECV_FLAGS, timeout);
469         if (-1 == bytes)
470         {
471             rv = PR_FAILURE;
472             if (Aborted(rv)) {
473                 goto aborted;
474             }
475             if (PR_IO_TIMEOUT_ERROR == PR_GetError())
476             {
477                 TEST_LOG(
478                     cltsrv_log_file, TEST_LOG_ERROR,
479                     ("\t\tProcessRequest(0x%p): receive data timeout\n", me));
480                 goto aborted;
481             }
482             /*
483              * XXX: I got (PR_CONNECT_RESET_ERROR, ERROR_NETNAME_DELETED)
484              * on NT here.  This is equivalent to ECONNRESET on Unix.
485              *     -wtc
486              */
487             TEST_LOG(
488                 cltsrv_log_file, TEST_LOG_WARNING,
489                 ("\t\tProcessRequest(0x%p): unexpected error (%d, %d)\n",
490                  me, PR_GetError(), PR_GetOSError()));
491             goto aborted;
492         }
493         if(0 == bytes)
494         {
495             TEST_LOG(
496                 cltsrv_log_file, TEST_LOG_WARNING,
497                 ("\t\tProcessRequest(0x%p): unexpected end of stream\n", me));
498             rv = PR_FAILURE;
499             goto aborted;
500         }
501         filebytes += bytes;
502         netbytes = bytes;
503         /* The byte count for PR_Write should be positive */
504         MY_ASSERT(netbytes > 0);
505         TEST_LOG(
506             cltsrv_log_file, TEST_LOG_VERBOSE,
507             ("\tProcessRequest(0x%p): write %d bytes to file\n", me, netbytes));
508         bytes = PR_Write(file, buffer, netbytes);
509         if (netbytes != bytes)
510         {
511             rv = PR_FAILURE;
512             if (Aborted(rv)) {
513                 goto aborted;
514             }
515             if (PR_IO_TIMEOUT_ERROR == PR_GetError())
516             {
517                 TEST_LOG(
518                     cltsrv_log_file, TEST_LOG_ERROR,
519                     ("\t\tProcessRequest(0x%p): write file timeout\n", me));
520                 goto aborted;
521             }
522         }
523         TEST_ASSERT(bytes > 0);
524     }
525 
526     PR_Lock(server->ml);
527     server->operations += 1;
528     server->bytesTransferred += filebytes;
529     PR_Unlock(server->ml);
530 
531     rv = PR_Close(file);
532     if (Aborted(rv)) {
533         goto aborted;
534     }
535     TEST_ASSERT(PR_SUCCESS == rv);
536     file = NULL;
537 
538     TEST_LOG(
539         cltsrv_log_file, TEST_LOG_VERBOSE,
540         ("\t\tProcessRequest(0x%p): opening %s\n", me, descriptor->filename));
541     file = PR_Open(descriptor->filename, PR_RDONLY, 0);
542     if (NULL == file)
543     {
544         rv = PR_FAILURE;
545         if (Aborted(rv)) {
546             goto aborted;
547         }
548         if (PR_IO_TIMEOUT_ERROR == PR_GetError())
549         {
550             TEST_LOG(
551                 cltsrv_log_file, TEST_LOG_ERROR,
552                 ("\t\tProcessRequest(0x%p): open file timeout\n",
553                  PR_GetCurrentThread()));
554             goto aborted;
555         }
556         TEST_LOG(
557             cltsrv_log_file, TEST_LOG_ERROR,
558             ("\t\tProcessRequest(0x%p): other file open error (%u, %u)\n",
559              me, PR_GetError(), PR_GetOSError()));
560         goto aborted;
561     }
562     TEST_ASSERT(NULL != file);
563 
564     netbytes = 0;
565     while (netbytes < descbytes)
566     {
567         filebytes = sizeof(buffer);
568         if ((descbytes - netbytes) < filebytes) {
569             filebytes = descbytes - netbytes;
570         }
571         TEST_LOG(
572             cltsrv_log_file, TEST_LOG_VERBOSE,
573             ("\tProcessRequest(0x%p): read %d bytes from file\n", me, filebytes));
574         bytes = PR_Read(file, buffer, filebytes);
575         if (filebytes != bytes)
576         {
577             rv = PR_FAILURE;
578             if (Aborted(rv)) {
579                 goto aborted;
580             }
581             if (PR_IO_TIMEOUT_ERROR == PR_GetError())
582                 TEST_LOG(
583                     cltsrv_log_file, TEST_LOG_ERROR,
584                     ("\t\tProcessRequest(0x%p): read file timeout\n", me));
585             else
586                 TEST_LOG(
587                     cltsrv_log_file, TEST_LOG_ERROR,
588                     ("\t\tProcessRequest(0x%p): other file error (%d, %d)\n",
589                      me, PR_GetError(), PR_GetOSError()));
590             goto aborted;
591         }
592         TEST_ASSERT(bytes > 0);
593         netbytes += bytes;
594         filebytes = bytes;
595         TEST_LOG(
596             cltsrv_log_file, TEST_LOG_VERBOSE,
597             ("\t\tProcessRequest(0x%p): sending %d bytes\n", me, filebytes));
598         bytes = PR_Send(fd, buffer, filebytes, SEND_FLAGS, timeout);
599         if (filebytes != bytes)
600         {
601             rv = PR_FAILURE;
602             if (Aborted(rv)) {
603                 goto aborted;
604             }
605             if (PR_IO_TIMEOUT_ERROR == PR_GetError())
606             {
607                 TEST_LOG(
608                     cltsrv_log_file, TEST_LOG_ERROR,
609                     ("\t\tProcessRequest(0x%p): send data timeout\n", me));
610                 goto aborted;
611             }
612             break;
613         }
614         TEST_ASSERT(bytes > 0);
615     }
616 
617     PR_Lock(server->ml);
618     server->bytesTransferred += filebytes;
619     PR_Unlock(server->ml);
620 
621     rv = PR_Shutdown(fd, PR_SHUTDOWN_BOTH);
622     if (Aborted(rv)) {
623         goto aborted;
624     }
625 
626     rv = PR_Close(file);
627     if (Aborted(rv)) {
628         goto aborted;
629     }
630     TEST_ASSERT(PR_SUCCESS == rv);
631     file = NULL;
632 
633 aborted:
634     PR_ClearInterrupt();
635     if (NULL != file) {
636         PR_Close(file);
637     }
638     drv = PR_Delete(descriptor->filename);
639     TEST_ASSERT(PR_SUCCESS == drv);
640 exit:
641     TEST_LOG(
642         cltsrv_log_file, TEST_LOG_VERBOSE,
643         ("\t\tProcessRequest(0x%p): Finished\n", me));
644 
645     PR_DELETE(descriptor);
646 
647 #if defined(WIN95)
648     PR_Sleep(PR_MillisecondsToInterval(200)); /* lth. see note [1] */
649 #endif
650     return rv;
651 }  /* ProcessRequest */
652 
CreateWorker(CSServer_t * server,CSPool_t * pool)653 static PRStatus CreateWorker(CSServer_t *server, CSPool_t *pool)
654 {
655     CSWorker_t *worker = PR_NEWZAP(CSWorker_t);
656     worker->server = server;
657     PR_INIT_CLIST(&worker->element);
658     worker->thread = PR_CreateThread(
659                          PR_USER_THREAD, Worker, worker,
660                          DEFAULT_SERVER_PRIORITY, thread_scope,
661                          PR_UNJOINABLE_THREAD, 0);
662     if (NULL == worker->thread)
663     {
664         PR_DELETE(worker);
665         return PR_FAILURE;
666     }
667 
668     TEST_LOG(cltsrv_log_file, TEST_LOG_STATUS,
669              ("\tCreateWorker(0x%p): create new worker (0x%p)\n",
670               PR_GetCurrentThread(), worker->thread));
671 
672     return PR_SUCCESS;
673 }  /* CreateWorker */
674 
Worker(void * arg)675 static void PR_CALLBACK Worker(void *arg)
676 {
677     PRStatus rv;
678     PRNetAddr from;
679     PRFileDesc *fd = NULL;
680     PRThread *me = PR_GetCurrentThread();
681     CSWorker_t *worker = (CSWorker_t*)arg;
682     CSServer_t *server = worker->server;
683     CSPool_t *pool = &server->pool;
684 
685     TEST_LOG(
686         cltsrv_log_file, TEST_LOG_NOTICE,
687         ("\t\tWorker(0x%p): started [%u]\n", me, pool->workers + 1));
688 
689     PR_Lock(server->ml);
690     PR_APPEND_LINK(&worker->element, &server->list);
691     pool->workers += 1;  /* define our existance */
692 
693     while (cs_run == server->state)
694     {
695         while (pool->accepting >= server->workers.accepting)
696         {
697             TEST_LOG(
698                 cltsrv_log_file, TEST_LOG_VERBOSE,
699                 ("\t\tWorker(0x%p): waiting for accept slot[%d]\n",
700                  me, pool->accepting));
701             rv = PR_WaitCondVar(pool->acceptComplete, PR_INTERVAL_NO_TIMEOUT);
702             if (Aborted(rv) || (cs_run != server->state))
703             {
704                 TEST_LOG(
705                     cltsrv_log_file, TEST_LOG_NOTICE,
706                     ("\tWorker(0x%p): has been %s\n",
707                      me, (Aborted(rv) ? "interrupted" : "stopped")));
708                 goto exit;
709             }
710         }
711         pool->accepting += 1;  /* how many are really in accept */
712         PR_Unlock(server->ml);
713 
714         TEST_LOG(
715             cltsrv_log_file, TEST_LOG_VERBOSE,
716             ("\t\tWorker(0x%p): calling accept\n", me));
717         fd = PR_Accept(server->listener, &from, PR_INTERVAL_NO_TIMEOUT);
718 
719         PR_Lock(server->ml);
720         pool->accepting -= 1;
721         PR_NotifyCondVar(pool->acceptComplete);
722 
723         if ((NULL == fd) && Aborted(PR_FAILURE))
724         {
725             if (NULL != server->listener)
726             {
727                 PR_Close(server->listener);
728                 server->listener = NULL;
729             }
730             goto exit;
731         }
732 
733         if (NULL != fd)
734         {
735             /*
736             ** Create another worker of the total number of workers is
737             ** less than the minimum specified or we have none left in
738             ** accept() AND we're not over the maximum.
739             ** This sort of presumes that the number allowed in accept
740             ** is at least as many as the minimum. Otherwise we'll keep
741             ** creating new threads and deleting them soon after.
742             */
743             PRBool another =
744                 ((pool->workers < server->workers.minimum) ||
745                  ((0 == pool->accepting)
746                   && (pool->workers < server->workers.maximum))) ?
747                 PR_TRUE : PR_FALSE;
748             pool->active += 1;
749             PR_Unlock(server->ml);
750 
751             if (another) {
752                 (void)CreateWorker(server, pool);
753             }
754 
755             rv = ProcessRequest(fd, server);
756             if (PR_SUCCESS != rv)
757                 TEST_LOG(
758                     cltsrv_log_file, TEST_LOG_ERROR,
759                     ("\t\tWorker(0x%p): server process ended abnormally\n", me));
760             (void)PR_Close(fd); fd = NULL;
761 
762             PR_Lock(server->ml);
763             pool->active -= 1;
764         }
765     }
766 
767 exit:
768     PR_ClearInterrupt();
769     PR_Unlock(server->ml);
770 
771     if (NULL != fd)
772     {
773         (void)PR_Shutdown(fd, PR_SHUTDOWN_BOTH);
774         (void)PR_Close(fd);
775     }
776 
777     TEST_LOG(
778         cltsrv_log_file, TEST_LOG_NOTICE,
779         ("\t\tWorker(0x%p): exiting [%u]\n", PR_GetCurrentThread(), pool->workers));
780 
781     PR_Lock(server->ml);
782     pool->workers -= 1;  /* undefine our existance */
783     PR_REMOVE_AND_INIT_LINK(&worker->element);
784     PR_NotifyCondVar(pool->exiting);
785     PR_Unlock(server->ml);
786 
787     PR_DELETE(worker);  /* destruction of the "worker" object */
788 
789 }  /* Worker */
790 
Server(void * arg)791 static void PR_CALLBACK Server(void *arg)
792 {
793     PRStatus rv;
794     PRNetAddr serverAddress;
795     PRThread *me = PR_GetCurrentThread();
796     CSServer_t *server = (CSServer_t*)arg;
797     PRSocketOptionData sockOpt;
798 
799     server->listener = PR_Socket(domain, SOCK_STREAM, protocol);
800 
801     sockOpt.option = PR_SockOpt_Reuseaddr;
802     sockOpt.value.reuse_addr = PR_TRUE;
803     rv = PR_SetSocketOption(server->listener, &sockOpt);
804     TEST_ASSERT(PR_SUCCESS == rv);
805 
806     memset(&serverAddress, 0, sizeof(serverAddress));
807     if (PR_AF_INET6 != domain) {
808         TEST_LOG(cltsrv_log_file, TEST_LOG_ALWAYS,
809                  ("server binding to ip port %s\n", DEFAULT_PORT));
810         rv = PR_InitializeNetAddr(PR_IpAddrAny, DEFAULT_PORT, &serverAddress);
811     }
812     else {
813         TEST_LOG(cltsrv_log_file, TEST_LOG_ALWAYS,
814                  ("server binding to ipv6 port %s\n", DEFAULT_PORT));
815         rv = PR_SetNetAddr(PR_IpAddrAny, PR_AF_INET6, DEFAULT_PORT,
816                            &serverAddress);
817     }
818     rv = PR_Bind(server->listener, &serverAddress);
819     TEST_ASSERT(PR_SUCCESS == rv);
820 
821     rv = PR_Listen(server->listener, server->backlog);
822     TEST_ASSERT(PR_SUCCESS == rv);
823 
824     server->started = PR_IntervalNow();
825     TimeOfDayMessage("Server started at", me);
826 
827     PR_Lock(server->ml);
828     server->state = cs_run;
829     PR_NotifyCondVar(server->stateChange);
830     PR_Unlock(server->ml);
831 
832     /*
833     ** Create the first worker (actually, a thread that accepts
834     ** connections and then processes the work load as needed).
835     ** From this point on, additional worker threads are created
836     ** as they are needed by existing worker threads.
837     */
838     rv = CreateWorker(server, &server->pool);
839     TEST_ASSERT(PR_SUCCESS == rv);
840 
841     /*
842     ** From here on this thread is merely hanging around as the contact
843     ** point for the main test driver. It's just waiting for the driver
844     ** to declare the test complete.
845     */
846     TEST_LOG(
847         cltsrv_log_file, TEST_LOG_VERBOSE,
848         ("\tServer(0x%p): waiting for state change\n", me));
849 
850     PR_Lock(server->ml);
851     while ((cs_run == server->state) && !Aborted(rv))
852     {
853         rv = PR_WaitCondVar(server->stateChange, PR_INTERVAL_NO_TIMEOUT);
854     }
855     PR_Unlock(server->ml);
856     PR_ClearInterrupt();
857 
858     TEST_LOG(
859         cltsrv_log_file, TEST_LOG_INFO,
860         ("\tServer(0x%p): shutting down workers\n", me));
861 
862     /*
863     ** Get all the worker threads to exit. They know how to
864     ** clean up after themselves, so this is just a matter of
865     ** waiting for clorine in the pool to take effect. During
866     ** this stage we're ignoring interrupts.
867     */
868     server->workers.minimum = server->workers.maximum = 0;
869 
870     PR_Lock(server->ml);
871     while (!PR_CLIST_IS_EMPTY(&server->list))
872     {
873         PRCList *head = PR_LIST_HEAD(&server->list);
874         CSWorker_t *worker = (CSWorker_t*)head;
875         TEST_LOG(
876             cltsrv_log_file, TEST_LOG_VERBOSE,
877             ("\tServer(0x%p): interrupting worker(0x%p)\n", me, worker));
878         rv = PR_Interrupt(worker->thread);
879         TEST_ASSERT(PR_SUCCESS == rv);
880         PR_REMOVE_AND_INIT_LINK(head);
881     }
882 
883     while (server->pool.workers > 0)
884     {
885         TEST_LOG(
886             cltsrv_log_file, TEST_LOG_NOTICE,
887             ("\tServer(0x%p): waiting for %u workers to exit\n",
888              me, server->pool.workers));
889         (void)PR_WaitCondVar(server->pool.exiting, PR_INTERVAL_NO_TIMEOUT);
890     }
891 
892     server->state = cs_exit;
893     PR_NotifyCondVar(server->stateChange);
894     PR_Unlock(server->ml);
895 
896     TEST_LOG(
897         cltsrv_log_file, TEST_LOG_ALWAYS,
898         ("\tServer(0x%p): stopped after %u operations and %u bytes\n",
899          me, server->operations, server->bytesTransferred));
900 
901     if (NULL != server->listener) {
902         PR_Close(server->listener);
903     }
904     server->stopped = PR_IntervalNow();
905 
906 }  /* Server */
907 
WaitForCompletion(PRIntn execution)908 static void WaitForCompletion(PRIntn execution)
909 {
910     while (execution > 0)
911     {
912         PRIntn dally = (execution > 30) ? 30 : execution;
913         PR_Sleep(PR_SecondsToInterval(dally));
914         if (pthread_stats) {
915             PT_FPrintStats(debug_out, "\nPThread Statistics\n");
916         }
917         execution -= dally;
918     }
919 }  /* WaitForCompletion */
920 
Help(void)921 static void Help(void)
922 {
923     PR_fprintf(debug_out, "cltsrv test program usage:\n");
924     PR_fprintf(debug_out, "\t-a <n>       threads allowed in accept        (5)\n");
925     PR_fprintf(debug_out, "\t-b <n>       backlock for listen              (5)\n");
926     PR_fprintf(debug_out, "\t-c <threads> number of clients to create      (1)\n");
927     PR_fprintf(debug_out, "\t-f <low>     low water mark for fd caching    (0)\n");
928     PR_fprintf(debug_out, "\t-F <high>    high water mark for fd caching   (0)\n");
929     PR_fprintf(debug_out, "\t-w <threads> minimal number of server threads (1)\n");
930     PR_fprintf(debug_out, "\t-W <threads> maximum number of server threads (1)\n");
931     PR_fprintf(debug_out, "\t-e <seconds> duration of the test in seconds  (10)\n");
932     PR_fprintf(debug_out, "\t-s <string>  dsn name of server               (localhost)\n");
933     PR_fprintf(debug_out, "\t-G           use GLOBAL threads               (LOCAL)\n");
934     PR_fprintf(debug_out, "\t-X           use XTP as transport             (TCP)\n");
935     PR_fprintf(debug_out, "\t-6           Use IPv6                         (IPv4)\n");
936     PR_fprintf(debug_out, "\t-v           verbosity (accumulative)         (0)\n");
937     PR_fprintf(debug_out, "\t-p           pthread statistics               (FALSE)\n");
938     PR_fprintf(debug_out, "\t-d           debug mode                       (FALSE)\n");
939     PR_fprintf(debug_out, "\t-h           this message\n");
940 }  /* Help */
941 
IncrementVerbosity(void)942 static Verbosity IncrementVerbosity(void)
943 {
944     PRIntn verboge = (PRIntn)verbosity + 1;
945     return (Verbosity)verboge;
946 }  /* IncrementVerbosity */
947 
main(int argc,char ** argv)948 int main(int argc, char** argv)
949 {
950     PRUintn index;
951     PRBool boolean;
952     CSClient_t *client;
953     PRStatus rv, joinStatus;
954     CSServer_t *server = NULL;
955 
956     PRUintn backlog = DEFAULT_BACKLOG;
957     PRUintn clients = DEFAULT_CLIENTS;
958     const char *serverName = DEFAULT_SERVER;
959     PRBool serverIsLocal = PR_TRUE;
960     PRUintn accepting = ALLOWED_IN_ACCEPT;
961     PRUintn workersMin = DEFAULT_WORKERS_MIN;
962     PRUintn workersMax = DEFAULT_WORKERS_MAX;
963     PRIntn execution = DEFAULT_EXECUTION_TIME;
964     PRIntn low = DEFAULT_LOW, high = DEFAULT_HIGH;
965 
966     /*
967      * -G           use global threads
968      * -a <n>       threads allowed in accept
969      * -b <n>       backlock for listen
970      * -c <threads> number of clients to create
971      * -f <low>     low water mark for caching FDs
972      * -F <high>    high water mark for caching FDs
973      * -w <threads> minimal number of server threads
974      * -W <threads> maximum number of server threads
975      * -e <seconds> duration of the test in seconds
976      * -s <string>  dsn name of server (implies no server here)
977      * -v           verbosity
978      */
979 
980     PLOptStatus os;
981     PLOptState *opt = PL_CreateOptState(argc, argv, "GX6b:a:c:f:F:w:W:e:s:vdhp");
982 
983     debug_out = PR_GetSpecialFD(PR_StandardError);
984 
985     while (PL_OPT_EOL != (os = PL_GetNextOpt(opt)))
986     {
987         if (PL_OPT_BAD == os) {
988             continue;
989         }
990         switch (opt->option)
991         {
992             case 'G':  /* use global threads */
993                 thread_scope = PR_GLOBAL_THREAD;
994                 break;
995             case 'X':  /* use XTP as transport */
996                 protocol = 36;
997                 break;
998             case '6':  /* Use IPv6 */
999                 domain = PR_AF_INET6;
1000                 break;
1001             case 'a':  /* the value for accepting */
1002                 accepting = atoi(opt->value);
1003                 break;
1004             case 'b':  /* the value for backlock */
1005                 backlog = atoi(opt->value);
1006                 break;
1007             case 'c':  /* number of client threads */
1008                 clients = atoi(opt->value);
1009                 break;
1010             case 'f':  /* low water fd cache */
1011                 low = atoi(opt->value);
1012                 break;
1013             case 'F':  /* low water fd cache */
1014                 high = atoi(opt->value);
1015                 break;
1016             case 'w':  /* minimum server worker threads */
1017                 workersMin = atoi(opt->value);
1018                 break;
1019             case 'W':  /* maximum server worker threads */
1020                 workersMax = atoi(opt->value);
1021                 break;
1022             case 'e':  /* program execution time in seconds */
1023                 execution = atoi(opt->value);
1024                 break;
1025             case 's':  /* server's address */
1026                 serverName = opt->value;
1027                 break;
1028             case 'v':  /* verbosity */
1029                 verbosity = IncrementVerbosity();
1030                 break;
1031             case 'd':  /* debug mode */
1032                 debug_mode = PR_TRUE;
1033                 break;
1034             case 'p':  /* pthread mode */
1035                 pthread_stats = PR_TRUE;
1036                 break;
1037             case 'h':
1038             default:
1039                 Help();
1040                 return 2;
1041         }
1042     }
1043     PL_DestroyOptState(opt);
1044 
1045     if (0 != PL_strcmp(serverName, DEFAULT_SERVER)) {
1046         serverIsLocal = PR_FALSE;
1047     }
1048     if (0 == execution) {
1049         execution = DEFAULT_EXECUTION_TIME;
1050     }
1051     if (0 == workersMax) {
1052         workersMax = DEFAULT_WORKERS_MAX;
1053     }
1054     if (0 == workersMin) {
1055         workersMin = DEFAULT_WORKERS_MIN;
1056     }
1057     if (0 == accepting) {
1058         accepting = ALLOWED_IN_ACCEPT;
1059     }
1060     if (0 == backlog) {
1061         backlog = DEFAULT_BACKLOG;
1062     }
1063 
1064     if (workersMin > accepting) {
1065         accepting = workersMin;
1066     }
1067 
1068     PR_STDIO_INIT();
1069     TimeOfDayMessage("Client/Server started at", PR_GetCurrentThread());
1070 
1071     cltsrv_log_file = PR_NewLogModule("cltsrv_log");
1072     MY_ASSERT(NULL != cltsrv_log_file);
1073     boolean = PR_SetLogFile("cltsrv.log");
1074     MY_ASSERT(boolean);
1075 
1076     rv = PR_SetFDCacheSize(low, high);
1077     PR_ASSERT(PR_SUCCESS == rv);
1078 
1079     if (serverIsLocal)
1080     {
1081         /* Establish the server */
1082         TEST_LOG(
1083             cltsrv_log_file, TEST_LOG_INFO,
1084             ("main(0x%p): starting server\n", PR_GetCurrentThread()));
1085 
1086         server = PR_NEWZAP(CSServer_t);
1087         PR_INIT_CLIST(&server->list);
1088         server->state = cs_init;
1089         server->ml = PR_NewLock();
1090         server->backlog = backlog;
1091         server->port = DEFAULT_PORT;
1092         server->workers.minimum = workersMin;
1093         server->workers.maximum = workersMax;
1094         server->workers.accepting = accepting;
1095         server->stateChange = PR_NewCondVar(server->ml);
1096         server->pool.exiting = PR_NewCondVar(server->ml);
1097         server->pool.acceptComplete = PR_NewCondVar(server->ml);
1098 
1099         TEST_LOG(
1100             cltsrv_log_file, TEST_LOG_NOTICE,
1101             ("main(0x%p): creating server thread\n", PR_GetCurrentThread()));
1102 
1103         server->thread = PR_CreateThread(
1104                              PR_USER_THREAD, Server, server, PR_PRIORITY_HIGH,
1105                              thread_scope, PR_JOINABLE_THREAD, 0);
1106         TEST_ASSERT(NULL != server->thread);
1107 
1108         TEST_LOG(
1109             cltsrv_log_file, TEST_LOG_VERBOSE,
1110             ("main(0x%p): waiting for server init\n", PR_GetCurrentThread()));
1111 
1112         PR_Lock(server->ml);
1113         while (server->state == cs_init) {
1114             PR_WaitCondVar(server->stateChange, PR_INTERVAL_NO_TIMEOUT);
1115         }
1116         PR_Unlock(server->ml);
1117 
1118         TEST_LOG(
1119             cltsrv_log_file, TEST_LOG_VERBOSE,
1120             ("main(0x%p): server init complete (port #%d)\n",
1121              PR_GetCurrentThread(), server->port));
1122     }
1123 
1124     if (clients != 0)
1125     {
1126         /* Create all of the clients */
1127         PRHostEnt host;
1128         char buffer[BUFFER_SIZE];
1129         client = (CSClient_t*)PR_CALLOC(clients * sizeof(CSClient_t));
1130 
1131         TEST_LOG(
1132             cltsrv_log_file, TEST_LOG_VERBOSE,
1133             ("main(0x%p): creating %d client threads\n",
1134              PR_GetCurrentThread(), clients));
1135 
1136         if (!serverIsLocal)
1137         {
1138             rv = PR_GetHostByName(serverName, buffer, BUFFER_SIZE, &host);
1139             if (PR_SUCCESS != rv)
1140             {
1141                 PL_FPrintError(PR_STDERR, "PR_GetHostByName");
1142                 return 2;
1143             }
1144         }
1145 
1146         for (index = 0; index < clients; ++index)
1147         {
1148             client[index].state = cs_init;
1149             client[index].ml = PR_NewLock();
1150             if (serverIsLocal)
1151             {
1152                 if (PR_AF_INET6 != domain) {
1153                     TEST_LOG(cltsrv_log_file, TEST_LOG_ALWAYS,
1154                              ("loopback client ip port %s\n", DEFAULT_PORT));
1155                     (void)PR_InitializeNetAddr(
1156                         PR_IpAddrLoopback, DEFAULT_PORT,
1157                         &client[index].serverAddress);
1158                 }
1159                 else {
1160                     TEST_LOG(cltsrv_log_file, TEST_LOG_ALWAYS,
1161                              ("loopback client ipv6 port %s\n", DEFAULT_PORT));
1162                     rv = PR_SetNetAddr(PR_IpAddrLoopback, PR_AF_INET6,
1163                                        DEFAULT_PORT, &client[index].serverAddress);
1164                 }
1165             }
1166             else
1167             {
1168                 TEST_LOG(cltsrv_log_file, TEST_LOG_ALWAYS,
1169                          ("client enumerate port %s\n", DEFAULT_PORT));
1170                 (void)PR_EnumerateHostEnt(
1171                     0, &host, DEFAULT_PORT, &client[index].serverAddress);
1172             }
1173             client[index].stateChange = PR_NewCondVar(client[index].ml);
1174             TEST_LOG(
1175                 cltsrv_log_file, TEST_LOG_INFO,
1176                 ("main(0x%p): creating client threads\n", PR_GetCurrentThread()));
1177             client[index].thread = PR_CreateThread(
1178                                        PR_USER_THREAD, Client, &client[index], PR_PRIORITY_NORMAL,
1179                                        thread_scope, PR_JOINABLE_THREAD, 0);
1180             TEST_ASSERT(NULL != client[index].thread);
1181             PR_Lock(client[index].ml);
1182             while (cs_init == client[index].state) {
1183                 PR_WaitCondVar(client[index].stateChange, PR_INTERVAL_NO_TIMEOUT);
1184             }
1185             PR_Unlock(client[index].ml);
1186         }
1187     }
1188 
1189     /* Then just let them go at it for a bit */
1190     TEST_LOG(
1191         cltsrv_log_file, TEST_LOG_ALWAYS,
1192         ("main(0x%p): waiting for execution interval (%d seconds)\n",
1193          PR_GetCurrentThread(), execution));
1194 
1195     WaitForCompletion(execution);
1196 
1197     TimeOfDayMessage("Shutting down", PR_GetCurrentThread());
1198 
1199     if (clients != 0)
1200     {
1201         for (index = 0; index < clients; ++index)
1202         {
1203             TEST_LOG(cltsrv_log_file, TEST_LOG_STATUS,
1204                      ("main(0x%p): notifying client(0x%p) to stop\n",
1205                       PR_GetCurrentThread(), client[index].thread));
1206 
1207             PR_Lock(client[index].ml);
1208             if (cs_run == client[index].state)
1209             {
1210                 client[index].state = cs_stop;
1211                 PR_Interrupt(client[index].thread);
1212                 while (cs_stop == client[index].state)
1213                     PR_WaitCondVar(
1214                         client[index].stateChange, PR_INTERVAL_NO_TIMEOUT);
1215             }
1216             PR_Unlock(client[index].ml);
1217 
1218             TEST_LOG(cltsrv_log_file, TEST_LOG_VERBOSE,
1219                      ("main(0x%p): joining client(0x%p)\n",
1220                       PR_GetCurrentThread(), client[index].thread));
1221 
1222             joinStatus = PR_JoinThread(client[index].thread);
1223             TEST_ASSERT(PR_SUCCESS == joinStatus);
1224             PR_DestroyCondVar(client[index].stateChange);
1225             PR_DestroyLock(client[index].ml);
1226         }
1227         PR_DELETE(client);
1228     }
1229 
1230     if (NULL != server)
1231     {
1232         /* All clients joined - retrieve the server */
1233         TEST_LOG(
1234             cltsrv_log_file, TEST_LOG_NOTICE,
1235             ("main(0x%p): notifying server(0x%p) to stop\n",
1236              PR_GetCurrentThread(), server->thread));
1237 
1238         PR_Lock(server->ml);
1239         server->state = cs_stop;
1240         PR_Interrupt(server->thread);
1241         while (cs_exit != server->state) {
1242             PR_WaitCondVar(server->stateChange, PR_INTERVAL_NO_TIMEOUT);
1243         }
1244         PR_Unlock(server->ml);
1245 
1246         TEST_LOG(
1247             cltsrv_log_file, TEST_LOG_NOTICE,
1248             ("main(0x%p): joining server(0x%p)\n",
1249              PR_GetCurrentThread(), server->thread));
1250         joinStatus = PR_JoinThread(server->thread);
1251         TEST_ASSERT(PR_SUCCESS == joinStatus);
1252 
1253         PR_DestroyCondVar(server->stateChange);
1254         PR_DestroyCondVar(server->pool.exiting);
1255         PR_DestroyCondVar(server->pool.acceptComplete);
1256         PR_DestroyLock(server->ml);
1257         PR_DELETE(server);
1258     }
1259 
1260     TEST_LOG(
1261         cltsrv_log_file, TEST_LOG_ALWAYS,
1262         ("main(0x%p): test complete\n", PR_GetCurrentThread()));
1263 
1264     PT_FPrintStats(debug_out, "\nPThread Statistics\n");
1265 
1266     TimeOfDayMessage("Test exiting at", PR_GetCurrentThread());
1267     PR_Cleanup();
1268     return 0;
1269 }  /* main */
1270 
1271 /* cltsrv.c */
1272