michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: ** File: ntioto.c michael@0: ** Description: michael@0: ** This test, ntioto.c, was designed to reproduce a bug reported by NES michael@0: ** on WindowsNT (fibers implementation). NSPR was asserting in ntio.c michael@0: ** after PR_AcceptRead() had timed out. I/O performed subsequent to the michael@0: ** call to PR_AcceptRead() could complete on a CPU other than the one michael@0: ** on which it was started. The assert in ntio.c detected this, then michael@0: ** asserted. michael@0: ** michael@0: ** Design: michael@0: ** This test will fail with an assert in ntio.c if the problem it was michael@0: ** designed to catch occurs. It returns 0 otherwise. michael@0: ** michael@0: ** The main() thread initializes and tears things down. A file is michael@0: ** opened for writing; this file will be written to by AcceptThread() michael@0: ** and JitterThread(). Main() creates a socket for reading, listens michael@0: ** and binds the socket. michael@0: ** michael@0: ** ConnectThread() connects to the socket created by main, then polls michael@0: ** the "state" variable. When state is AllDone, ConnectThread() exits. michael@0: ** michael@0: ** AcceptThread() calls PR_AcceptRead() on the socket. He fully expects michael@0: ** it to time out. After the timeout, AccpetThread() interacts with michael@0: ** JitterThread() via a common condition variable and the state michael@0: ** variable. The two threads ping-pong back and forth, each thread michael@0: ** writes the the file opened by main. This should provoke the michael@0: ** condition reported by NES (if we didn't fix it). michael@0: ** michael@0: ** The failure is not solid. It may fail within a few ping-pongs between michael@0: ** AcceptThread() and JitterThread() or may take a while. The default michael@0: ** iteration count, jitter, is set by DEFAULT_JITTER. This may be michael@0: ** modified at the command line with the -j option. michael@0: ** michael@0: */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: /* michael@0: ** Test harness infrastructure michael@0: */ michael@0: PRLogModuleInfo *lm; michael@0: PRLogModuleLevel msgLevel = PR_LOG_NONE; michael@0: PRIntn debug = 0; michael@0: PRIntn verbose = 0; michael@0: PRUint32 failed_already = 0; michael@0: /* end Test harness infrastructure */ michael@0: michael@0: /* JITTER_DEFAULT: the number of times AcceptThread() and JitterThread() ping-pong */ michael@0: #define JITTER_DEFAULT 100000 michael@0: #define BASE_PORT 9867 michael@0: michael@0: PRIntervalTime timeout; michael@0: PRNetAddr listenAddr; michael@0: PRFileDesc *listenSock; michael@0: PRLock *ml; michael@0: PRCondVar *cv; michael@0: volatile enum { michael@0: RunJitter, michael@0: RunAcceptRead, michael@0: AllDone michael@0: } state = RunAcceptRead; michael@0: PRFileDesc *file1; michael@0: PRIntn iCounter = 0; michael@0: PRIntn jitter = JITTER_DEFAULT; michael@0: PRBool resume = PR_FALSE; michael@0: michael@0: /* michael@0: ** Emit help text for this test michael@0: */ michael@0: static void Help( void ) michael@0: { michael@0: printf("Template: Help(): display your help message(s) here"); michael@0: exit(1); michael@0: } /* end Help() */ michael@0: michael@0: michael@0: /* michael@0: ** static computation of PR_AcceptRead() buffer size. michael@0: */ michael@0: #define ACCEPT_READ_DATASIZE 10 michael@0: #define ACCEPT_READ_BUFSIZE (PR_ACCEPT_READ_BUF_OVERHEAD + ACCEPT_READ_DATASIZE) michael@0: michael@0: static void AcceptThread(void *arg) michael@0: { michael@0: PRIntn bytesRead; michael@0: char dataBuf[ACCEPT_READ_BUFSIZE]; michael@0: PRFileDesc *arSock; michael@0: PRNetAddr *arAddr; michael@0: michael@0: bytesRead = PR_AcceptRead( listenSock, michael@0: &arSock, michael@0: &arAddr, michael@0: dataBuf, michael@0: ACCEPT_READ_DATASIZE, michael@0: PR_SecondsToInterval(1)); michael@0: michael@0: if ( bytesRead == -1 && PR_GetError() == PR_IO_TIMEOUT_ERROR ) { michael@0: if ( debug ) printf("AcceptRead timed out\n"); michael@0: } else { michael@0: if ( debug ) printf("Oops! read: %d, error: %d\n", bytesRead, PR_GetError()); michael@0: } michael@0: michael@0: while( state != AllDone ) { michael@0: PR_Lock( ml ); michael@0: while( state != RunAcceptRead ) michael@0: PR_WaitCondVar( cv, PR_INTERVAL_NO_TIMEOUT ); michael@0: if ( ++iCounter >= jitter ) michael@0: state = AllDone; michael@0: else michael@0: state = RunJitter; michael@0: if ( verbose ) printf("."); michael@0: PR_NotifyCondVar( cv ); michael@0: PR_Unlock( ml ); michael@0: PR_Write( file1, ".", 1 ); michael@0: } michael@0: michael@0: return; michael@0: } /* end AcceptThread() */ michael@0: michael@0: static void JitterThread(void *arg) michael@0: { michael@0: while( state != AllDone ) { michael@0: PR_Lock( ml ); michael@0: while( state != RunJitter && state != AllDone ) michael@0: PR_WaitCondVar( cv, PR_INTERVAL_NO_TIMEOUT ); michael@0: if ( state != AllDone) michael@0: state = RunAcceptRead; michael@0: if ( verbose ) printf("+"); michael@0: PR_NotifyCondVar( cv ); michael@0: PR_Unlock( ml ); michael@0: PR_Write( file1, "+", 1 ); michael@0: } michael@0: return; michael@0: } /* end Goofy() */ michael@0: michael@0: static void ConnectThread( void *arg ) michael@0: { michael@0: PRStatus rv; michael@0: PRFileDesc *clientSock; michael@0: PRNetAddr serverAddress; michael@0: clientSock = PR_NewTCPSocket(); michael@0: michael@0: PR_ASSERT(clientSock); michael@0: michael@0: if ( resume ) { michael@0: if ( debug ) printf("pausing 3 seconds before connect\n"); michael@0: PR_Sleep( PR_SecondsToInterval(3)); michael@0: } michael@0: michael@0: memset(&serverAddress, 0, sizeof(serverAddress)); michael@0: rv = PR_InitializeNetAddr(PR_IpAddrLoopback, BASE_PORT, &serverAddress); michael@0: PR_ASSERT( PR_SUCCESS == rv ); michael@0: rv = PR_Connect( clientSock, michael@0: &serverAddress, michael@0: PR_SecondsToInterval(1)); michael@0: PR_ASSERT( PR_SUCCESS == rv ); michael@0: michael@0: /* that's all we do. ... Wait for the acceptread() to timeout */ michael@0: while( state != AllDone ) michael@0: PR_Sleep( PR_SecondsToInterval(1)); michael@0: return; michael@0: } /* end ConnectThread() */ michael@0: michael@0: michael@0: int main(int argc, char **argv) michael@0: { michael@0: PRThread *tJitter; michael@0: PRThread *tAccept; michael@0: PRThread *tConnect; michael@0: PRStatus rv; michael@0: /* This test if valid for WinNT only! */ michael@0: michael@0: #if !defined(WINNT) michael@0: return 0; michael@0: #endif michael@0: michael@0: { michael@0: /* michael@0: ** Get command line options michael@0: */ michael@0: PLOptStatus os; michael@0: PLOptState *opt = PL_CreateOptState(argc, argv, "hdrvj:"); michael@0: michael@0: while (PL_OPT_EOL != (os = PL_GetNextOpt(opt))) michael@0: { michael@0: if (PL_OPT_BAD == os) continue; michael@0: switch (opt->option) michael@0: { michael@0: case 'd': /* debug */ michael@0: debug = 1; michael@0: msgLevel = PR_LOG_ERROR; michael@0: break; michael@0: case 'v': /* verbose mode */ michael@0: verbose = 1; michael@0: msgLevel = PR_LOG_DEBUG; michael@0: break; michael@0: case 'j': michael@0: jitter = atoi(opt->value); michael@0: if ( jitter == 0) michael@0: jitter = JITTER_DEFAULT; michael@0: break; michael@0: case 'r': michael@0: resume = PR_TRUE; michael@0: break; michael@0: case 'h': /* help message */ michael@0: Help(); michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: PL_DestroyOptState(opt); michael@0: } michael@0: michael@0: lm = PR_NewLogModule("Test"); /* Initialize logging */ michael@0: michael@0: /* set concurrency */ michael@0: PR_SetConcurrency( 4 ); michael@0: michael@0: /* setup thread synchronization mechanics */ michael@0: ml = PR_NewLock(); michael@0: cv = PR_NewCondVar( ml ); michael@0: michael@0: /* setup a tcp socket */ michael@0: memset(&listenAddr, 0, sizeof(listenAddr)); michael@0: rv = PR_InitializeNetAddr(PR_IpAddrAny, BASE_PORT, &listenAddr); michael@0: PR_ASSERT( PR_SUCCESS == rv ); michael@0: michael@0: listenSock = PR_NewTCPSocket(); michael@0: PR_ASSERT( listenSock ); michael@0: michael@0: rv = PR_Bind( listenSock, &listenAddr); michael@0: PR_ASSERT( PR_SUCCESS == rv ); michael@0: michael@0: rv = PR_Listen( listenSock, 5 ); michael@0: PR_ASSERT( PR_SUCCESS == rv ); michael@0: michael@0: /* open a file for writing, provoke bug */ michael@0: file1 = PR_Open("xxxTestFile", PR_CREATE_FILE | PR_RDWR, 666); michael@0: michael@0: /* create Connect thread */ michael@0: tConnect = PR_CreateThread( michael@0: PR_USER_THREAD, ConnectThread, NULL, michael@0: PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, michael@0: PR_JOINABLE_THREAD, 0 ); michael@0: PR_ASSERT( tConnect ); michael@0: michael@0: /* create jitter off thread */ michael@0: tJitter = PR_CreateThread( michael@0: PR_USER_THREAD, JitterThread, NULL, michael@0: PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, michael@0: PR_JOINABLE_THREAD, 0 ); michael@0: PR_ASSERT( tJitter ); michael@0: michael@0: /* create acceptread thread */ michael@0: tAccept = PR_CreateThread( michael@0: PR_USER_THREAD, AcceptThread, NULL, michael@0: PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, michael@0: PR_JOINABLE_THREAD, 0 ); michael@0: PR_ASSERT( tAccept ); michael@0: michael@0: /* wait for all threads to quit, then terminate gracefully */ michael@0: PR_JoinThread( tConnect ); michael@0: PR_JoinThread( tAccept ); michael@0: PR_JoinThread( tJitter ); michael@0: PR_Close( listenSock ); michael@0: PR_DestroyCondVar(cv); michael@0: PR_DestroyLock(ml); michael@0: PR_Close( file1 ); michael@0: PR_Delete( "xxxTestFile"); michael@0: michael@0: /* test return and exit */ michael@0: if (debug) printf("%s\n", (failed_already)? "FAIL" : "PASS"); michael@0: return( (failed_already == PR_TRUE )? 1 : 0 ); michael@0: } /* main() */ michael@0: /* end ntioto.c */