@@ -388,6 +388,24 @@ func (e *Etcd) Config() Config {
388
388
// Close gracefully shuts down all servers/listeners.
389
389
// Client requests will be terminated with request timeout.
390
390
// After timeout, enforce remaning requests be closed immediately.
391
+ //
392
+ // The rough workflow to shut down etcd:
393
+ // 1. close the `stopc` channel, so that all error handlers (child
394
+ // goroutines) won't send back any errors anymore;
395
+ // 2. close all client and metrics listeners, so that etcd server
396
+ // stops receiving any new connection immediately;
397
+ // 3. stop the http and grpc servers gracefully, within request timeout;
398
+ // 4. call the cancel function to close the gateway context, so that
399
+ // all gateway connections are closed.
400
+ // 5. stop etcd server gracefully, and ensure the main raft loop
401
+ // goroutine is stopped;
402
+ // 6. stop all peer listeners, so that it stops receives peer connections
403
+ // and messages (wait up to 1-second);
404
+ // 7. wait for all child goroutines (i.e. client handlers, peer handlers
405
+ // and metrics handlers) to exit;
406
+ // 8. close the `errc` channel to release the resource. Note that it's only
407
+ // safe to close the `errc` after step 6 above is done, otherwise the
408
+ // child goroutines may send errors back to already closed `errc` channel.
391
409
func (e * Etcd ) Close () {
392
410
fields := []zap.Field {
393
411
zap .String ("name" , e .cfg .Name ),
@@ -407,10 +425,14 @@ func (e *Etcd) Close() {
407
425
lg .Sync ()
408
426
}()
409
427
428
+ // 1. close the `stopc` channel, so that all error handlers (child
429
+ // goroutines) won't send back any errors anymore;
410
430
e .closeOnce .Do (func () {
411
431
close (e .stopc )
412
432
})
413
433
434
+ // 2. close all client and metrics listeners, so that etcd server
435
+ // stops receiving any new connection immediately;
414
436
for i := range e .Clients {
415
437
if e .Clients [i ] != nil {
416
438
e .Clients [i ].Close ()
@@ -421,7 +443,7 @@ func (e *Etcd) Close() {
421
443
e .metricsListeners [i ].Close ()
422
444
}
423
445
424
- // close client requests with request timeout
446
+ // 3. stop the http and grpc servers gracefully, within request timeout;
425
447
timeout := 2 * time .Second
426
448
if e .Server != nil {
427
449
timeout = e .Server .Cfg .ReqTimeout ()
@@ -434,6 +456,8 @@ func (e *Etcd) Close() {
434
456
}
435
457
}
436
458
459
+ // 4. call the cancel function to close the gateway context, so that
460
+ // all gateway connections are closed.
437
461
for _ , sctx := range e .sctxs {
438
462
sctx .cancel ()
439
463
}
@@ -443,12 +467,14 @@ func (e *Etcd) Close() {
443
467
e .tracingExporterShutdown ()
444
468
}
445
469
446
- // close rafthttp transports
470
+ // 5. stop etcd server gracefully, and ensure the main raft loop
471
+ // goroutine is stopped;
447
472
if e .Server != nil {
448
473
e .Server .Stop ()
449
474
}
450
475
451
- // close all idle connections in peer handler (wait up to 1-second)
476
+ // 6. stop all peer listeners, so that it stops receives peer connections
477
+ // and messages (wait up to 1-second);
452
478
for i := range e .Peers {
453
479
if e .Peers [i ] != nil && e .Peers [i ].close != nil {
454
480
ctx , cancel := context .WithTimeout (context .Background (), time .Second )
@@ -457,7 +483,13 @@ func (e *Etcd) Close() {
457
483
}
458
484
}
459
485
if e .errc != nil {
486
+ // 7. wait for all child goroutines (i.e. client handlers, peer handlers
487
+ // and metrics handlers) to exit;
460
488
e .wg .Wait ()
489
+
490
+ // 8. close the `errc` channel to release the resource. Note that it's only
491
+ // safe to close the `errc` after step 6 above is done, otherwise the
492
+ // child goroutines may send errors back to already closed `errc` channel.
461
493
close (e .errc )
462
494
}
463
495
}
0 commit comments