From 7647d5427164706f0d79bc881af91b49c63b22dd Mon Sep 17 00:00:00 2001 From: "nilesh.kale" Date: Mon, 23 Feb 2026 10:46:14 +0530 Subject: [PATCH] fix: solve OTA resumption CI failures This commit started threaded server to serve multiple clients simultaneouly in individual thread. --- .../advanced_https_ota/pytest_advanced_ota.py | 59 +++++++++++++++---- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/examples/system/ota/advanced_https_ota/pytest_advanced_ota.py b/examples/system/ota/advanced_https_ota/pytest_advanced_ota.py index 7a3d678235..3c269bbc2f 100644 --- a/examples/system/ota/advanced_https_ota/pytest_advanced_ota.py +++ b/examples/system/ota/advanced_https_ota/pytest_advanced_ota.py @@ -4,6 +4,7 @@ import http.server import multiprocessing import os import random +import socket import ssl import struct import subprocess @@ -40,12 +41,26 @@ def restart_device_with_random_delay(dut: Dut, min_delay: int = 10, max_delay: i print('Device restarted after random delay.') -def https_request_handler() -> Callable[..., http.server.BaseHTTPRequestHandler]: +def https_request_handler( + ssl_context: ssl.SSLContext | None = None, +) -> Callable[..., http.server.BaseHTTPRequestHandler]: """ - Returns a request handler class that handles broken pipe exception + Returns a request handler class that handles broken pipe exception. + If ssl_context is provided, SSL wrapping is done per-connection in the handler + thread (inside setup()) so that the server's main accept loop never blocks on + an SSL handshake — critical for OTA resumption tests where the device hard-resets + mid-connection. """ class RequestHandler(RangeRequestHandler): + def setup(self) -> None: + if ssl_context is not None: + sock: socket.socket = self.request # type: ignore[has-type] + sock.settimeout(10) # Timeout for SSL handshake + self.request = ssl_context.wrap_socket(sock, server_side=True) + self.request.settimeout(None) # Back to blocking mode for data transfer + super().setup() + def finish(self) -> None: try: if not self.wfile.closed: @@ -66,13 +81,15 @@ def https_request_handler() -> Callable[..., http.server.BaseHTTPRequestHandler] def start_https_server(ota_image_dir: str, server_ip: str, server_port: int) -> None: os.chdir(ota_image_dir) - requestHandler = https_request_handler() - httpd = http.server.HTTPServer((server_ip, server_port), requestHandler) ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) ssl_context.load_cert_chain(certfile=server_file, keyfile=key_file) - httpd.socket = ssl_context.wrap_socket(httpd.socket, server_side=True) + # Pass ssl_context to handler — SSL is done per-connection in handler threads, + # keeping the main accept loop on plain TCP (never blocks on SSL handshake) + requestHandler = https_request_handler(ssl_context) + httpd = http.server.ThreadingHTTPServer((server_ip, server_port), requestHandler) + httpd.daemon_threads = True # Handler threads won't block server shutdown httpd.serve_forever() @@ -94,12 +111,26 @@ def start_chunked_server(ota_image_dir: str, server_port: int) -> subprocess.Pop return chunked_server -def redirect_handler_factory(url: str) -> Callable[..., http.server.BaseHTTPRequestHandler]: +def redirect_handler_factory( + url: str, + ssl_context: ssl.SSLContext | None = None, +) -> Callable[..., http.server.BaseHTTPRequestHandler]: """ - Returns a request handler class that redirects to supplied `url` + Returns a request handler class that redirects to supplied `url`. + If ssl_context is provided, SSL wrapping is done per-connection in the handler + thread (inside setup()) so that the server's main accept loop never blocks on + an SSL handshake. """ class RedirectHandler(http.server.SimpleHTTPRequestHandler): + def setup(self) -> None: + if ssl_context is not None: + sock: socket.socket = self.request # type: ignore[has-type] + sock.settimeout(10) # Timeout for SSL handshake + self.request = ssl_context.wrap_socket(sock, server_side=True) + self.request.settimeout(None) # Back to blocking mode for data transfer + super().setup() + def do_GET(self) -> None: print('Sending resp, URL: ' + url) self.send_response(301) @@ -117,16 +148,18 @@ def redirect_handler_factory(url: str) -> Callable[..., http.server.BaseHTTPRequ def start_redirect_server(ota_image_dir: str, server_ip: str, server_port: int, redirection_port: int) -> None: os.chdir(ota_image_dir) - redirectHandler = redirect_handler_factory( - 'https://' + server_ip + ':' + str(redirection_port) + '/advanced_https_ota.bin' - ) - - httpd = http.server.HTTPServer((server_ip, server_port), redirectHandler) ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) ssl_context.load_cert_chain(certfile=server_file, keyfile=key_file) - httpd.socket = ssl_context.wrap_socket(httpd.socket, server_side=True) + # Pass ssl_context to handler — SSL is done per-connection in handler threads, + # keeping the main accept loop on plain TCP (never blocks on SSL handshake) + redirectHandler = redirect_handler_factory( + 'https://' + server_ip + ':' + str(redirection_port) + '/advanced_https_ota.bin', + ssl_context, + ) + httpd = http.server.ThreadingHTTPServer((server_ip, server_port), redirectHandler) + httpd.daemon_threads = True # Handler threads won't block server shutdown httpd.serve_forever()