@@ -1306,6 +1306,7 @@ void us_internal_ssl_attach(struct us_socket_t *s, SSL_CTX *ctx,
13061306 s -> ssl_fatal_error = 0 ;
13071307 s -> ssl_raw_tap = 0 ;
13081308 s -> ssl_shutdown_after_spill = 0 ;
1309+ s -> ssl_close_after_spill = 0 ;
13091310 s -> ssl_in_use = 0 ;
13101311 s -> ssl_pending_detach = 0 ;
13111312 s -> ssl_pending_close_code = 0 ;
@@ -1549,17 +1550,30 @@ static int ssl_handle_shutdown(struct us_socket_t *s, int force_fast_shutdown) {
15491550}
15501551
15511552struct us_socket_t * us_internal_ssl_close (struct us_socket_t * s , int code , void * reason ) {
1552- ssl_release_spill (s -> group -> loop , s );
15531553 if (s -> ssl && s -> ssl_in_use ) {
15541554 /* A JS callback running from inside SSL_do_handshake/SSL_read (ALPN, SNI,
15551555 * keylog, ...) destroyed this socket. Reaching ssl_set_loop_data /
15561556 * SSL_do_handshake here would re-enter BoringSSL on the same SSL* while
15571557 * the outer ssl_run_handshake is still on the stack; defer to the SSL
1558- * driver's epilogue (the same protocol close_raw and ssl_detach honor). */
1558+ * driver's epilogue (the same protocol close_raw and ssl_detach honor),
1559+ * releasing the spill now so the re-issued close cannot itself defer. */
1560+ ssl_release_spill (s -> group -> loop , s );
15591561 s -> ssl_pending_detach = 1 ;
15601562 s -> ssl_pending_close_code = (unsigned char ) code ;
15611563 return s ;
15621564 }
1565+ /* node's `_handle.close()` (FAST_SHUTDOWN, no reason) must not cut off spilled
1566+ * ciphertext already reported as written: SSL sealed it, so it can only be
1567+ * delivered, never re-sent. Mirror ssl_shutdown_after_spill; defer at most once. */
1568+ if (code == LIBUS_SOCKET_CLOSE_CODE_FAST_SHUTDOWN && !reason
1569+ && !s -> ssl_close_after_spill && !s -> ssl_fatal_error && !us_socket_is_closed (s )) {
1570+ struct loop_ssl_data * loop_ssl_data = (struct loop_ssl_data * )s -> group -> loop -> data .ssl_data ;
1571+ if (loop_ssl_data && !ssl_drain_spill (loop_ssl_data , s )) {
1572+ s -> ssl_close_after_spill = 1 ;
1573+ return s ;
1574+ }
1575+ }
1576+ ssl_release_spill (s -> group -> loop , s );
15631577 /* SEMI_SOCKET never connected — SSL was attached eagerly on the fast-path
15641578 * connect, but no bytes were ever exchanged. Firing on_handshake(0) here
15651579 * lands in JS after onConnectError already tore down `this`/its handlers. */
@@ -1722,6 +1736,10 @@ struct us_socket_t *us_internal_ssl_on_writable(struct us_socket_t *s) {
17221736 us_internal_ssl_shutdown (s );
17231737 if (ssl_gone (s )) return s ;
17241738 }
1739+ if (s -> ssl_close_after_spill ) {
1740+ s -> ssl_close_after_spill = 0 ;
1741+ return us_internal_ssl_close (s , LIBUS_SOCKET_CLOSE_CODE_FAST_SHUTDOWN , NULL );
1742+ }
17251743 }
17261744 ssl_update_handshake (s );
17271745 if (ssl_gone (s )) return s ;
0 commit comments