Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | #include <stdio.h> |
michael@0 | 6 | #include "nsCOMPtr.h" |
michael@0 | 7 | #include "nsIEventQueueService.h" |
michael@0 | 8 | #include "nsIServiceManager.h" |
michael@0 | 9 | #include "nsIStreamListener.h" |
michael@0 | 10 | #include "nsIURI.h" |
michael@0 | 11 | #include "nsNetUtil.h" |
michael@0 | 12 | #include <algorithm> |
michael@0 | 13 | //#include "prthread.h" |
michael@0 | 14 | |
michael@0 | 15 | // This test attempts to load a URL on a separate thread. It is currently |
michael@0 | 16 | // designed simply to expose the problems inherent in such an ambitous task |
michael@0 | 17 | // (i.e., it don't work). |
michael@0 | 18 | |
michael@0 | 19 | // Utility functions... |
michael@0 | 20 | |
michael@0 | 21 | // Create event queue for current thread. |
michael@0 | 22 | static nsCOMPtr<nsIEventQueue> |
michael@0 | 23 | createEventQueue() { |
michael@0 | 24 | nsCOMPtr<nsIEventQueue> result; |
michael@0 | 25 | // Get event queue service. |
michael@0 | 26 | nsresult rv = NS_OK; |
michael@0 | 27 | nsCOMPtr<nsIEventQueueService> eqs = |
michael@0 | 28 | do_GetService(NS_EVENTQUEUESERVICE_CONTRACTID, &rv); |
michael@0 | 29 | if ( NS_SUCCEEDED( rv ) ) { |
michael@0 | 30 | eqs->GetThreadEventQueue(NS_CURRENT_THREAD, getter_AddRefs(result)); |
michael@0 | 31 | } else { |
michael@0 | 32 | printf( "%s %d: NS_WITH_SERVICE(nsIEventQueueService) failed, rv=0x%08X\n", |
michael@0 | 33 | (char*)__FILE__, (int)__LINE__, (int)rv ); |
michael@0 | 34 | } |
michael@0 | 35 | return result; |
michael@0 | 36 | } |
michael@0 | 37 | |
michael@0 | 38 | // Create channel for requested URL. |
michael@0 | 39 | static nsCOMPtr<nsIChannel> |
michael@0 | 40 | createChannel( const char *url ) { |
michael@0 | 41 | nsCOMPtr<nsIInputStream> result; |
michael@0 | 42 | |
michael@0 | 43 | nsCOMPtr<nsIURI> uri; |
michael@0 | 44 | printf( "Calling NS_NewURI for %s...\n", url ); |
michael@0 | 45 | nsresult rv = NS_NewURI( getter_AddRefs( uri ), url ); |
michael@0 | 46 | |
michael@0 | 47 | if ( NS_SUCCEEDED( rv ) ) { |
michael@0 | 48 | printf( "...NS_NewURI completed OK\n" ); |
michael@0 | 49 | |
michael@0 | 50 | // Allocate a new input channel on this thread. |
michael@0 | 51 | printf( "Calling NS_OpenURI...\n" ); |
michael@0 | 52 | nsresult rv = NS_OpenURI( getter_AddRefs( result ), uri, 0 ); |
michael@0 | 53 | |
michael@0 | 54 | if ( NS_SUCCEEDED( rv ) ) { |
michael@0 | 55 | printf( "...NS_OpenURI completed OK\n" ); |
michael@0 | 56 | } else { |
michael@0 | 57 | printf( "%s %d: NS_OpenURI failed, rv=0x%08X\n", |
michael@0 | 58 | (char*)__FILE__, (int)__LINE__, (int)rv ); |
michael@0 | 59 | } |
michael@0 | 60 | } else { |
michael@0 | 61 | printf( "%s %d: NS_NewURI failed, rv=0x%08X\n", |
michael@0 | 62 | (char*)__FILE__, (int)__LINE__, (int)rv ); |
michael@0 | 63 | } |
michael@0 | 64 | return result; |
michael@0 | 65 | } |
michael@0 | 66 | |
michael@0 | 67 | // Test listener. It basically dumps incoming data to console. |
michael@0 | 68 | class TestListener : public nsIStreamListener { |
michael@0 | 69 | public: |
michael@0 | 70 | NS_DECL_ISUPPORTS |
michael@0 | 71 | NS_DECL_NSISTREAMLISTENER |
michael@0 | 72 | NS_DECL_NSISTREAMOBSERVER |
michael@0 | 73 | |
michael@0 | 74 | TestListener(); |
michael@0 | 75 | ~TestListener(); |
michael@0 | 76 | static void IOThread( void *p ); |
michael@0 | 77 | |
michael@0 | 78 | private: |
michael@0 | 79 | bool mDone; |
michael@0 | 80 | int mThreadNo; |
michael@0 | 81 | FILE *mFile; |
michael@0 | 82 | static int threadCount; |
michael@0 | 83 | }; // class TestListener |
michael@0 | 84 | |
michael@0 | 85 | int TestListener::threadCount = 0; |
michael@0 | 86 | |
michael@0 | 87 | TestListener::TestListener() |
michael@0 | 88 | : mDone( false ), mThreadNo( ++threadCount ) { |
michael@0 | 89 | printf( "TestListener ctor called on thread %d\n", mThreadNo ); |
michael@0 | 90 | } |
michael@0 | 91 | |
michael@0 | 92 | TestListener::~TestListener() { |
michael@0 | 93 | printf( "TestListener dtor called on thread %d\n", mThreadNo ); |
michael@0 | 94 | } |
michael@0 | 95 | |
michael@0 | 96 | NS_IMPL_ISUPPORTS( TestListener, nsIStreamListener, nsIRequestObserver ) |
michael@0 | 97 | |
michael@0 | 98 | NS_IMETHODIMP |
michael@0 | 99 | TestListener::OnStartRequest( nsIChannel *aChannel, nsISupports *aContext ) { |
michael@0 | 100 | nsresult rv = NS_OK; |
michael@0 | 101 | |
michael@0 | 102 | printf( "TestListener::OnStartRequest called on thread %d\n", mThreadNo ); |
michael@0 | 103 | |
michael@0 | 104 | // Open output file. |
michael@0 | 105 | char fileName[32]; |
michael@0 | 106 | sprintf( fileName, "%s%d", "thread", mThreadNo ); |
michael@0 | 107 | mFile = fopen( fileName, "wb" ); |
michael@0 | 108 | setbuf( mFile, 0 ); |
michael@0 | 109 | |
michael@0 | 110 | return rv; |
michael@0 | 111 | } |
michael@0 | 112 | |
michael@0 | 113 | NS_IMETHODIMP |
michael@0 | 114 | TestListener::OnStopRequest( nsIChannel *aChannel, |
michael@0 | 115 | nsISupports *aContext, |
michael@0 | 116 | nsresult aStatus, |
michael@0 | 117 | const char16_t *aMsg ) { |
michael@0 | 118 | nsresult rv = NS_OK; |
michael@0 | 119 | |
michael@0 | 120 | printf( "TestListener::OnStopRequest called on thread %d\n", mThreadNo ); |
michael@0 | 121 | |
michael@0 | 122 | fclose( mFile ); |
michael@0 | 123 | mDone = true; |
michael@0 | 124 | |
michael@0 | 125 | return rv; |
michael@0 | 126 | } |
michael@0 | 127 | |
michael@0 | 128 | NS_IMETHODIMP |
michael@0 | 129 | TestListener::OnDataAvailable( nsIChannel *aChannel, |
michael@0 | 130 | nsISupports *aContext, |
michael@0 | 131 | nsIInputStream *aStream, |
michael@0 | 132 | uint64_t offset, |
michael@0 | 133 | uint32_t aLength ) { |
michael@0 | 134 | nsresult rv = NS_OK; |
michael@0 | 135 | |
michael@0 | 136 | printf( "TestListener::OnDataAvailable called on thread %d\n", mThreadNo ); |
michael@0 | 137 | |
michael@0 | 138 | // Write the data to the console. |
michael@0 | 139 | // Read a buffer full till aLength bytes have been processed. |
michael@0 | 140 | char buffer[ 8192 ]; |
michael@0 | 141 | unsigned long bytesRemaining = aLength; |
michael@0 | 142 | while ( bytesRemaining ) { |
michael@0 | 143 | unsigned int bytesRead; |
michael@0 | 144 | // Read a buffer full or the number remaining (whichever is smaller). |
michael@0 | 145 | rv = aStream->Read( buffer, |
michael@0 | 146 | std::min( sizeof( buffer ), bytesRemaining ), |
michael@0 | 147 | &bytesRead ); |
michael@0 | 148 | if ( NS_SUCCEEDED( rv ) ) { |
michael@0 | 149 | // Write the bytes just read to the output file. |
michael@0 | 150 | fwrite( buffer, 1, bytesRead, mFile ); |
michael@0 | 151 | bytesRemaining -= bytesRead; |
michael@0 | 152 | } else { |
michael@0 | 153 | printf( "%s %d: Read error, rv=0x%08X\n", |
michael@0 | 154 | (char*)__FILE__, (int)__LINE__, (int)rv ); |
michael@0 | 155 | break; |
michael@0 | 156 | } |
michael@0 | 157 | } |
michael@0 | 158 | printf( "\n" ); |
michael@0 | 159 | |
michael@0 | 160 | return rv; |
michael@0 | 161 | } |
michael@0 | 162 | |
michael@0 | 163 | // IOThread: this function creates a new TestListener object (on the new |
michael@0 | 164 | // thread), opens a channel, and does AsyncRead to it. |
michael@0 | 165 | void |
michael@0 | 166 | TestListener::IOThread( void *p ) { |
michael@0 | 167 | printf( "I/O thread (0x%08X) started...\n", (int)(void*)PR_GetCurrentThread() ); |
michael@0 | 168 | |
michael@0 | 169 | // Argument is pointer to the nsIEventQueue for the main thread. |
michael@0 | 170 | nsIEventQueue *mainThreadQ = static_cast<nsIEventQueue*>(p); |
michael@0 | 171 | |
michael@0 | 172 | // Create channel for random web page. |
michael@0 | 173 | nsCOMPtr<nsIChannel> channel = createChannel( (const char*)p ); |
michael@0 | 174 | |
michael@0 | 175 | if ( channel ) { |
michael@0 | 176 | // Create event queue. |
michael@0 | 177 | nsCOMPtr<nsIEventQueue> ioEventQ = createEventQueue(); |
michael@0 | 178 | |
michael@0 | 179 | if ( ioEventQ ) { |
michael@0 | 180 | // Create test listener. |
michael@0 | 181 | TestListener *testListener = new TestListener(); |
michael@0 | 182 | testListener->AddRef(); |
michael@0 | 183 | |
michael@0 | 184 | // Read the channel. |
michael@0 | 185 | printf( "Doing AsyncRead...\n" ); |
michael@0 | 186 | nsresult rv = channel->AsyncRead( testListener, 0 ); |
michael@0 | 187 | |
michael@0 | 188 | if ( NS_SUCCEEDED( rv ) ) { |
michael@0 | 189 | printf( "...AsyncRead completed OK\n" ); |
michael@0 | 190 | |
michael@0 | 191 | // Process events till testListener says stop. |
michael@0 | 192 | printf( "Start event loop on io thread %d...\n", testListener->mThreadNo ); |
michael@0 | 193 | while ( !testListener->mDone ) { |
michael@0 | 194 | PLEvent *event; |
michael@0 | 195 | ioEventQ->GetEvent( &event ); |
michael@0 | 196 | ioEventQ->HandleEvent( event ); |
michael@0 | 197 | } |
michael@0 | 198 | printf( "...io thread %d event loop exiting\n", testListener->mThreadNo ); |
michael@0 | 199 | } else { |
michael@0 | 200 | printf( "%s %d: AsyncRead failed on thread %d, rv=0x%08X\n", |
michael@0 | 201 | (char*)__FILE__, (int)__LINE__, testListener->mThreadNo, (int)rv ); |
michael@0 | 202 | } |
michael@0 | 203 | |
michael@0 | 204 | // Release the test listener. |
michael@0 | 205 | testListener->Release(); |
michael@0 | 206 | } |
michael@0 | 207 | } |
michael@0 | 208 | |
michael@0 | 209 | printf( "...I/O thread terminating\n" ); |
michael@0 | 210 | } |
michael@0 | 211 | |
michael@0 | 212 | static const int maxThreads = 5; |
michael@0 | 213 | |
michael@0 | 214 | int |
michael@0 | 215 | main( int argc, char* argv[] ) { |
michael@0 | 216 | setbuf( stdout, 0 ); |
michael@0 | 217 | if ( argc < 2 || argc > maxThreads + 1 ) { |
michael@0 | 218 | printf( "usage: testThreadedIO url1 <url2>...\n" |
michael@0 | 219 | "where <url#> is a location to be loaded on a separate thread\n" |
michael@0 | 220 | "limit is %d urls/threads", maxThreads ); |
michael@0 | 221 | return -1; |
michael@0 | 222 | } |
michael@0 | 223 | |
michael@0 | 224 | nsresult rv= (nsresult)-1; |
michael@0 | 225 | |
michael@0 | 226 | printf( "Test starting...\n" ); |
michael@0 | 227 | |
michael@0 | 228 | // Initialize XPCOM. |
michael@0 | 229 | printf( "Initializing XPCOM...\n" ); |
michael@0 | 230 | rv = NS_InitXPCOM2(nullptr, nullptr, nullptr); |
michael@0 | 231 | if ( NS_FAILED( rv ) ) { |
michael@0 | 232 | printf( "%s %d: NS_InitXPCOM failed, rv=0x%08X\n", |
michael@0 | 233 | (char*)__FILE__, (int)__LINE__, (int)rv ); |
michael@0 | 234 | return rv; |
michael@0 | 235 | } |
michael@0 | 236 | printf( "...XPCOM initialized OK\n" ); |
michael@0 | 237 | // Create the Event Queue for this thread... |
michael@0 | 238 | printf( "Creating event queue for main thread (0x%08X)...\n", |
michael@0 | 239 | (int)(void*)PR_GetCurrentThread() ); |
michael@0 | 240 | { |
michael@0 | 241 | nsCOMPtr<nsIEventQueue> mainThreadQ = createEventQueue(); |
michael@0 | 242 | |
michael@0 | 243 | if ( mainThreadQ ) { |
michael@0 | 244 | printf( "...main thread's event queue created OK\n" ); |
michael@0 | 245 | |
michael@0 | 246 | // Spawn threads to do I/O. |
michael@0 | 247 | int goodThreads = 0; |
michael@0 | 248 | PRThread *thread[ maxThreads ]; |
michael@0 | 249 | for ( int threadNo = 1; threadNo < argc; threadNo++ ) { |
michael@0 | 250 | printf( "Creating I/O thread %d to load %s...\n", threadNo, argv[threadNo] ); |
michael@0 | 251 | PRThread *ioThread = PR_CreateThread( PR_USER_THREAD, |
michael@0 | 252 | TestListener::IOThread, |
michael@0 | 253 | argv[threadNo], |
michael@0 | 254 | PR_PRIORITY_NORMAL, |
michael@0 | 255 | PR_GLOBAL_THREAD, |
michael@0 | 256 | PR_JOINABLE_THREAD, |
michael@0 | 257 | 0 ); |
michael@0 | 258 | if ( ioThread ) { |
michael@0 | 259 | thread[ goodThreads++ ] = ioThread; |
michael@0 | 260 | printf( "...I/O thread %d (0x%08X) created OK\n", |
michael@0 | 261 | threadNo, (int)(void*)ioThread ); |
michael@0 | 262 | } else { |
michael@0 | 263 | printf( "%s %d: PR_CreateThread for thread %d failed\n", |
michael@0 | 264 | (char*)__FILE__, (int)__LINE__, threadNo ); |
michael@0 | 265 | } |
michael@0 | 266 | } |
michael@0 | 267 | |
michael@0 | 268 | // Wait for all the threads to terminate. |
michael@0 | 269 | for ( int joinThread = 0; joinThread < goodThreads; joinThread++ ) { |
michael@0 | 270 | printf( "Waiting for thread %d to terminate...\n", joinThread+1 ); |
michael@0 | 271 | PR_JoinThread( thread[ joinThread ] ); |
michael@0 | 272 | } |
michael@0 | 273 | } |
michael@0 | 274 | } // this scopes the nsCOMPtrs |
michael@0 | 275 | // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM |
michael@0 | 276 | // Shut down XPCOM. |
michael@0 | 277 | printf( "Shutting down XPCOM...\n" ); |
michael@0 | 278 | NS_ShutdownXPCOM( 0 ); |
michael@0 | 279 | printf( "...XPCOM shutdown complete\n" ); |
michael@0 | 280 | |
michael@0 | 281 | // Exit. |
michael@0 | 282 | printf( "...test complete, rv=0x%08X\n", (int)rv ); |
michael@0 | 283 | return rv; |
michael@0 | 284 | } |