@@ -2805,49 +2805,112 @@ mod _ssl {
28052805 let stream = self . connection . read ( ) ;
28062806 let ssl_ptr = stream. ssl ( ) . as_ptr ( ) ;
28072807
2808- // Perform SSL shutdown - may need to be called twice:
2809- // 1st call: sends close-notify, returns 0
2810- // 2nd call: reads peer's close-notify, returns 1
2811- let mut ret = unsafe { sys:: SSL_shutdown ( ssl_ptr) } ;
2812-
2813- // If ret == 0, try once more to complete the bidirectional shutdown
2814- // This handles the case where peer's close-notify is already available
2815- if ret == 0 {
2816- ret = unsafe { sys:: SSL_shutdown ( ssl_ptr) } ;
2808+ // BIO mode: just try shutdown once and raise SSLWantReadError if needed
2809+ if stream. is_bio ( ) {
2810+ let ret = unsafe { sys:: SSL_shutdown ( ssl_ptr) } ;
2811+ if ret < 0 {
2812+ let err = unsafe { sys:: SSL_get_error ( ssl_ptr, ret) } ;
2813+ if err == sys:: SSL_ERROR_WANT_READ {
2814+ return Err ( create_ssl_want_read_error ( vm) . upcast ( ) ) ;
2815+ } else if err == sys:: SSL_ERROR_WANT_WRITE {
2816+ return Err ( create_ssl_want_write_error ( vm) . upcast ( ) ) ;
2817+ } else {
2818+ return Err ( new_ssl_error (
2819+ vm,
2820+ format ! ( "SSL shutdown failed: error code {}" , err) ,
2821+ ) ) ;
2822+ }
2823+ } else if ret == 0 {
2824+ // Sent close-notify, waiting for peer's - raise SSLWantReadError
2825+ return Err ( create_ssl_want_read_error ( vm) . upcast ( ) ) ;
2826+ }
2827+ return Ok ( None ) ;
28172828 }
28182829
2819- if ret < 0 {
2820- // Error occurred
2830+ // Socket mode: loop with select to wait for peer's close-notify
2831+ let socket_stream = stream. get_ref ( ) . expect ( "get_ref() failed for socket mode" ) ;
2832+ let deadline = socket_stream. timeout_deadline ( ) ;
2833+
2834+ // Track how many times we've seen ret == 0 (max 2 tries)
2835+ let mut zeros = 0 ;
2836+
2837+ loop {
2838+ let ret = unsafe { sys:: SSL_shutdown ( ssl_ptr) } ;
2839+
2840+ // ret > 0: complete shutdown
2841+ if ret > 0 {
2842+ break ;
2843+ }
2844+
2845+ // ret == 0: sent our close-notify, need to receive peer's
2846+ if ret == 0 {
2847+ zeros += 1 ;
2848+ if zeros > 1 {
2849+ // Already tried twice, break out (legacy behavior)
2850+ break ;
2851+ }
2852+ // Wait briefly for peer's close_notify before retrying
2853+ match socket_stream. select ( SslNeeds :: Read , & deadline) {
2854+ SelectRet :: TimedOut => break , // Timeout waiting for peer
2855+ SelectRet :: Closed => break , // Socket closed
2856+ SelectRet :: Nonblocking => {
2857+ // Non-blocking, just continue
2858+ }
2859+ SelectRet :: Ok => {
2860+ // Data available, continue to retry
2861+ }
2862+ }
2863+ continue ;
2864+ }
2865+
2866+ // ret < 0: error or would-block
28212867 let err = unsafe { sys:: SSL_get_error ( ssl_ptr, ret) } ;
28222868
2823- if err == sys:: SSL_ERROR_WANT_READ {
2824- return Err ( create_ssl_want_read_error ( vm ) . upcast ( ) ) ;
2869+ let needs = if err == sys:: SSL_ERROR_WANT_READ {
2870+ SslNeeds :: Read
28252871 } else if err == sys:: SSL_ERROR_WANT_WRITE {
2826- return Err ( create_ssl_want_write_error ( vm ) . upcast ( ) ) ;
2872+ SslNeeds :: Write
28272873 } else {
2874+ // Real error
28282875 return Err ( new_ssl_error (
28292876 vm,
28302877 format ! ( "SSL shutdown failed: error code {}" , err) ,
28312878 ) ) ;
2832- }
2833- } else if ret == 0 {
2834- // Still waiting for peer's close-notify after retry
2835- // In BIO mode, raise SSLWantReadError
2836- if stream. is_bio ( ) {
2837- return Err ( create_ssl_want_read_error ( vm) . upcast ( ) ) ;
2838- }
2839- }
2879+ } ;
28402880
2841- // BIO mode doesn't have an underlying socket to return
2842- if stream. is_bio ( ) {
2843- return Ok ( None ) ;
2881+ // Wait on the socket
2882+ match socket_stream. select ( needs, & deadline) {
2883+ SelectRet :: TimedOut => {
2884+ let msg = if err == sys:: SSL_ERROR_WANT_READ {
2885+ "The read operation timed out"
2886+ } else {
2887+ "The write operation timed out"
2888+ } ;
2889+ return Err ( vm. new_exception_msg (
2890+ vm. ctx . exceptions . timeout_error . to_owned ( ) ,
2891+ msg. to_owned ( ) ,
2892+ ) ) ;
2893+ }
2894+ SelectRet :: Closed => {
2895+ return Err ( socket_closed_error ( vm) ) ;
2896+ }
2897+ SelectRet :: Nonblocking => {
2898+ // Non-blocking socket, raise SSLWantReadError/SSLWantWriteError
2899+ if err == sys:: SSL_ERROR_WANT_READ {
2900+ return Err ( create_ssl_want_read_error ( vm) . upcast ( ) ) ;
2901+ } else {
2902+ return Err ( create_ssl_want_write_error ( vm) . upcast ( ) ) ;
2903+ }
2904+ }
2905+ SelectRet :: Ok => {
2906+ // Socket is ready, retry shutdown
2907+ continue ;
2908+ }
2909+ }
28442910 }
28452911
2846- // Return the underlying socket for socket mode
2847- let socket = stream
2848- . get_ref ( )
2849- . expect ( "unwrap() called on bio mode; should only be called in socket mode" ) ;
2850- Ok ( Some ( socket. 0 . clone ( ) ) )
2912+ // Return the underlying socket
2913+ Ok ( Some ( socket_stream. 0 . clone ( ) ) )
28512914 }
28522915
28532916 #[ cfg( osslconf = "OPENSSL_NO_COMP" ) ]
0 commit comments