625 lines
16 KiB
C
625 lines
16 KiB
C
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/***********************************************************************
|
|
**
|
|
** This server simulates a server running in loopback mode.
|
|
**
|
|
** The idea is that a single server is created. The server initially creates
|
|
** a number of worker threads. Then, with the server running, a number of
|
|
** clients are created which start requesting service from the server.
|
|
**
|
|
**
|
|
** Modification History:
|
|
** 19-May-97 AGarcia- Converted the test to accomodate the debug_mode flag.
|
|
** The debug mode will print all of the printfs associated with this
|
|
*test.
|
|
** The regress mode will be the default mode. Since the regress tool
|
|
*limits
|
|
** the output to a one line status:PASS or FAIL,all of the printf
|
|
*statements
|
|
** have been handled with an if (debug_mode) statement.
|
|
***********************************************************************/
|
|
|
|
/***********************************************************************
|
|
** Includes
|
|
***********************************************************************/
|
|
/* Used to get the command line option */
|
|
#include "plgetopt.h"
|
|
|
|
#include "nspr.h"
|
|
#include "pprthred.h"
|
|
|
|
#include <string.h>
|
|
|
|
#define PORT 15004
|
|
#define THREAD_STACKSIZE 0
|
|
|
|
#define PASS 0
|
|
#define FAIL 1
|
|
static int debug_mode = 0;
|
|
static int failed_already = 0;
|
|
|
|
static int _iterations = 1000;
|
|
static int _clients = 1;
|
|
static int _client_data = 250;
|
|
static int _server_data = (8 * 1024);
|
|
|
|
static PRThreadScope ServerScope, ClientScope;
|
|
|
|
#define SERVER "Server"
|
|
#define MAIN "Main"
|
|
|
|
#define SERVER_STATE_STARTUP 0
|
|
#define SERVER_STATE_READY 1
|
|
#define SERVER_STATE_DYING 2
|
|
#define SERVER_STATE_DEAD 4
|
|
int ServerState;
|
|
PRLock* ServerStateCVLock;
|
|
PRCondVar* ServerStateCV;
|
|
|
|
#undef DEBUGPRINTS
|
|
#ifdef DEBUGPRINTS
|
|
# define DPRINTF printf
|
|
#else
|
|
# define DPRINTF
|
|
#endif
|
|
|
|
/***********************************************************************
|
|
** PRIVATE FUNCTION: Test_Result
|
|
** DESCRIPTION: Used in conjunction with the regress tool, prints out the
|
|
** status of the test case.
|
|
** INPUTS: PASS/FAIL
|
|
** OUTPUTS: None
|
|
** RETURN: None
|
|
** SIDE EFFECTS:
|
|
**
|
|
** RESTRICTIONS:
|
|
** None
|
|
** MEMORY: NA
|
|
** ALGORITHM: Determine what the status is and print accordingly.
|
|
**
|
|
***********************************************************************/
|
|
|
|
static void Test_Result(int result) {
|
|
switch (result) {
|
|
case PASS:
|
|
printf("PASS\n");
|
|
break;
|
|
case FAIL:
|
|
printf("FAIL\n");
|
|
failed_already = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void do_work(void);
|
|
|
|
/* --- Server state functions --------------------------------------------- */
|
|
void SetServerState(char* waiter, PRInt32 state) {
|
|
PR_Lock(ServerStateCVLock);
|
|
ServerState = state;
|
|
PR_NotifyCondVar(ServerStateCV);
|
|
|
|
if (debug_mode) {
|
|
DPRINTF("\t%s changed state to %d\n", waiter, state);
|
|
}
|
|
|
|
PR_Unlock(ServerStateCVLock);
|
|
}
|
|
|
|
int WaitServerState(char* waiter, PRInt32 state) {
|
|
PRInt32 rv;
|
|
|
|
PR_Lock(ServerStateCVLock);
|
|
|
|
if (debug_mode) {
|
|
DPRINTF("\t%s waiting for state %d\n", waiter, state);
|
|
}
|
|
|
|
while (!(ServerState & state)) {
|
|
PR_WaitCondVar(ServerStateCV, PR_INTERVAL_NO_TIMEOUT);
|
|
}
|
|
rv = ServerState;
|
|
|
|
if (debug_mode)
|
|
DPRINTF("\t%s resuming from wait for state %d; state now %d\n", waiter,
|
|
state, ServerState);
|
|
PR_Unlock(ServerStateCVLock);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* --- Server Functions ------------------------------------------- */
|
|
|
|
PRLock* workerThreadsLock;
|
|
PRInt32 workerThreads;
|
|
PRInt32 workerThreadsBusy;
|
|
|
|
void WorkerThreadFunc(void* _listenSock) {
|
|
PRFileDesc* listenSock = (PRFileDesc*)_listenSock;
|
|
PRInt32 bytesRead;
|
|
PRInt32 bytesWritten;
|
|
char* dataBuf;
|
|
char* sendBuf;
|
|
|
|
if (debug_mode)
|
|
DPRINTF("\tServer buffer is %d bytes; %d data, %d netaddrs\n",
|
|
_client_data + (2 * sizeof(PRNetAddr)) + 32, _client_data,
|
|
(2 * sizeof(PRNetAddr)) + 32);
|
|
dataBuf = (char*)PR_MALLOC(_client_data + 2 * sizeof(PRNetAddr) + 32);
|
|
if (!dataBuf)
|
|
if (debug_mode) {
|
|
printf("\tServer could not malloc space!?\n");
|
|
}
|
|
sendBuf = (char*)PR_MALLOC(_server_data * sizeof(char));
|
|
if (!sendBuf)
|
|
if (debug_mode) {
|
|
printf("\tServer could not malloc space!?\n");
|
|
}
|
|
|
|
if (debug_mode) {
|
|
DPRINTF("\tServer worker thread running\n");
|
|
}
|
|
|
|
while (1) {
|
|
PRInt32 bytesToRead = _client_data;
|
|
PRInt32 bytesToWrite = _server_data;
|
|
PRFileDesc* newSock;
|
|
PRNetAddr* rAddr;
|
|
PRInt32 loops = 0;
|
|
|
|
loops++;
|
|
|
|
if (debug_mode) {
|
|
DPRINTF("\tServer thread going into accept\n");
|
|
}
|
|
|
|
bytesRead = PR_AcceptRead(listenSock, &newSock, &rAddr, dataBuf,
|
|
bytesToRead, PR_INTERVAL_NO_TIMEOUT);
|
|
|
|
if (bytesRead < 0) {
|
|
if (debug_mode) {
|
|
printf("\tServer error in accept (%d)\n", bytesRead);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (debug_mode) {
|
|
DPRINTF("\tServer accepted connection (%d bytes)\n", bytesRead);
|
|
}
|
|
|
|
PR_AtomicIncrement(&workerThreadsBusy);
|
|
if (workerThreadsBusy == workerThreads) {
|
|
PR_Lock(workerThreadsLock);
|
|
if (workerThreadsBusy == workerThreads) {
|
|
PRThread* WorkerThread;
|
|
|
|
WorkerThread = PR_CreateThread(
|
|
PR_SYSTEM_THREAD, WorkerThreadFunc, listenSock, PR_PRIORITY_NORMAL,
|
|
ServerScope, PR_UNJOINABLE_THREAD, THREAD_STACKSIZE);
|
|
|
|
if (!WorkerThread) {
|
|
if (debug_mode) {
|
|
printf("Error creating client thread %d\n", workerThreads);
|
|
}
|
|
} else {
|
|
PR_AtomicIncrement(&workerThreads);
|
|
if (debug_mode) {
|
|
DPRINTF("\tServer creates worker (%d)\n", workerThreads);
|
|
}
|
|
}
|
|
}
|
|
PR_Unlock(workerThreadsLock);
|
|
}
|
|
|
|
bytesToRead -= bytesRead;
|
|
while (bytesToRead) {
|
|
bytesRead =
|
|
PR_Recv(newSock, dataBuf, bytesToRead, 0, PR_INTERVAL_NO_TIMEOUT);
|
|
if (bytesRead < 0) {
|
|
if (debug_mode) {
|
|
printf("\tServer error receiving data (%d)\n", bytesRead);
|
|
}
|
|
continue;
|
|
}
|
|
if (debug_mode) {
|
|
DPRINTF("\tServer received %d bytes\n", bytesRead);
|
|
}
|
|
}
|
|
|
|
bytesWritten =
|
|
PR_Send(newSock, sendBuf, bytesToWrite, 0, PR_INTERVAL_NO_TIMEOUT);
|
|
if (bytesWritten != _server_data) {
|
|
if (debug_mode)
|
|
printf("\tError sending data to client (%d, %d)\n", bytesWritten,
|
|
PR_GetOSError());
|
|
} else {
|
|
if (debug_mode) {
|
|
DPRINTF("\tServer sent %d bytes\n", bytesWritten);
|
|
}
|
|
}
|
|
|
|
PR_Close(newSock);
|
|
PR_AtomicDecrement(&workerThreadsBusy);
|
|
}
|
|
}
|
|
|
|
PRFileDesc* ServerSetup(void) {
|
|
PRFileDesc* listenSocket;
|
|
PRSocketOptionData sockOpt;
|
|
PRNetAddr serverAddr;
|
|
PRThread* WorkerThread;
|
|
|
|
if ((listenSocket = PR_NewTCPSocket()) == NULL) {
|
|
if (debug_mode) {
|
|
printf("\tServer error creating listen socket\n");
|
|
} else {
|
|
Test_Result(FAIL);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
sockOpt.option = PR_SockOpt_Reuseaddr;
|
|
sockOpt.value.reuse_addr = PR_TRUE;
|
|
if (PR_SetSocketOption(listenSocket, &sockOpt) != PR_SUCCESS) {
|
|
if (debug_mode)
|
|
printf("\tServer error setting socket option: OS error %d\n",
|
|
PR_GetOSError());
|
|
else {
|
|
Test_Result(FAIL);
|
|
}
|
|
PR_Close(listenSocket);
|
|
return NULL;
|
|
}
|
|
|
|
memset(&serverAddr, 0, sizeof(PRNetAddr));
|
|
serverAddr.inet.family = PR_AF_INET;
|
|
serverAddr.inet.port = PR_htons(PORT);
|
|
serverAddr.inet.ip = PR_htonl(PR_INADDR_ANY);
|
|
|
|
if (PR_Bind(listenSocket, &serverAddr) != PR_SUCCESS) {
|
|
if (debug_mode)
|
|
printf("\tServer error binding to server address: OS error %d\n",
|
|
PR_GetOSError());
|
|
else {
|
|
Test_Result(FAIL);
|
|
}
|
|
PR_Close(listenSocket);
|
|
return NULL;
|
|
}
|
|
|
|
if (PR_Listen(listenSocket, 128) != PR_SUCCESS) {
|
|
if (debug_mode) {
|
|
printf("\tServer error listening to server socket\n");
|
|
} else {
|
|
Test_Result(FAIL);
|
|
}
|
|
PR_Close(listenSocket);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Create Clients */
|
|
workerThreads = 0;
|
|
workerThreadsBusy = 0;
|
|
|
|
workerThreadsLock = PR_NewLock();
|
|
|
|
WorkerThread = PR_CreateThread(PR_SYSTEM_THREAD, WorkerThreadFunc,
|
|
listenSocket, PR_PRIORITY_NORMAL, ServerScope,
|
|
PR_UNJOINABLE_THREAD, THREAD_STACKSIZE);
|
|
|
|
if (!WorkerThread) {
|
|
if (debug_mode) {
|
|
printf("error creating working thread\n");
|
|
}
|
|
PR_Close(listenSocket);
|
|
return NULL;
|
|
}
|
|
PR_AtomicIncrement(&workerThreads);
|
|
if (debug_mode) {
|
|
DPRINTF("\tServer created primordial worker thread\n");
|
|
}
|
|
|
|
return listenSocket;
|
|
}
|
|
|
|
/* The main server loop */
|
|
void ServerThreadFunc(void* unused) {
|
|
PRFileDesc* listenSocket;
|
|
|
|
/* Do setup */
|
|
listenSocket = ServerSetup();
|
|
|
|
if (!listenSocket) {
|
|
SetServerState(SERVER, SERVER_STATE_DEAD);
|
|
} else {
|
|
if (debug_mode) {
|
|
DPRINTF("\tServer up\n");
|
|
}
|
|
|
|
/* Tell clients they can start now. */
|
|
SetServerState(SERVER, SERVER_STATE_READY);
|
|
|
|
/* Now wait for server death signal */
|
|
WaitServerState(SERVER, SERVER_STATE_DYING);
|
|
|
|
/* Cleanup */
|
|
SetServerState(SERVER, SERVER_STATE_DEAD);
|
|
}
|
|
}
|
|
|
|
/* --- Client Functions ------------------------------------------- */
|
|
|
|
PRInt32 numRequests;
|
|
PRInt32 numClients;
|
|
PRMonitor* clientMonitor;
|
|
|
|
void ClientThreadFunc(void* unused) {
|
|
PRNetAddr serverAddr;
|
|
PRFileDesc* clientSocket;
|
|
char* sendBuf;
|
|
char* recvBuf;
|
|
PRInt32 rv;
|
|
PRInt32 bytesNeeded;
|
|
|
|
sendBuf = (char*)PR_MALLOC(_client_data * sizeof(char));
|
|
if (!sendBuf)
|
|
if (debug_mode) {
|
|
printf("\tClient could not malloc space!?\n");
|
|
}
|
|
recvBuf = (char*)PR_MALLOC(_server_data * sizeof(char));
|
|
if (!recvBuf)
|
|
if (debug_mode) {
|
|
printf("\tClient could not malloc space!?\n");
|
|
}
|
|
|
|
memset(&serverAddr, 0, sizeof(PRNetAddr));
|
|
serverAddr.inet.family = PR_AF_INET;
|
|
serverAddr.inet.port = PR_htons(PORT);
|
|
serverAddr.inet.ip = PR_htonl(PR_INADDR_LOOPBACK);
|
|
|
|
while (numRequests > 0) {
|
|
if ((numRequests % 10) == 0)
|
|
if (debug_mode) {
|
|
printf(".");
|
|
}
|
|
if (debug_mode) {
|
|
DPRINTF("\tClient starting request %d\n", numRequests);
|
|
}
|
|
|
|
clientSocket = PR_NewTCPSocket();
|
|
if (!clientSocket) {
|
|
if (debug_mode)
|
|
printf("Client error creating socket: OS error %d\n", PR_GetOSError());
|
|
continue;
|
|
}
|
|
|
|
if (debug_mode) {
|
|
DPRINTF("\tClient connecting\n");
|
|
}
|
|
|
|
rv = PR_Connect(clientSocket, &serverAddr, PR_INTERVAL_NO_TIMEOUT);
|
|
if (!clientSocket) {
|
|
if (debug_mode) {
|
|
printf("\tClient error connecting\n");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (debug_mode) {
|
|
DPRINTF("\tClient connected\n");
|
|
}
|
|
|
|
rv =
|
|
PR_Send(clientSocket, sendBuf, _client_data, 0, PR_INTERVAL_NO_TIMEOUT);
|
|
if (rv != _client_data) {
|
|
if (debug_mode) {
|
|
printf("Client error sending data (%d)\n", rv);
|
|
}
|
|
PR_Close(clientSocket);
|
|
continue;
|
|
}
|
|
|
|
if (debug_mode) {
|
|
DPRINTF("\tClient sent %d bytes\n", rv);
|
|
}
|
|
|
|
bytesNeeded = _server_data;
|
|
while (bytesNeeded) {
|
|
rv = PR_Recv(clientSocket, recvBuf, bytesNeeded, 0,
|
|
PR_INTERVAL_NO_TIMEOUT);
|
|
if (rv <= 0) {
|
|
if (debug_mode)
|
|
printf("Client error receiving data (%d) (%d/%d)\n", rv,
|
|
(_server_data - bytesNeeded), _server_data);
|
|
break;
|
|
}
|
|
if (debug_mode) {
|
|
DPRINTF("\tClient received %d bytes; need %d more\n", rv,
|
|
bytesNeeded - rv);
|
|
}
|
|
bytesNeeded -= rv;
|
|
}
|
|
|
|
PR_Close(clientSocket);
|
|
|
|
PR_AtomicDecrement(&numRequests);
|
|
}
|
|
|
|
PR_EnterMonitor(clientMonitor);
|
|
--numClients;
|
|
PR_Notify(clientMonitor);
|
|
PR_ExitMonitor(clientMonitor);
|
|
|
|
PR_DELETE(sendBuf);
|
|
PR_DELETE(recvBuf);
|
|
}
|
|
|
|
void RunClients(void) {
|
|
PRInt32 index;
|
|
|
|
numRequests = _iterations;
|
|
numClients = _clients;
|
|
clientMonitor = PR_NewMonitor();
|
|
|
|
for (index = 0; index < _clients; index++) {
|
|
PRThread* clientThread;
|
|
|
|
clientThread = PR_CreateThread(PR_USER_THREAD, ClientThreadFunc, NULL,
|
|
PR_PRIORITY_NORMAL, ClientScope,
|
|
PR_UNJOINABLE_THREAD, THREAD_STACKSIZE);
|
|
|
|
if (!clientThread) {
|
|
if (debug_mode) {
|
|
printf("\terror creating client thread %d\n", index);
|
|
}
|
|
} else if (debug_mode) {
|
|
DPRINTF("\tMain created client %d/%d\n", index + 1, _clients);
|
|
}
|
|
}
|
|
|
|
PR_EnterMonitor(clientMonitor);
|
|
while (numClients) {
|
|
PR_Wait(clientMonitor, PR_INTERVAL_NO_TIMEOUT);
|
|
}
|
|
PR_ExitMonitor(clientMonitor);
|
|
}
|
|
|
|
/* --- Main Function ---------------------------------------------- */
|
|
|
|
static void do_work() {
|
|
PRThread* ServerThread;
|
|
PRInt32 state;
|
|
|
|
SetServerState(MAIN, SERVER_STATE_STARTUP);
|
|
ServerThread = PR_CreateThread(PR_USER_THREAD, ServerThreadFunc, NULL,
|
|
PR_PRIORITY_NORMAL, ServerScope,
|
|
PR_JOINABLE_THREAD, THREAD_STACKSIZE);
|
|
if (!ServerThread) {
|
|
if (debug_mode) {
|
|
printf("error creating main server thread\n");
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Wait for server to be ready */
|
|
state = WaitServerState(MAIN, SERVER_STATE_READY | SERVER_STATE_DEAD);
|
|
|
|
if (!(state & SERVER_STATE_DEAD)) {
|
|
/* Run Test Clients */
|
|
RunClients();
|
|
|
|
/* Send death signal to server */
|
|
SetServerState(MAIN, SERVER_STATE_DYING);
|
|
}
|
|
|
|
PR_JoinThread(ServerThread);
|
|
}
|
|
|
|
static void do_workUU(void) {
|
|
ServerScope = PR_LOCAL_THREAD;
|
|
ClientScope = PR_LOCAL_THREAD;
|
|
do_work();
|
|
}
|
|
|
|
static void do_workUK(void) {
|
|
ServerScope = PR_LOCAL_THREAD;
|
|
ClientScope = PR_GLOBAL_THREAD;
|
|
do_work();
|
|
}
|
|
|
|
static void do_workKU(void) {
|
|
ServerScope = PR_GLOBAL_THREAD;
|
|
ClientScope = PR_LOCAL_THREAD;
|
|
do_work();
|
|
}
|
|
|
|
static void do_workKK(void) {
|
|
ServerScope = PR_GLOBAL_THREAD;
|
|
ClientScope = PR_GLOBAL_THREAD;
|
|
do_work();
|
|
}
|
|
|
|
static void Measure(void (*func)(void), const char* msg) {
|
|
PRIntervalTime start, stop;
|
|
double d;
|
|
|
|
start = PR_IntervalNow();
|
|
(*func)();
|
|
stop = PR_IntervalNow();
|
|
|
|
d = (double)PR_IntervalToMicroseconds(stop - start);
|
|
|
|
if (debug_mode) {
|
|
printf("\n%40s: %6.2f usec\n", msg, d / _iterations);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
/* The command line argument: -d is used to determine if the test is being run
|
|
in debug mode. The regress tool requires only one line output:PASS or FAIL.
|
|
All of the printfs associated with this test has been handled with a if
|
|
(debug_mode) test. Usage: test_name -d
|
|
*/
|
|
PLOptStatus os;
|
|
PLOptState* opt = PL_CreateOptState(argc, argv, "d");
|
|
while (PL_OPT_EOL != (os = PL_GetNextOpt(opt))) {
|
|
if (PL_OPT_BAD == os) {
|
|
continue;
|
|
}
|
|
switch (opt->option) {
|
|
case 'd': /* debug mode */
|
|
debug_mode = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
PL_DestroyOptState(opt);
|
|
|
|
/* main test */
|
|
if (debug_mode) {
|
|
printf("Enter number of iterations: \n");
|
|
scanf("%d", &_iterations);
|
|
printf("Enter number of clients : \n");
|
|
scanf("%d", &_clients);
|
|
printf("Enter size of client data : \n");
|
|
scanf("%d", &_client_data);
|
|
printf("Enter size of server data : \n");
|
|
scanf("%d", &_server_data);
|
|
} else {
|
|
_iterations = 10;
|
|
_clients = 1;
|
|
_client_data = 10;
|
|
_server_data = 10;
|
|
}
|
|
|
|
if (debug_mode) {
|
|
printf("\n\n%d iterations with %d client threads.\n", _iterations,
|
|
_clients);
|
|
printf("Sending %d bytes of client data and %d bytes of server data\n",
|
|
_client_data, _server_data);
|
|
}
|
|
PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
|
|
PR_STDIO_INIT();
|
|
|
|
ServerStateCVLock = PR_NewLock();
|
|
ServerStateCV = PR_NewCondVar(ServerStateCVLock);
|
|
|
|
Measure(do_workUU, "server loop user/user");
|
|
#if 0
|
|
Measure(do_workUK, "server loop user/kernel");
|
|
Measure(do_workKU, "server loop kernel/user");
|
|
Measure(do_workKK, "server loop kernel/kernel");
|
|
#endif
|
|
|
|
PR_Cleanup();
|
|
|
|
return failed_already;
|
|
}
|