]> Repositorios git - scryer-prolog.git/commitdiff
tls: release connection state on close and cache ServerConfig js/fixes origin/js/fixes
authorJavier Sagredo <[email protected]>
Sun, 31 May 2026 23:40:01 +0000 (01:40 +0200)
committerJavier Sagredo <[email protected]>
Sun, 31 May 2026 23:40:01 +0000 (01:40 +0200)
Closing a TLS or TCP stream previously only shut down the socket /
sent close_notify without dropping the arena-allocated payload, so the
rustls ServerConnection (and its buffers) lingered until the arena was
garbage-collected. For a server negotiating a fresh TLS connection per
request, this caused resident memory to climb steadily. Call
drop_payload() in Stream::close for the NamedTcp and NamedTls variants
so the underlying resources are freed immediately, matching the file,
byte and pipe stream variants.

Additionally, tls_accept_client rebuilt a ServerConfig on every
connection, re-parsing the certificate chain and private key each time.
Cache the most recently built config keyed by the raw cert/key bytes
and reuse it, avoiding the repeated parse and allocation.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
src/machine/streams.rs
src/machine/system_calls.rs

index 3f6adc386111a965f6fa98c2fb5eb45ac7bfd657..207b6cff7adbc9325007ee64e16fa2fe27a81b00 100644 (file)
@@ -1586,13 +1586,22 @@ impl Stream {
     pub(crate) fn close(&mut self) -> Result<(), std::io::Error> {
         match self {
             Stream::NamedTcp(ref mut tcp_stream) => {
-                tcp_stream.inner_mut().tcp_stream.shutdown(Shutdown::Both)
+                let result = tcp_stream.inner_mut().tcp_stream.shutdown(Shutdown::Both);
+                // Release the underlying socket immediately instead of waiting
+                // for the arena to be garbage-collected.
+                tcp_stream.drop_payload();
+                result
             }
             #[cfg(feature = "tls")]
             Stream::NamedTls(ref mut tls_stream) => {
                 let inner = tls_stream.inner_mut();
                 inner.tls_stream.send_close_notify();
-                inner.tls_stream.flush()
+                let result = inner.tls_stream.flush();
+                // Release the rustls connection state (buffers, ServerConfig)
+                // immediately instead of waiting for the arena to be
+                // garbage-collected.
+                tls_stream.drop_payload();
+                result
             }
             #[cfg(feature = "http")]
             Stream::HttpRead(ref mut http_stream) => {
index ffd7a29f5c0005e122bb0c46dc5e7da2e62b2b65..8dba07c715031b210b24fbb06a7f4199e2b9acea 100644 (file)
@@ -7293,32 +7293,19 @@ impl Machine {
     #[cfg(feature = "tls")]
     #[inline(always)]
     pub(crate) fn tls_accept_client(&mut self) -> CallResult {
-        let cert_pem = self.string_encoding_bytes(self.machine_st.registers[1], atom!("octet"));
-        let key_pem = self.string_encoding_bytes(self.machine_st.registers[2], atom!("octet"));
+        use std::cell::RefCell;
 
-        let cert_chain: Vec<CertificateDer<'static>> =
-            match rustls_pemfile::certs(&mut &cert_pem[..]).collect::<Result<Vec<_>, _>>() {
-                Ok(certs) if !certs.is_empty() => certs,
-                _ => {
-                    return Err(self.machine_st.open_permission_error(
-                        self.machine_st.registers[1],
-                        atom!("tls_server_negotiate"),
-                        3,
-                    ));
-                }
-            };
+        // A new TLS connection is negotiated for every incoming request. Building
+        // a fresh ServerConfig each time means re-parsing the certificate chain
+        // and (potentially large) private key on every connection. Cache the most
+        // recently built config, keyed by the raw cert/key bytes, and reuse it.
+        thread_local! {
+            static CONFIG_CACHE: RefCell<Option<(Vec<u8>, Vec<u8>, Arc<ServerConfig>)>> =
+                RefCell::new(None);
+        }
 
-        let private_key: PrivateKeyDer<'static> =
-            match rustls_pemfile::private_key(&mut &key_pem[..]) {
-                Ok(Some(key)) => key,
-                _ => {
-                    return Err(self.machine_st.open_permission_error(
-                        self.machine_st.registers[2],
-                        atom!("tls_server_negotiate"),
-                        3,
-                    ));
-                }
-            };
+        let cert_pem = self.string_encoding_bytes(self.machine_st.registers[1], atom!("octet"));
+        let key_pem = self.string_encoding_bytes(self.machine_st.registers[2], atom!("octet"));
 
         let stream0 = self.machine_st.get_stream_or_alias(
             self.machine_st.registers[3],
@@ -7327,21 +7314,63 @@ impl Machine {
             3,
         )?;
 
-        let config = match ServerConfig::builder()
-            .with_client_cert_verifier(Arc::new(GeminiClientVerifier))
-            .with_single_cert(cert_chain, private_key)
-        {
-            Ok(config) => config,
-            Err(_) => {
-                return Err(self.machine_st.open_permission_error(
-                    self.machine_st.registers[1],
-                    atom!("tls_server_negotiate"),
-                    3,
-                ));
+        let cached_config = CONFIG_CACHE.with(|cache| {
+            cache.borrow().as_ref().and_then(|(cert, key, config)| {
+                (cert == &cert_pem && key == &key_pem).then(|| config.clone())
+            })
+        });
+
+        let config = match cached_config {
+            Some(config) => config,
+            None => {
+                let cert_chain: Vec<CertificateDer<'static>> =
+                    match rustls_pemfile::certs(&mut &cert_pem[..]).collect::<Result<Vec<_>, _>>() {
+                        Ok(certs) if !certs.is_empty() => certs,
+                        _ => {
+                            return Err(self.machine_st.open_permission_error(
+                                self.machine_st.registers[1],
+                                atom!("tls_server_negotiate"),
+                                3,
+                            ));
+                        }
+                    };
+
+                let private_key: PrivateKeyDer<'static> =
+                    match rustls_pemfile::private_key(&mut &key_pem[..]) {
+                        Ok(Some(key)) => key,
+                        _ => {
+                            return Err(self.machine_st.open_permission_error(
+                                self.machine_st.registers[2],
+                                atom!("tls_server_negotiate"),
+                                3,
+                            ));
+                        }
+                    };
+
+                let config = match ServerConfig::builder()
+                    .with_client_cert_verifier(Arc::new(GeminiClientVerifier))
+                    .with_single_cert(cert_chain, private_key)
+                {
+                    Ok(config) => Arc::new(config),
+                    Err(_) => {
+                        return Err(self.machine_st.open_permission_error(
+                            self.machine_st.registers[1],
+                            atom!("tls_server_negotiate"),
+                            3,
+                        ));
+                    }
+                };
+
+                CONFIG_CACHE.with(|cache| {
+                    *cache.borrow_mut() =
+                        Some((cert_pem.clone(), key_pem.clone(), config.clone()));
+                });
+
+                config
             }
         };
 
-        let mut conn = match ServerConnection::new(Arc::new(config)) {
+        let mut conn = match ServerConnection::new(config) {
             Ok(conn) => conn,
             Err(_) => {
                 return Err(self.machine_st.open_permission_error(