Bm_dev_mgr_remove_port can deadlock the BMI port manager when a packet arrives during the removal

Hi everyone,

On a switch that receives traffic, a Thrift bm_dev_mgr_remove_port sometimes never returns. Afterward the switch forwards nothing, and every later add_port, remove_port call hangs too. Only a restart recovers it.

Did someone already have a problem with that ? Do you have a safer way to remove an interface ?

def add_port_via_thrift(thrift_ip, thrift_port, iface_name, port_num, timeout=3.0):
    old_timeout = socket.getdefaulttimeout()
    socket.setdefaulttimeout(timeout)
    try:
        transport = TSocket.TSocket(thrift_ip, thrift_port)
        transport = TTransport.TBufferedTransport(transport)
        protocol = TBinaryProtocol.TBinaryProtocol(transport)
        multiplexed_protocol = TMultiplexedProtocol.TMultiplexedProtocol(protocol, "standard")
        client = Standard.Client(multiplexed_protocol)

        logger.info(f"[thrift] Opening connection for {iface_name}:{port_num}")
        transport.open()
        logger.info(f"[thrift] Connected, calling bm_dev_mgr_add_port for {iface_name}")
        try:
            client.bm_dev_mgr_add_port(iface_name, port_num, "")
            logger.info(f"[thrift] bm_dev_mgr_add_port done for {iface_name}")
        finally:
            transport.close()
    except Exception as e:
        logger.warning(f"[thrift] Failed for {iface_name}: {e}")
        raise
    finally:
        socket.setdefaulttimeout(old_timeout)

def remove_port_via_thrift(thrift_ip, thrift_port, port_num, timeout=3.0):
    old_timeout = socket.getdefaulttimeout()
    socket.setdefaulttimeout(timeout)
    try:
        transport = TSocket.TSocket(thrift_ip, thrift_port)
        transport = TTransport.TBufferedTransport(transport)
        protocol = TBinaryProtocol.TBinaryProtocol(transport)

        multiplexed_protocol = TMultiplexedProtocol.TMultiplexedProtocol(protocol, "standard")
        client = Standard.Client(multiplexed_protocol)

        logger.info(f"[thrift] Opening connection to remove port {port_num}")
        transport.open()
        logger.info(f"[thrift] Connected, calling bm_dev_mgr_remove_port for port {port_num}")
        client.bm_dev_mgr_remove_port(port_num)
        logger.info(f"Port {port_num} successfully removed via Thrift.")
    except Exception as e:
        logger.warning(f"[thrift] Failed to remove port {port_num}: {e}")
        raise
    finally:
        transport.close()
        socket.setdefaulttimeout(old_timeout)
#
# Autogenerated by Thrift Compiler (0.16.0)
#
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
#
#  options string: py
#


    def bm_dev_mgr_add_port(self, iface_name, port_num, pcap_path):
        """
        Parameters:
         - iface_name
         - port_num
         - pcap_path

        """
        self.send_bm_dev_mgr_add_port(iface_name, port_num, pcap_path)
        self.recv_bm_dev_mgr_add_port()

    def bm_dev_mgr_remove_port(self, port_num):
        """
        Parameters:
         - port_num

        """
        self.send_bm_dev_mgr_remove_port(port_num)
        self.recv_bm_dev_mgr_remove_port()