Browse Source

[tcp] Gracefully close connections during shutdown

We currently do not wait for a received FIN before exiting to boot a
loaded OS.  In the common case of booting from an HTTP server, this
means that the TCP connection is left consuming resources on the
server side: the server will retransmit the FIN several times before
giving up.

Fix by initiating a graceful close of all TCP connections and waiting
(for up to one second) for all connections to finish closing
gracefully (i.e. for the outgoing FIN to have been sent and ACKed, and
for the incoming FIN to have been received and ACKed at least once).

Signed-off-by: Michael Brown <mcb30@ipxe.org>
tags/v1.20.1
Michael Brown 9 years ago
parent
commit
38afcc51ea
2 changed files with 63 additions and 1 deletions
  1. 7
    0
      src/include/ipxe/tcp.h
  2. 56
    1
      src/net/tcp.c

+ 7
- 0
src/include/ipxe/tcp.h View File

@@ -420,6 +420,13 @@ static inline int tcp_in_window ( uint32_t seq, uint32_t start,
420 420
 	return ( ( seq - start ) < len );
421 421
 }
422 422
 
423
+/** TCP finish wait time
424
+ *
425
+ * Currently set to one second, since we should not allow a slowly
426
+ * responding server to substantially delay a call to shutdown().
427
+ */
428
+#define TCP_FINISH_TIMEOUT ( 1 * TICKS_PER_SEC )
429
+
423 430
 extern struct tcpip_protocol tcp_protocol __tcpip_protocol;
424 431
 
425 432
 #endif /* _IPXE_TCP_H */

+ 56
- 1
src/net/tcp.c View File

@@ -1501,13 +1501,68 @@ struct cache_discarder tcp_discarder __cache_discarder ( CACHE_NORMAL ) = {
1501 1501
 	.discard = tcp_discard,
1502 1502
 };
1503 1503
 
1504
+/**
1505
+ * Find first TCP connection that has not yet been closed
1506
+ *
1507
+ * @ret tcp		First unclosed connection, or NULL
1508
+ */
1509
+static struct tcp_connection * tcp_first_unclosed ( void ) {
1510
+	struct tcp_connection *tcp;
1511
+
1512
+	/* Find first connection which has not yet been closed */
1513
+	list_for_each_entry ( tcp, &tcp_conns, list ) {
1514
+		if ( ! ( tcp->flags & TCP_XFER_CLOSED ) )
1515
+			return tcp;
1516
+	}
1517
+	return NULL;
1518
+}
1519
+
1520
+/**
1521
+ * Find first TCP connection that has not yet finished all operations
1522
+ *
1523
+ * @ret tcp		First unfinished connection, or NULL
1524
+ */
1525
+static struct tcp_connection * tcp_first_unfinished ( void ) {
1526
+	struct tcp_connection *tcp;
1527
+
1528
+	/* Find first connection which has not yet closed gracefully,
1529
+	 * or which still has a pending transmission (e.g. to ACK the
1530
+	 * received FIN).
1531
+	 */
1532
+	list_for_each_entry ( tcp, &tcp_conns, list ) {
1533
+		if ( ( ! TCP_CLOSED_GRACEFULLY ( tcp->tcp_state ) ) ||
1534
+		     process_running ( &tcp->process ) ) {
1535
+			return tcp;
1536
+		}
1537
+	}
1538
+	return NULL;
1539
+}
1540
+
1504 1541
 /**
1505 1542
  * Shut down all TCP connections
1506 1543
  *
1507 1544
  */
1508 1545
 static void tcp_shutdown ( int booting __unused ) {
1509 1546
 	struct tcp_connection *tcp;
1547
+	unsigned long start;
1548
+	unsigned long elapsed;
1549
+
1550
+	/* Initiate a graceful close of all connections, allowing for
1551
+	 * the fact that the connection list may change as we do so.
1552
+	 */
1553
+	while ( ( tcp = tcp_first_unclosed() ) ) {
1554
+		DBGC ( tcp, "TCP %p closing for shutdown\n", tcp );
1555
+		tcp_close ( tcp, -ECANCELED );
1556
+	}
1557
+
1558
+	/* Wait for all connections to finish closing gracefully */
1559
+	start = currticks();
1560
+	while ( ( tcp = tcp_first_unfinished() ) &&
1561
+		( ( elapsed = ( currticks() - start ) ) < TCP_FINISH_TIMEOUT )){
1562
+		step();
1563
+	}
1510 1564
 
1565
+	/* Forcibly close any remaining connections */
1511 1566
 	while ( ( tcp = list_first_entry ( &tcp_conns, struct tcp_connection,
1512 1567
 					   list ) ) != NULL ) {
1513 1568
 		tcp->tcp_state = TCP_CLOSED;
@@ -1517,7 +1572,7 @@ static void tcp_shutdown ( int booting __unused ) {
1517 1572
 }
1518 1573
 
1519 1574
 /** TCP shutdown function */
1520
-struct startup_fn tcp_startup_fn __startup_fn ( STARTUP_EARLY ) = {
1575
+struct startup_fn tcp_startup_fn __startup_fn ( STARTUP_LATE ) = {
1521 1576
 	.shutdown = tcp_shutdown,
1522 1577
 };
1523 1578
 

Loading…
Cancel
Save