fix(examples): solve OTA resumption CI failures (backport v6.0)

This commit started threaded server to serve multiple clients
simultaneously in individual thread, eliminating the single-threaded
server limitation that caused the device's mid-OTA reconnect to time
out while the previous connection was still being torn down.

SSL is now wrapped per-connection inside the handler thread (in
setup()) so the main accept loop never blocks on a TLS handshake.

Closes IDFCI-3505

(cherry picked from commit 7647d54271)
This commit is contained in:
nilesh.kale
2026-02-23 10:46:14 +05:30
committed by Hrushikesh Bhosale
parent bd324b3311
commit 62a67bbcb8

View File

@@ -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()