From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 1633745527; Fri, 28 Jun 2024 19:33:18 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id AED1C42E4D; Fri, 28 Jun 2024 19:33:06 +0200 (CEST) Received: from mail-pj1-f104.google.com (mail-pj1-f104.google.com [209.85.216.104]) by mails.dpdk.org (Postfix) with ESMTP id F0AAC42DD1 for ; Fri, 28 Jun 2024 19:33:04 +0200 (CEST) Received: by mail-pj1-f104.google.com with SMTP id 98e67ed59e1d1-2c9015b075bso1479709a91.1 for ; Fri, 28 Jun 2024 10:33:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=iol.unh.edu; s=unh-iol; t=1719595984; x=1720200784; darn=dpdk.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=IeJg9gru6WX8U01EJAZU96Ll5tGoQLj0vtvSH9/uA4k=; b=V6lBnXLzcu3xANHKFLkxZK/8J+o/yzqZMNzzD1JCrSJI6ogW/EdlqR+3zQzSIV1bxH TIfLVZ1c1Y3mt4GPipl3D3swF94Oq51l/YKdJUR9R/7as5L+fTBqlHtX1dqdx9iqoA6W h9/3wArzDPDBTpo255hxCAl1X1MqihoopqaYM= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1719595984; x=1720200784; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=IeJg9gru6WX8U01EJAZU96Ll5tGoQLj0vtvSH9/uA4k=; b=no1s9mY0swpUPORwyF2aWiDa29NdAKbmqe1z4+KiuDMRopfu0KiDmtdZoUY0vJVbd9 hwofpV2vm9okmcyE10Kkn7zfvLRYsUK+iodaJ1lgu0yq4oesapGHh1jRwoJH5wd1ayrH ZiLuHN1MJWLYWPQbyla8DJEvNiZZG7CFXjKlvx7sMTOgRLbVYR1gjtVv1WQHlgtq7kQv Bfh2RJqgi9Ur3SIoY5XjFVRpaT0TsulHOpJJIZMtxF/BE1ALn5NqLiNi8b9ylZi1Ag52 2JJ4hEIJLr09aNjqnoSFMS0M8sdz403AkDulAjW4OwjWEMAWiC1phkbbj98a5QzBDiWS fXbg== X-Gm-Message-State: AOJu0YzRKukJ8W30HMFl7UKn4WzuLaPM6zhsLiwoXpst/ZoAmchhTYIi g4mpNEngcZvdvAUpnk8Qvq6cJ4G6kn9b2CJAGkv0T+aluQ9J6KG+3LWoZi+fcz5UjFV+5c/Xhvs e2T6tGaenrO7FMM/n2w5gDteXsXi5uPNwX9tEoN0oZpRxXMRj X-Google-Smtp-Source: AGHT+IHXEdr0SPxJhQnaaEdAl7JFpSFf1z4V94s3uSNnNQzYKuPaNAvisIywkBAwkv8Jg3GM+zLg99+IL9CJ X-Received: by 2002:a17:90a:e289:b0:2c2:ee8e:ceff with SMTP id 98e67ed59e1d1-2c92815618cmr3068684a91.24.1719595983945; Fri, 28 Jun 2024 10:33:03 -0700 (PDT) Received: from postal.iol.unh.edu (postal.iol.unh.edu. [2606:4100:3880:1234::84]) by smtp-relay.gmail.com with ESMTPS id 98e67ed59e1d1-2c91d3cfac5sm107901a91.17.2024.06.28.10.33.03 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 28 Jun 2024 10:33:03 -0700 (PDT) X-Relaying-Domain: iol.unh.edu Received: from iol.unh.edu (unknown [IPv6:2606:4100:3880:1257::1083]) by postal.iol.unh.edu (Postfix) with ESMTP id B1EFB605C373; Fri, 28 Jun 2024 13:33:02 -0400 (EDT) From: jspewock@iol.unh.edu To: paul.szczepanek@arm.com, npratte@iol.unh.edu, juraj.linkes@pantheon.tech, wathsala.vithanage@arm.com, Honnappa.Nagarahalli@arm.com, yoan.picchi@foss.arm.com, thomas@monjalon.net, Luca.Vizzarro@arm.com, probb@iol.unh.edu Cc: dev@dpdk.org, Jeremy Spewock Subject: [PATCH v6 2/4] dts: improve starting and stopping interactive shells Date: Fri, 28 Jun 2024 13:32:30 -0400 Message-ID: <20240628173232.22906-3-jspewock@iol.unh.edu> X-Mailer: git-send-email 2.45.2 In-Reply-To: <20240628173232.22906-1-jspewock@iol.unh.edu> References: <20240514201436.2496-1-jspewock@iol.unh.edu> <20240628173232.22906-1-jspewock@iol.unh.edu> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org From: Jeremy Spewock The InteractiveShell class currently relies on being cleaned up and shutdown at the time of garbage collection, but this cleanup of the class does no verification that the session is still running prior to cleanup. So, if a user were to call this method themselves prior to garbage collection, it would be called twice and throw an exception when the desired behavior is to do nothing since the session is already cleaned up. This is solved by using a weakref and a finalize class which achieves the same result of calling the method at garbage collection, but also ensures that it is called exactly once. Additionally, this fixes issues regarding starting a primary DPDK application while another is still cleaning up via a retry when starting interactive shells. It also adds catch for attempting to send a command to an interactive shell that is not running to create a more descriptive error message. Signed-off-by: Jeremy Spewock --- .../remote_session/interactive_shell.py | 29 +++++++++++----- .../single_active_interactive_shell.py | 34 +++++++++++++++++-- dts/framework/remote_session/testpmd_shell.py | 2 +- 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/dts/framework/remote_session/interactive_shell.py b/dts/framework/remote_session/interactive_shell.py index 9d124b8245..615843a826 100644 --- a/dts/framework/remote_session/interactive_shell.py +++ b/dts/framework/remote_session/interactive_shell.py @@ -8,6 +8,9 @@ collection. """ +import weakref +from typing import ClassVar + from .single_active_interactive_shell import SingleActiveInteractiveShell @@ -15,18 +18,26 @@ class InteractiveShell(SingleActiveInteractiveShell): """Adds manual start and stop functionality to interactive shells. Like its super-class, this class should not be instantiated directly and should instead be - extended. This class also provides an option for automated cleanup of the application through - the garbage collector. + extended. This class also provides an option for automated cleanup of the application using a + weakref and a finalize class. This finalize class allows for cleanup of the class at the time + of garbage collection and also ensures that cleanup only happens once. This way if a user + initiates the closing of the shell manually it is not repeated at the time of garbage + collection. """ + _finalizer: weakref.finalize + #: Shells that do not require only one instance to be running shouldn't need more than 1 + #: attempt to start. + _init_attempts: ClassVar[int] = 1 + def start_application(self) -> None: - """Start the application.""" + """Start the application. + + After the application has started, add a weakref finalize class to manage cleanup. + """ self._start_application(self._get_privileged_command) + self._finalizer = weakref.finalize(self, self._close) def close(self) -> None: - """Properly free all resources.""" - self._close() - - def __del__(self) -> None: - """Make sure the session is properly closed before deleting the object.""" - self.close() + """Free all resources using finalize class.""" + self._finalizer() diff --git a/dts/framework/remote_session/single_active_interactive_shell.py b/dts/framework/remote_session/single_active_interactive_shell.py index 74060be8a7..282ceec483 100644 --- a/dts/framework/remote_session/single_active_interactive_shell.py +++ b/dts/framework/remote_session/single_active_interactive_shell.py @@ -44,6 +44,10 @@ class SingleActiveInteractiveShell(ABC): Interactive shells are started and stopped using a context manager. This allows for the start and cleanup of the application to happen at predictable times regardless of exceptions or interrupts. + + Attributes: + is_alive: :data:`True` if the application has started successfully, :data:`False` + otherwise. """ _interactive_session: SSHClient @@ -55,6 +59,9 @@ class SingleActiveInteractiveShell(ABC): _app_args: str _get_privileged_command: Callable[[str], str] | None + #: The number of times to try starting the application before considering it a failure. + _init_attempts: ClassVar[int] = 5 + #: Prompt to expect at the end of output when sending a command. #: This is often overridden by subclasses. _default_prompt: ClassVar[str] = "" @@ -71,6 +78,8 @@ class SingleActiveInteractiveShell(ABC): #: for DPDK on the node will be prepended to the path to the executable. dpdk_app: ClassVar[bool] = False + is_alive: bool = False + def __init__( self, interactive_session: SSHClient, @@ -110,17 +119,34 @@ def _start_application(self, get_privileged_command: Callable[[str], str] | None This method is often overridden by subclasses as their process for starting may look different. A new SSH channel is initialized for the application to run on, then the - application is started. + application is started. Initialization of the shell on the host can be retried up to + `self._init_attempts` - 1 times. This is done because some DPDK applications need slightly + more time after exiting their script to clean up EAL before others can start. Args: get_privileged_command: A function (but could be any callable) that produces the version of the command with elevated privileges. """ self._init_channel() + self._ssh_channel.settimeout(5) start_command = f"{self.path} {self._app_args}" if get_privileged_command is not None: start_command = get_privileged_command(start_command) - self.send_command(start_command) + self.is_alive = True + for attempt in range(self._init_attempts): + try: + self.send_command(start_command) + break + except TimeoutError: + self._logger.info( + f"Interactive shell failed to start (attempt {attempt+1} out of " + f"{self._init_attempts})" + ) + else: + self._ssh_channel.settimeout(self._timeout) + self.is_alive = False # update state on failure to start + raise InteractiveCommandExecutionError("Failed to start application.") + self._ssh_channel.settimeout(self._timeout) def send_command(self, command: str, prompt: str | None = None) -> str: """Send `command` and get all output before the expected ending string. @@ -142,6 +168,10 @@ def send_command(self, command: str, prompt: str | None = None) -> str: Returns: All output in the buffer before expected string. """ + if not self.is_alive: + raise InteractiveCommandExecutionError( + f"Cannot send command {command} to application because the shell is not running." + ) self._logger.info(f"Sending: '{command}'") if prompt is None: prompt = self._default_prompt diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py index 17561d4dae..805bb3a77d 100644 --- a/dts/framework/remote_session/testpmd_shell.py +++ b/dts/framework/remote_session/testpmd_shell.py @@ -230,7 +230,7 @@ def set_forward_mode(self, mode: TestPmdForwardingModes, verify: bool = True): def _close(self) -> None: """Overrides :meth:`~.interactive_shell.close`.""" self.stop() - self.send_command("quit", "") + self.send_command("quit", "Bye...") return super()._close() def get_capas_rxq( -- 2.45.2