@@ -26,6 +26,8 @@ import { HttpServer } from './http_server';
26
26
import { Readable } from 'stream' ;
27
27
import { RequestHandlerContext } from 'kibana/server' ;
28
28
import { KBN_CERT_PATH , KBN_KEY_PATH } from '@kbn/dev-utils' ;
29
+ import moment from 'moment' ;
30
+ import { of } from 'rxjs' ;
29
31
30
32
const cookieOptions = {
31
33
name : 'sid' ,
@@ -65,6 +67,7 @@ beforeEach(() => {
65
67
cors : {
66
68
enabled : false ,
67
69
} ,
70
+ shutdownTimeout : moment . duration ( 500 , 'ms' ) ,
68
71
} as any ;
69
72
70
73
configWithSSL = {
@@ -79,7 +82,7 @@ beforeEach(() => {
79
82
} ,
80
83
} as HttpConfig ;
81
84
82
- server = new HttpServer ( loggingService , 'tests' ) ;
85
+ server = new HttpServer ( loggingService , 'tests' , of ( config . shutdownTimeout ) ) ;
83
86
} ) ;
84
87
85
88
afterEach ( async ( ) => {
@@ -1431,3 +1434,79 @@ describe('setup contract', () => {
1431
1434
} ) ;
1432
1435
} ) ;
1433
1436
} ) ;
1437
+
1438
+ describe ( 'Graceful shutdown' , ( ) => {
1439
+ let shutdownTimeout : number ;
1440
+ let innerServerListener : Server ;
1441
+
1442
+ beforeEach ( async ( ) => {
1443
+ shutdownTimeout = config . shutdownTimeout . asMilliseconds ( ) ;
1444
+ const { registerRouter, server : innerServer } = await server . setup ( config ) ;
1445
+ innerServerListener = innerServer . listener ;
1446
+
1447
+ const router = new Router ( '' , logger , enhanceWithContext ) ;
1448
+ router . post (
1449
+ {
1450
+ path : '/' ,
1451
+ validate : false ,
1452
+ options : { body : { accepts : 'application/json' } } ,
1453
+ } ,
1454
+ async ( context , req , res ) => {
1455
+ // It takes to resolve the same period of the shutdownTimeout.
1456
+ // Since we'll trigger the stop a few ms after, it should have time to finish
1457
+ await new Promise ( ( resolve ) => setTimeout ( resolve , shutdownTimeout ) ) ;
1458
+ return res . ok ( { body : { ok : 1 } } ) ;
1459
+ }
1460
+ ) ;
1461
+ registerRouter ( router ) ;
1462
+
1463
+ await server . start ( ) ;
1464
+ } ) ;
1465
+
1466
+ test ( 'any ongoing requests should be resolved with `connection: close`' , async ( ) => {
1467
+ const [ response ] = await Promise . all ( [
1468
+ // Trigger a request that should hold the server from stopping until fulfilled
1469
+ supertest ( innerServerListener ) . post ( '/' ) ,
1470
+ // Stop the server while the request is in progress
1471
+ ( async ( ) => {
1472
+ await new Promise ( ( resolve ) => setTimeout ( resolve , shutdownTimeout / 3 ) ) ;
1473
+ await server . stop ( ) ;
1474
+ } ) ( ) ,
1475
+ ] ) ;
1476
+
1477
+ expect ( response . status ) . toBe ( 200 ) ;
1478
+ expect ( response . body ) . toStrictEqual ( { ok : 1 } ) ;
1479
+ // The server is about to be closed, we need to ask connections to close on their end (stop their keep-alive policies)
1480
+ expect ( response . header . connection ) . toBe ( 'close' ) ;
1481
+ } ) ;
1482
+
1483
+ test ( 'any requests triggered while stopping should be rejected with 503' , async ( ) => {
1484
+ const [ , , response ] = await Promise . all ( [
1485
+ // Trigger a request that should hold the server from stopping until fulfilled (otherwise the server will stop straight away)
1486
+ supertest ( innerServerListener ) . post ( '/' ) ,
1487
+ // Stop the server while the request is in progress
1488
+ ( async ( ) => {
1489
+ await new Promise ( ( resolve ) => setTimeout ( resolve , shutdownTimeout / 3 ) ) ;
1490
+ await server . stop ( ) ;
1491
+ } ) ( ) ,
1492
+ // Trigger a new request while shutting down (should be rejected)
1493
+ ( async ( ) => {
1494
+ await new Promise ( ( resolve ) => setTimeout ( resolve , ( 2 * shutdownTimeout ) / 3 ) ) ;
1495
+ return supertest ( innerServerListener ) . post ( '/' ) ;
1496
+ } ) ( ) ,
1497
+ ] ) ;
1498
+ expect ( response . status ) . toBe ( 503 ) ;
1499
+ expect ( response . body ) . toStrictEqual ( {
1500
+ statusCode : 503 ,
1501
+ error : 'Service Unavailable' ,
1502
+ message : 'Kibana is shutting down and not accepting new incoming requests' ,
1503
+ } ) ;
1504
+ expect ( response . header . connection ) . toBe ( 'close' ) ;
1505
+ } ) ;
1506
+
1507
+ test ( 'when no ongoing connections, the server should stop without waiting any longer' , async ( ) => {
1508
+ const preStop = Date . now ( ) ;
1509
+ await server . stop ( ) ;
1510
+ expect ( Date . now ( ) - preStop ) . toBeLessThan ( shutdownTimeout ) ;
1511
+ } ) ;
1512
+ } ) ;
0 commit comments