From 4925772397a76d745014e39d189010fbcdf326d0 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Tue, 18 Jun 2024 18:12:04 +0200 Subject: [PATCH] [Python] Add locking to prevent concurrent access with asyncio Make sure that different asyncio tasks do not run the same function concurrently. This is done by adding an asyncio lock to functions which use callbacks. --- src/controller/python/chip/ChipDeviceCtrl.py | 52 +++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py index 1ca5a64e7cf198..cdd6c1a0bcc7f7 100644 --- a/src/controller/python/chip/ChipDeviceCtrl.py +++ b/src/controller/python/chip/ChipDeviceCtrl.py @@ -227,33 +227,36 @@ def wrapper(*args, **kwargs): class CallbackContext: - def __init__(self) -> None: + def __init__(self, lock: asyncio.Lock) -> None: + self._lock = lock self._future = None - def __enter__(self): + async def __aenter__(self): + await self._lock.acquire() self._future = concurrent.futures.Future() return self @property - def future(self) -> concurrent.futures.Future | None: + def future(self) -> typing.Optional[concurrent.futures.Future]: return self._future - def __exit__(self, exc_type, exc_value, traceback): + async def __aexit__(self, exc_type, exc_value, traceback): self._future = None + self._lock.release() class CommissioningContext(CallbackContext): - def __init__(self, devCtrl: ChipDeviceController) -> None: - super().__init__() + def __init__(self, devCtrl: ChipDeviceController, lock: asyncio.Lock) -> None: + super().__init__(lock) self._devCtrl = devCtrl - def __enter__(self): - super().__enter__() + async def __aenter__(self): + await super().__aenter__() self._devCtrl._fabricCheckNodeId = -1 return self - def __exit__(self, exc_type, exc_value, traceback): - super().__exit__(exc_type, exc_value, traceback) + async def __aexit__(self, exc_type, exc_value, traceback): + await super().__aexit__(exc_type, exc_value, traceback) class CommissionableNode(discovery.CommissionableNode): @@ -372,10 +375,11 @@ def __init__(self, name: str = ''): self._Cluster = ChipClusters(builtins.chipStack) self._Cluster.InitLib(self._dmLib) - self._commissioning_context: CommissioningContext = CommissioningContext(self) - self._open_window_context: CallbackContext = CallbackContext() - self._unpair_device_context: CallbackContext = CallbackContext() - self._pase_establishment_context: CallbackContext = CallbackContext() + self._commissioning_lock: asyncio.Lock = asyncio.Lock() + self._commissioning_context: CommissioningContext = CommissioningContext(self, self._commissioning_lock) + self._open_window_context: CallbackContext = CallbackContext(asyncio.Lock()) + self._unpair_device_context: CallbackContext = CallbackContext(asyncio.Lock()) + self._pase_establishment_context: CallbackContext = CallbackContext(self._commissioning_lock) def _set_dev_ctrl(self, devCtrl, pairingDelegate): def HandleCommissioningComplete(nodeId: int, err: PyChipError): @@ -579,7 +583,7 @@ async def ConnectBLE(self, discriminator: int, setupPinCode: int, nodeid: int, i self.CheckIsActive() self._enablePairingCompleteCallback(True) - with self._commissioning_context as ctx: + async with self._commissioning_context as ctx: res = await self._ChipStack.CallAsync( lambda: self._dmLib.pychip_DeviceController_ConnectBLE( self.devCtrl, discriminator, isShortDiscriminator, setupPinCode, nodeid) @@ -591,7 +595,7 @@ async def ConnectBLE(self, discriminator: int, setupPinCode: int, nodeid: int, i async def UnpairDevice(self, nodeid: int) -> None: self.CheckIsActive() - with self._unpair_device_context as ctx: + async with self._unpair_device_context as ctx: res = await self._ChipStack.CallAsync( lambda: self._dmLib.pychip_DeviceController_UnpairDevice( self.devCtrl, nodeid, self.cbHandleDeviceUnpairCompleteFunct) @@ -632,7 +636,7 @@ def CloseSession(self, nodeid): async def _establishPASESession(self, callFunct): self.CheckIsActive() - with self._pase_establishment_context as ctx: + async with self._pase_establishment_context as ctx: res = await self._ChipStack.CallAsync(callFunct) res.raise_on_error() await asyncio.futures.wrap_future(ctx.future) @@ -795,7 +799,7 @@ async def OpenCommissioningWindow(self, nodeid: int, timeout: int, iteration: in ''' self.CheckIsActive() - with self._open_window_context as ctx: + async with self._open_window_context as ctx: res = await self._ChipStack.CallAsync( lambda: self._dmLib.pychip_DeviceController_OpenCommissioningWindow( self.devCtrl, self.pairingDelegate, nodeid, timeout, iteration, discriminator, option) @@ -1814,7 +1818,7 @@ def __init__(self, opCredsContext: ctypes.c_void_p, fabricId: int, nodeId: int, f"caIndex({fabricAdmin.caIndex:x})/fabricId(0x{fabricId:016X})/nodeId(0x{nodeId:016X})" ) - self._issue_node_chain_context: CallbackContext = CallbackContext() + self._issue_node_chain_context: CallbackContext = CallbackContext(asyncio.Lock()) self._dmLib.pychip_DeviceController_SetIssueNOCChainCallbackPythonCallback(_IssueNOCChainCallbackPythonCallback) pairingDelegate = c_void_p(None) @@ -1869,7 +1873,7 @@ async def Commission(self, nodeid) -> int: self.CheckIsActive() self._enablePairingCompleteCallback(False) - with self._commissioning_context as ctx: + async with self._commissioning_context as ctx: res = await self._ChipStack.CallAsync( lambda: self._dmLib.pychip_DeviceController_Commission( self.devCtrl, nodeid) @@ -2017,7 +2021,7 @@ async def CommissionOnNetwork(self, nodeId: int, setupPinCode: int, filter = str(filter) self._enablePairingCompleteCallback(True) - with self._commissioning_context as ctx: + async with self._commissioning_context as ctx: res = await self._ChipStack.CallAsync( lambda: self._dmLib.pychip_DeviceController_OnNetworkCommission( self.devCtrl, self.pairingDelegate, nodeId, setupPinCode, int(filterType), str(filter).encode("utf-8") if filter is not None else None, discoveryTimeoutMsec) @@ -2038,7 +2042,7 @@ async def CommissionWithCode(self, setupPayload: str, nodeid: int, discoveryType self.CheckIsActive() self._enablePairingCompleteCallback(True) - with self._commissioning_context as ctx: + async with self._commissioning_context as ctx: res = await self._ChipStack.CallAsync( lambda: self._dmLib.pychip_DeviceController_ConnectWithCode( self.devCtrl, setupPayload.encode("utf-8"), nodeid, discoveryType.value) @@ -2058,7 +2062,7 @@ async def CommissionIP(self, ipaddr: str, setupPinCode: int, nodeid: int) -> int self.CheckIsActive() self._enablePairingCompleteCallback(True) - with self._commissioning_context as ctx: + async with self._commissioning_context as ctx: res = await self._ChipStack.CallAsync( lambda: self._dmLib.pychip_DeviceController_ConnectIP( self.devCtrl, ipaddr.encode("utf-8"), setupPinCode, nodeid) @@ -2079,7 +2083,7 @@ async def IssueNOCChain(self, csr: Clusters.OperationalCredentials.Commands.CSRR The NOC chain will be provided in TLV cert format.""" self.CheckIsActive() - with self._issue_node_chain_context as ctx: + async with self._issue_node_chain_context as ctx: res = await self._ChipStack.CallAsync( lambda: self._dmLib.pychip_DeviceController_IssueNOCChain( self.devCtrl, py_object(self), csr.NOCSRElements, len(csr.NOCSRElements), nodeId)