Fossil SCM
Make the '--stopper' option for the 'server' command on Windows work as advertised even when the server thread is being blocked by accept().
Commit
a6eb6510756fdda20cefb4c693a892c062a7c8e0
Parent
e464dd963c81345…
1 file changed
+88
-15
+88
-15
| --- src/winhttp.c | ||
| +++ src/winhttp.c | ||
| @@ -24,10 +24,24 @@ | ||
| 24 | 24 | /* This code is for win32 only */ |
| 25 | 25 | #include <windows.h> |
| 26 | 26 | #include <process.h> |
| 27 | 27 | #include "winhttp.h" |
| 28 | 28 | |
| 29 | +/* | |
| 30 | +** The HttpServer structure holds information about an instance of | |
| 31 | +** the HTTP server itself. | |
| 32 | +*/ | |
| 33 | +typedef struct HttpServer HttpServer; | |
| 34 | +struct HttpServer { | |
| 35 | + HANDLE hStoppedEvent; /* Event to signal when server is stopped, | |
| 36 | + ** must be closed by callee. */ | |
| 37 | + char *zStopper; /* The stopper file name, must be freed by | |
| 38 | + ** callee. */ | |
| 39 | + SOCKET listener; /* Socket on which the server is listening, | |
| 40 | + ** may be closed by callee. */ | |
| 41 | +}; | |
| 42 | + | |
| 29 | 43 | /* |
| 30 | 44 | ** The HttpRequest structure holds information about each incoming |
| 31 | 45 | ** HTTP request. |
| 32 | 46 | */ |
| 33 | 47 | typedef struct HttpRequest HttpRequest; |
| @@ -70,10 +84,51 @@ | ||
| 70 | 84 | const char *zService, |
| 71 | 85 | const char *zErr |
| 72 | 86 | ){ |
| 73 | 87 | fossil_fatal("unable to %s service '%s': %s", zOp, zService, zErr); |
| 74 | 88 | } |
| 89 | + | |
| 90 | +/* | |
| 91 | +** Make sure the server stops as soon as possible after the stopper file | |
| 92 | +** is found. If there is no stopper file name, do nothing. | |
| 93 | +*/ | |
| 94 | +static void win32_server_stopper(void *pAppData){ | |
| 95 | + HttpServer *p = (HttpServer*)pAppData; | |
| 96 | + if( p!=0 ){ | |
| 97 | + HANDLE hStoppedEvent = p->hStoppedEvent; | |
| 98 | + const char *zStopper = p->zStopper; | |
| 99 | + SOCKET listener = p->listener; | |
| 100 | + if( hStoppedEvent!=NULL && zStopper!=0 && listener!=INVALID_SOCKET ){ | |
| 101 | + while( 1 ){ | |
| 102 | + DWORD dwResult = WaitForMultipleObjectsEx(1, &hStoppedEvent, FALSE, | |
| 103 | + 1000, TRUE); | |
| 104 | + if( dwResult!=WAIT_IO_COMPLETION && dwResult!=WAIT_TIMEOUT ){ | |
| 105 | + /* The event is either invalid, signaled, or abandoned. Bail | |
| 106 | + ** out now because those conditions should indicate the parent | |
| 107 | + ** thread is dead or dying. */ | |
| 108 | + break; | |
| 109 | + } | |
| 110 | + if( file_size(zStopper)>=0 ){ | |
| 111 | + /* The stopper file has been found. Attempt to close the server | |
| 112 | + ** listener socket now and then exit. */ | |
| 113 | + closesocket(listener); | |
| 114 | + p->listener = INVALID_SOCKET; | |
| 115 | + break; | |
| 116 | + } | |
| 117 | + } | |
| 118 | + } | |
| 119 | + if( hStoppedEvent!=NULL ){ | |
| 120 | + CloseHandle(hStoppedEvent); | |
| 121 | + p->hStoppedEvent = NULL; | |
| 122 | + } | |
| 123 | + if( zStopper!=0 ){ | |
| 124 | + fossil_free(p->zStopper); | |
| 125 | + p->zStopper = 0; | |
| 126 | + } | |
| 127 | + fossil_free(p); | |
| 128 | + } | |
| 129 | +} | |
| 75 | 130 | |
| 76 | 131 | /* |
| 77 | 132 | ** Process a single incoming HTTP request. |
| 78 | 133 | */ |
| 79 | 134 | static void win32_http_request(void *pAppData){ |
| @@ -163,11 +218,11 @@ | ||
| 163 | 218 | if( in ) fclose(in); |
| 164 | 219 | closesocket(p->s); |
| 165 | 220 | file_delete(zRequestFName); |
| 166 | 221 | file_delete(zReplyFName); |
| 167 | 222 | file_delete(zCmdFName); |
| 168 | - free(p); | |
| 223 | + fossil_free(p); | |
| 169 | 224 | } |
| 170 | 225 | |
| 171 | 226 | /* |
| 172 | 227 | ** Process a single incoming SCGI request. |
| 173 | 228 | */ |
| @@ -225,11 +280,11 @@ | ||
| 225 | 280 | if( out ) fclose(out); |
| 226 | 281 | if( in ) fclose(in); |
| 227 | 282 | closesocket(p->s); |
| 228 | 283 | file_delete(zRequestFName); |
| 229 | 284 | file_delete(zReplyFName); |
| 230 | - free(p); | |
| 285 | + fossil_free(p); | |
| 231 | 286 | } |
| 232 | 287 | |
| 233 | 288 | |
| 234 | 289 | /* |
| 235 | 290 | ** Start a listening socket and process incoming HTTP requests on |
| @@ -243,19 +298,19 @@ | ||
| 243 | 298 | const char *zNotFound, /* The --notfound option, or NULL */ |
| 244 | 299 | const char *zFileGlob, /* The --fileglob option, or NULL */ |
| 245 | 300 | const char *zIpAddr, /* Bind to this IP address, if not NULL */ |
| 246 | 301 | int flags /* One or more HTTP_SERVER_ flags */ |
| 247 | 302 | ){ |
| 303 | + HANDLE hStoppedEvent; | |
| 248 | 304 | WSADATA wd; |
| 249 | 305 | SOCKET s = INVALID_SOCKET; |
| 250 | 306 | SOCKADDR_IN addr; |
| 251 | 307 | int idCnt = 0; |
| 252 | 308 | int iPort = mnPort; |
| 253 | 309 | Blob options; |
| 254 | 310 | wchar_t zTmpPath[MAX_PATH]; |
| 255 | 311 | |
| 256 | - if( zStopper ) file_delete(zStopper); | |
| 257 | 312 | blob_zero(&options); |
| 258 | 313 | if( zBaseUrl ){ |
| 259 | 314 | blob_appendf(&options, " --baseurl %s", zBaseUrl); |
| 260 | 315 | } |
| 261 | 316 | if( zNotFound ){ |
| @@ -324,17 +379,35 @@ | ||
| 324 | 379 | zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort); |
| 325 | 380 | fossil_print("Launch webbrowser: %s\n", zBrowser); |
| 326 | 381 | fossil_system(zBrowser); |
| 327 | 382 | } |
| 328 | 383 | fossil_print("Type Ctrl-C to stop the HTTP server\n"); |
| 384 | + /* Create an event used to signal when this server is exiting. */ | |
| 385 | + hStoppedEvent = CreateEvent(NULL, TRUE, FALSE, NULL); | |
| 386 | + assert( hStoppedEvent!=NULL ); | |
| 387 | + /* If there is a stopper file name, start the dedicated thread now. | |
| 388 | + ** It will attempt to close the listener socket within 1 second of | |
| 389 | + ** the stopper file being created. */ | |
| 390 | + if( zStopper ){ | |
| 391 | + HttpServer *pServer = fossil_malloc(sizeof(HttpServer)); | |
| 392 | + memset(pServer, 0, sizeof(HttpServer)); | |
| 393 | + DuplicateHandle(GetCurrentProcess(), hStoppedEvent, | |
| 394 | + GetCurrentProcess(), &pServer->hStoppedEvent, | |
| 395 | + 0, FALSE, DUPLICATE_SAME_ACCESS); | |
| 396 | + assert( pServer->hStoppedEvent!=NULL ); | |
| 397 | + pServer->zStopper = fossil_strdup(zStopper); | |
| 398 | + pServer->listener = s; | |
| 399 | + file_delete(zStopper); | |
| 400 | + _beginthread(win32_server_stopper, 0, (void*)pServer); | |
| 401 | + } | |
| 329 | 402 | /* Set the service status to running and pass the listener socket to the |
| 330 | 403 | ** service handling procedures. */ |
| 331 | 404 | win32_http_service_running(s); |
| 332 | 405 | for(;;){ |
| 333 | 406 | SOCKET client; |
| 334 | 407 | SOCKADDR_IN client_addr; |
| 335 | - HttpRequest *p; | |
| 408 | + HttpRequest *pRequest; | |
| 336 | 409 | int len = sizeof(client_addr); |
| 337 | 410 | int wsaError; |
| 338 | 411 | |
| 339 | 412 | client = accept(s, (struct sockaddr*)&client_addr, &len); |
| 340 | 413 | if( client==INVALID_SOCKET ){ |
| @@ -347,27 +420,27 @@ | ||
| 347 | 420 | }else{ |
| 348 | 421 | closesocket(s); |
| 349 | 422 | WSACleanup(); |
| 350 | 423 | fossil_fatal("error from accept()"); |
| 351 | 424 | } |
| 352 | - }else if( zStopper && file_size(zStopper)>=0 ){ | |
| 353 | - break; | |
| 354 | - } | |
| 355 | - p = fossil_malloc( sizeof(*p) ); | |
| 356 | - p->id = ++idCnt; | |
| 357 | - p->s = client; | |
| 358 | - p->addr = client_addr; | |
| 359 | - p->flags = flags; | |
| 360 | - p->zOptions = blob_str(&options); | |
| 425 | + } | |
| 426 | + pRequest = fossil_malloc(sizeof(HttpRequest)); | |
| 427 | + pRequest->id = ++idCnt; | |
| 428 | + pRequest->s = client; | |
| 429 | + pRequest->addr = client_addr; | |
| 430 | + pRequest->flags = flags; | |
| 431 | + pRequest->zOptions = blob_str(&options); | |
| 361 | 432 | if( flags & HTTP_SERVER_SCGI ){ |
| 362 | - _beginthread(win32_scgi_request, 0, (void*)p); | |
| 433 | + _beginthread(win32_scgi_request, 0, (void*)pRequest); | |
| 363 | 434 | }else{ |
| 364 | - _beginthread(win32_http_request, 0, (void*)p); | |
| 435 | + _beginthread(win32_http_request, 0, (void*)pRequest); | |
| 365 | 436 | } |
| 366 | 437 | } |
| 367 | 438 | closesocket(s); |
| 368 | 439 | WSACleanup(); |
| 440 | + SetEvent(hStoppedEvent); | |
| 441 | + CloseHandle(hStoppedEvent); | |
| 369 | 442 | } |
| 370 | 443 | |
| 371 | 444 | /* |
| 372 | 445 | ** The HttpService structure is used to pass information to the service main |
| 373 | 446 | ** function and to the service control handler function. |
| 374 | 447 |
| --- src/winhttp.c | |
| +++ src/winhttp.c | |
| @@ -24,10 +24,24 @@ | |
| 24 | /* This code is for win32 only */ |
| 25 | #include <windows.h> |
| 26 | #include <process.h> |
| 27 | #include "winhttp.h" |
| 28 | |
| 29 | /* |
| 30 | ** The HttpRequest structure holds information about each incoming |
| 31 | ** HTTP request. |
| 32 | */ |
| 33 | typedef struct HttpRequest HttpRequest; |
| @@ -70,10 +84,51 @@ | |
| 70 | const char *zService, |
| 71 | const char *zErr |
| 72 | ){ |
| 73 | fossil_fatal("unable to %s service '%s': %s", zOp, zService, zErr); |
| 74 | } |
| 75 | |
| 76 | /* |
| 77 | ** Process a single incoming HTTP request. |
| 78 | */ |
| 79 | static void win32_http_request(void *pAppData){ |
| @@ -163,11 +218,11 @@ | |
| 163 | if( in ) fclose(in); |
| 164 | closesocket(p->s); |
| 165 | file_delete(zRequestFName); |
| 166 | file_delete(zReplyFName); |
| 167 | file_delete(zCmdFName); |
| 168 | free(p); |
| 169 | } |
| 170 | |
| 171 | /* |
| 172 | ** Process a single incoming SCGI request. |
| 173 | */ |
| @@ -225,11 +280,11 @@ | |
| 225 | if( out ) fclose(out); |
| 226 | if( in ) fclose(in); |
| 227 | closesocket(p->s); |
| 228 | file_delete(zRequestFName); |
| 229 | file_delete(zReplyFName); |
| 230 | free(p); |
| 231 | } |
| 232 | |
| 233 | |
| 234 | /* |
| 235 | ** Start a listening socket and process incoming HTTP requests on |
| @@ -243,19 +298,19 @@ | |
| 243 | const char *zNotFound, /* The --notfound option, or NULL */ |
| 244 | const char *zFileGlob, /* The --fileglob option, or NULL */ |
| 245 | const char *zIpAddr, /* Bind to this IP address, if not NULL */ |
| 246 | int flags /* One or more HTTP_SERVER_ flags */ |
| 247 | ){ |
| 248 | WSADATA wd; |
| 249 | SOCKET s = INVALID_SOCKET; |
| 250 | SOCKADDR_IN addr; |
| 251 | int idCnt = 0; |
| 252 | int iPort = mnPort; |
| 253 | Blob options; |
| 254 | wchar_t zTmpPath[MAX_PATH]; |
| 255 | |
| 256 | if( zStopper ) file_delete(zStopper); |
| 257 | blob_zero(&options); |
| 258 | if( zBaseUrl ){ |
| 259 | blob_appendf(&options, " --baseurl %s", zBaseUrl); |
| 260 | } |
| 261 | if( zNotFound ){ |
| @@ -324,17 +379,35 @@ | |
| 324 | zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort); |
| 325 | fossil_print("Launch webbrowser: %s\n", zBrowser); |
| 326 | fossil_system(zBrowser); |
| 327 | } |
| 328 | fossil_print("Type Ctrl-C to stop the HTTP server\n"); |
| 329 | /* Set the service status to running and pass the listener socket to the |
| 330 | ** service handling procedures. */ |
| 331 | win32_http_service_running(s); |
| 332 | for(;;){ |
| 333 | SOCKET client; |
| 334 | SOCKADDR_IN client_addr; |
| 335 | HttpRequest *p; |
| 336 | int len = sizeof(client_addr); |
| 337 | int wsaError; |
| 338 | |
| 339 | client = accept(s, (struct sockaddr*)&client_addr, &len); |
| 340 | if( client==INVALID_SOCKET ){ |
| @@ -347,27 +420,27 @@ | |
| 347 | }else{ |
| 348 | closesocket(s); |
| 349 | WSACleanup(); |
| 350 | fossil_fatal("error from accept()"); |
| 351 | } |
| 352 | }else if( zStopper && file_size(zStopper)>=0 ){ |
| 353 | break; |
| 354 | } |
| 355 | p = fossil_malloc( sizeof(*p) ); |
| 356 | p->id = ++idCnt; |
| 357 | p->s = client; |
| 358 | p->addr = client_addr; |
| 359 | p->flags = flags; |
| 360 | p->zOptions = blob_str(&options); |
| 361 | if( flags & HTTP_SERVER_SCGI ){ |
| 362 | _beginthread(win32_scgi_request, 0, (void*)p); |
| 363 | }else{ |
| 364 | _beginthread(win32_http_request, 0, (void*)p); |
| 365 | } |
| 366 | } |
| 367 | closesocket(s); |
| 368 | WSACleanup(); |
| 369 | } |
| 370 | |
| 371 | /* |
| 372 | ** The HttpService structure is used to pass information to the service main |
| 373 | ** function and to the service control handler function. |
| 374 |
| --- src/winhttp.c | |
| +++ src/winhttp.c | |
| @@ -24,10 +24,24 @@ | |
| 24 | /* This code is for win32 only */ |
| 25 | #include <windows.h> |
| 26 | #include <process.h> |
| 27 | #include "winhttp.h" |
| 28 | |
| 29 | /* |
| 30 | ** The HttpServer structure holds information about an instance of |
| 31 | ** the HTTP server itself. |
| 32 | */ |
| 33 | typedef struct HttpServer HttpServer; |
| 34 | struct HttpServer { |
| 35 | HANDLE hStoppedEvent; /* Event to signal when server is stopped, |
| 36 | ** must be closed by callee. */ |
| 37 | char *zStopper; /* The stopper file name, must be freed by |
| 38 | ** callee. */ |
| 39 | SOCKET listener; /* Socket on which the server is listening, |
| 40 | ** may be closed by callee. */ |
| 41 | }; |
| 42 | |
| 43 | /* |
| 44 | ** The HttpRequest structure holds information about each incoming |
| 45 | ** HTTP request. |
| 46 | */ |
| 47 | typedef struct HttpRequest HttpRequest; |
| @@ -70,10 +84,51 @@ | |
| 84 | const char *zService, |
| 85 | const char *zErr |
| 86 | ){ |
| 87 | fossil_fatal("unable to %s service '%s': %s", zOp, zService, zErr); |
| 88 | } |
| 89 | |
| 90 | /* |
| 91 | ** Make sure the server stops as soon as possible after the stopper file |
| 92 | ** is found. If there is no stopper file name, do nothing. |
| 93 | */ |
| 94 | static void win32_server_stopper(void *pAppData){ |
| 95 | HttpServer *p = (HttpServer*)pAppData; |
| 96 | if( p!=0 ){ |
| 97 | HANDLE hStoppedEvent = p->hStoppedEvent; |
| 98 | const char *zStopper = p->zStopper; |
| 99 | SOCKET listener = p->listener; |
| 100 | if( hStoppedEvent!=NULL && zStopper!=0 && listener!=INVALID_SOCKET ){ |
| 101 | while( 1 ){ |
| 102 | DWORD dwResult = WaitForMultipleObjectsEx(1, &hStoppedEvent, FALSE, |
| 103 | 1000, TRUE); |
| 104 | if( dwResult!=WAIT_IO_COMPLETION && dwResult!=WAIT_TIMEOUT ){ |
| 105 | /* The event is either invalid, signaled, or abandoned. Bail |
| 106 | ** out now because those conditions should indicate the parent |
| 107 | ** thread is dead or dying. */ |
| 108 | break; |
| 109 | } |
| 110 | if( file_size(zStopper)>=0 ){ |
| 111 | /* The stopper file has been found. Attempt to close the server |
| 112 | ** listener socket now and then exit. */ |
| 113 | closesocket(listener); |
| 114 | p->listener = INVALID_SOCKET; |
| 115 | break; |
| 116 | } |
| 117 | } |
| 118 | } |
| 119 | if( hStoppedEvent!=NULL ){ |
| 120 | CloseHandle(hStoppedEvent); |
| 121 | p->hStoppedEvent = NULL; |
| 122 | } |
| 123 | if( zStopper!=0 ){ |
| 124 | fossil_free(p->zStopper); |
| 125 | p->zStopper = 0; |
| 126 | } |
| 127 | fossil_free(p); |
| 128 | } |
| 129 | } |
| 130 | |
| 131 | /* |
| 132 | ** Process a single incoming HTTP request. |
| 133 | */ |
| 134 | static void win32_http_request(void *pAppData){ |
| @@ -163,11 +218,11 @@ | |
| 218 | if( in ) fclose(in); |
| 219 | closesocket(p->s); |
| 220 | file_delete(zRequestFName); |
| 221 | file_delete(zReplyFName); |
| 222 | file_delete(zCmdFName); |
| 223 | fossil_free(p); |
| 224 | } |
| 225 | |
| 226 | /* |
| 227 | ** Process a single incoming SCGI request. |
| 228 | */ |
| @@ -225,11 +280,11 @@ | |
| 280 | if( out ) fclose(out); |
| 281 | if( in ) fclose(in); |
| 282 | closesocket(p->s); |
| 283 | file_delete(zRequestFName); |
| 284 | file_delete(zReplyFName); |
| 285 | fossil_free(p); |
| 286 | } |
| 287 | |
| 288 | |
| 289 | /* |
| 290 | ** Start a listening socket and process incoming HTTP requests on |
| @@ -243,19 +298,19 @@ | |
| 298 | const char *zNotFound, /* The --notfound option, or NULL */ |
| 299 | const char *zFileGlob, /* The --fileglob option, or NULL */ |
| 300 | const char *zIpAddr, /* Bind to this IP address, if not NULL */ |
| 301 | int flags /* One or more HTTP_SERVER_ flags */ |
| 302 | ){ |
| 303 | HANDLE hStoppedEvent; |
| 304 | WSADATA wd; |
| 305 | SOCKET s = INVALID_SOCKET; |
| 306 | SOCKADDR_IN addr; |
| 307 | int idCnt = 0; |
| 308 | int iPort = mnPort; |
| 309 | Blob options; |
| 310 | wchar_t zTmpPath[MAX_PATH]; |
| 311 | |
| 312 | blob_zero(&options); |
| 313 | if( zBaseUrl ){ |
| 314 | blob_appendf(&options, " --baseurl %s", zBaseUrl); |
| 315 | } |
| 316 | if( zNotFound ){ |
| @@ -324,17 +379,35 @@ | |
| 379 | zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort); |
| 380 | fossil_print("Launch webbrowser: %s\n", zBrowser); |
| 381 | fossil_system(zBrowser); |
| 382 | } |
| 383 | fossil_print("Type Ctrl-C to stop the HTTP server\n"); |
| 384 | /* Create an event used to signal when this server is exiting. */ |
| 385 | hStoppedEvent = CreateEvent(NULL, TRUE, FALSE, NULL); |
| 386 | assert( hStoppedEvent!=NULL ); |
| 387 | /* If there is a stopper file name, start the dedicated thread now. |
| 388 | ** It will attempt to close the listener socket within 1 second of |
| 389 | ** the stopper file being created. */ |
| 390 | if( zStopper ){ |
| 391 | HttpServer *pServer = fossil_malloc(sizeof(HttpServer)); |
| 392 | memset(pServer, 0, sizeof(HttpServer)); |
| 393 | DuplicateHandle(GetCurrentProcess(), hStoppedEvent, |
| 394 | GetCurrentProcess(), &pServer->hStoppedEvent, |
| 395 | 0, FALSE, DUPLICATE_SAME_ACCESS); |
| 396 | assert( pServer->hStoppedEvent!=NULL ); |
| 397 | pServer->zStopper = fossil_strdup(zStopper); |
| 398 | pServer->listener = s; |
| 399 | file_delete(zStopper); |
| 400 | _beginthread(win32_server_stopper, 0, (void*)pServer); |
| 401 | } |
| 402 | /* Set the service status to running and pass the listener socket to the |
| 403 | ** service handling procedures. */ |
| 404 | win32_http_service_running(s); |
| 405 | for(;;){ |
| 406 | SOCKET client; |
| 407 | SOCKADDR_IN client_addr; |
| 408 | HttpRequest *pRequest; |
| 409 | int len = sizeof(client_addr); |
| 410 | int wsaError; |
| 411 | |
| 412 | client = accept(s, (struct sockaddr*)&client_addr, &len); |
| 413 | if( client==INVALID_SOCKET ){ |
| @@ -347,27 +420,27 @@ | |
| 420 | }else{ |
| 421 | closesocket(s); |
| 422 | WSACleanup(); |
| 423 | fossil_fatal("error from accept()"); |
| 424 | } |
| 425 | } |
| 426 | pRequest = fossil_malloc(sizeof(HttpRequest)); |
| 427 | pRequest->id = ++idCnt; |
| 428 | pRequest->s = client; |
| 429 | pRequest->addr = client_addr; |
| 430 | pRequest->flags = flags; |
| 431 | pRequest->zOptions = blob_str(&options); |
| 432 | if( flags & HTTP_SERVER_SCGI ){ |
| 433 | _beginthread(win32_scgi_request, 0, (void*)pRequest); |
| 434 | }else{ |
| 435 | _beginthread(win32_http_request, 0, (void*)pRequest); |
| 436 | } |
| 437 | } |
| 438 | closesocket(s); |
| 439 | WSACleanup(); |
| 440 | SetEvent(hStoppedEvent); |
| 441 | CloseHandle(hStoppedEvent); |
| 442 | } |
| 443 | |
| 444 | /* |
| 445 | ** The HttpService structure is used to pass information to the service main |
| 446 | ** function and to the service control handler function. |
| 447 |