4545import io .grpc .netty .shaded .io .netty .handler .ssl .util .SelfSignedCertificate ;
4646import io .grpc .netty .shaded .io .netty .util .AsciiString ;
4747import io .grpc .netty .shaded .io .netty .util .CharsetUtil ;
48+ import io .grpc .netty .shaded .io .netty .util .ReferenceCountUtil ;
4849
4950import java .io .IOException ;
5051import java .io .InputStream ;
@@ -77,7 +78,7 @@ public class ProxyAndTlsProtocolNegotiator implements InternalProtocolNegotiator
7778 */
7879 private static final int SSL_RECORD_HEADER_LENGTH = 5 ;
7980
80- private static SslContext sslContext ;
81+ private static volatile SslContext sslContext ;
8182
8283 public ProxyAndTlsProtocolNegotiator () {
8384 try {
@@ -113,9 +114,10 @@ public static void loadSslContext() throws CertificateException, IOException {
113114 provider = SslProvider .JDK ;
114115 log .info ("Using JDK SSL provider" );
115116 }
117+ SslContext newSslContext ;
116118 if (proxyConfig .isTlsTestModeEnable ()) {
117119 SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate ();
118- sslContext = GrpcSslContexts .forServer (selfSignedCertificate .certificate (), selfSignedCertificate .privateKey ())
120+ newSslContext = GrpcSslContexts .forServer (selfSignedCertificate .certificate (), selfSignedCertificate .privateKey ())
119121 .sslProvider (provider )
120122 .trustManager (InsecureTrustManagerFactory .INSTANCE )
121123 .clientAuth (ClientAuth .NONE )
@@ -128,14 +130,32 @@ public static void loadSslContext() throws CertificateException, IOException {
128130 Paths .get (tlsKeyPath ));
129131 InputStream serverCertificateStream = Files .newInputStream (
130132 Paths .get (tlsCertPath ))) {
131- sslContext = GrpcSslContexts .forServer (serverCertificateStream ,
133+ newSslContext = GrpcSslContexts .forServer (serverCertificateStream ,
132134 serverKeyInputStream ,
133135 StringUtils .isNotBlank (tlsKeyPassword ) ? tlsKeyPassword : null )
134136 .trustManager (InsecureTrustManagerFactory .INSTANCE )
135137 .clientAuth (ClientAuth .NONE )
136138 .build ();
137139 }
138140 }
141+ SslContext oldSslContext = sslContext ;
142+ sslContext = newSslContext ;
143+ if (oldSslContext != null ) {
144+ // Release the old SslContext to free native memory (OpenSSL provider only).
145+ // ReferenceCountUtil.release() is a no-op for JDK SslContext since it does not
146+ // implement ReferenceCounted.
147+ // Note: there is a theoretical race where an event-loop thread could read the old
148+ // sslContext (volatile) and call newHandler() after release. In practice this is
149+ // negligible because cert reload is very infrequent and the window is nanoseconds.
150+ // Worst case: the single new connection gets an IllegalReferenceCountException and
151+ // the client retries successfully — no pod crash or service disruption.
152+ try {
153+ ReferenceCountUtil .release (oldSslContext );
154+ log .info ("Old SslContext released for proxy server" );
155+ } catch (Exception e ) {
156+ log .warn ("Failed to release old SslContext for proxy server" , e );
157+ }
158+ }
139159 }
140160
141161 private class ProxyAndTlsProtocolHandler extends ByteToMessageDecoder {
0 commit comments