Skip to content

Commit 4925772

Browse files
committed
[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.
1 parent 316a9bb commit 4925772

File tree

1 file changed

+28
-24
lines changed

1 file changed

+28
-24
lines changed

src/controller/python/chip/ChipDeviceCtrl.py

+28-24
Original file line numberDiff line numberDiff line change
@@ -227,33 +227,36 @@ def wrapper(*args, **kwargs):
227227

228228

229229
class CallbackContext:
230-
def __init__(self) -> None:
230+
def __init__(self, lock: asyncio.Lock) -> None:
231+
self._lock = lock
231232
self._future = None
232233

233-
def __enter__(self):
234+
async def __aenter__(self):
235+
await self._lock.acquire()
234236
self._future = concurrent.futures.Future()
235237
return self
236238

237239
@property
238-
def future(self) -> concurrent.futures.Future | None:
240+
def future(self) -> typing.Optional[concurrent.futures.Future]:
239241
return self._future
240242

241-
def __exit__(self, exc_type, exc_value, traceback):
243+
async def __aexit__(self, exc_type, exc_value, traceback):
242244
self._future = None
245+
self._lock.release()
243246

244247

245248
class CommissioningContext(CallbackContext):
246-
def __init__(self, devCtrl: ChipDeviceController) -> None:
247-
super().__init__()
249+
def __init__(self, devCtrl: ChipDeviceController, lock: asyncio.Lock) -> None:
250+
super().__init__(lock)
248251
self._devCtrl = devCtrl
249252

250-
def __enter__(self):
251-
super().__enter__()
253+
async def __aenter__(self):
254+
await super().__aenter__()
252255
self._devCtrl._fabricCheckNodeId = -1
253256
return self
254257

255-
def __exit__(self, exc_type, exc_value, traceback):
256-
super().__exit__(exc_type, exc_value, traceback)
258+
async def __aexit__(self, exc_type, exc_value, traceback):
259+
await super().__aexit__(exc_type, exc_value, traceback)
257260

258261

259262
class CommissionableNode(discovery.CommissionableNode):
@@ -372,10 +375,11 @@ def __init__(self, name: str = ''):
372375

373376
self._Cluster = ChipClusters(builtins.chipStack)
374377
self._Cluster.InitLib(self._dmLib)
375-
self._commissioning_context: CommissioningContext = CommissioningContext(self)
376-
self._open_window_context: CallbackContext = CallbackContext()
377-
self._unpair_device_context: CallbackContext = CallbackContext()
378-
self._pase_establishment_context: CallbackContext = CallbackContext()
378+
self._commissioning_lock: asyncio.Lock = asyncio.Lock()
379+
self._commissioning_context: CommissioningContext = CommissioningContext(self, self._commissioning_lock)
380+
self._open_window_context: CallbackContext = CallbackContext(asyncio.Lock())
381+
self._unpair_device_context: CallbackContext = CallbackContext(asyncio.Lock())
382+
self._pase_establishment_context: CallbackContext = CallbackContext(self._commissioning_lock)
379383

380384
def _set_dev_ctrl(self, devCtrl, pairingDelegate):
381385
def HandleCommissioningComplete(nodeId: int, err: PyChipError):
@@ -579,7 +583,7 @@ async def ConnectBLE(self, discriminator: int, setupPinCode: int, nodeid: int, i
579583
self.CheckIsActive()
580584

581585
self._enablePairingCompleteCallback(True)
582-
with self._commissioning_context as ctx:
586+
async with self._commissioning_context as ctx:
583587
res = await self._ChipStack.CallAsync(
584588
lambda: self._dmLib.pychip_DeviceController_ConnectBLE(
585589
self.devCtrl, discriminator, isShortDiscriminator, setupPinCode, nodeid)
@@ -591,7 +595,7 @@ async def ConnectBLE(self, discriminator: int, setupPinCode: int, nodeid: int, i
591595
async def UnpairDevice(self, nodeid: int) -> None:
592596
self.CheckIsActive()
593597

594-
with self._unpair_device_context as ctx:
598+
async with self._unpair_device_context as ctx:
595599
res = await self._ChipStack.CallAsync(
596600
lambda: self._dmLib.pychip_DeviceController_UnpairDevice(
597601
self.devCtrl, nodeid, self.cbHandleDeviceUnpairCompleteFunct)
@@ -632,7 +636,7 @@ def CloseSession(self, nodeid):
632636
async def _establishPASESession(self, callFunct):
633637
self.CheckIsActive()
634638

635-
with self._pase_establishment_context as ctx:
639+
async with self._pase_establishment_context as ctx:
636640
res = await self._ChipStack.CallAsync(callFunct)
637641
res.raise_on_error()
638642
await asyncio.futures.wrap_future(ctx.future)
@@ -795,7 +799,7 @@ async def OpenCommissioningWindow(self, nodeid: int, timeout: int, iteration: in
795799
'''
796800
self.CheckIsActive()
797801

798-
with self._open_window_context as ctx:
802+
async with self._open_window_context as ctx:
799803
res = await self._ChipStack.CallAsync(
800804
lambda: self._dmLib.pychip_DeviceController_OpenCommissioningWindow(
801805
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,
18141818
f"caIndex({fabricAdmin.caIndex:x})/fabricId(0x{fabricId:016X})/nodeId(0x{nodeId:016X})"
18151819
)
18161820

1817-
self._issue_node_chain_context: CallbackContext = CallbackContext()
1821+
self._issue_node_chain_context: CallbackContext = CallbackContext(asyncio.Lock())
18181822
self._dmLib.pychip_DeviceController_SetIssueNOCChainCallbackPythonCallback(_IssueNOCChainCallbackPythonCallback)
18191823

18201824
pairingDelegate = c_void_p(None)
@@ -1869,7 +1873,7 @@ async def Commission(self, nodeid) -> int:
18691873
self.CheckIsActive()
18701874

18711875
self._enablePairingCompleteCallback(False)
1872-
with self._commissioning_context as ctx:
1876+
async with self._commissioning_context as ctx:
18731877
res = await self._ChipStack.CallAsync(
18741878
lambda: self._dmLib.pychip_DeviceController_Commission(
18751879
self.devCtrl, nodeid)
@@ -2017,7 +2021,7 @@ async def CommissionOnNetwork(self, nodeId: int, setupPinCode: int,
20172021
filter = str(filter)
20182022

20192023
self._enablePairingCompleteCallback(True)
2020-
with self._commissioning_context as ctx:
2024+
async with self._commissioning_context as ctx:
20212025
res = await self._ChipStack.CallAsync(
20222026
lambda: self._dmLib.pychip_DeviceController_OnNetworkCommission(
20232027
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
20382042
self.CheckIsActive()
20392043

20402044
self._enablePairingCompleteCallback(True)
2041-
with self._commissioning_context as ctx:
2045+
async with self._commissioning_context as ctx:
20422046
res = await self._ChipStack.CallAsync(
20432047
lambda: self._dmLib.pychip_DeviceController_ConnectWithCode(
20442048
self.devCtrl, setupPayload.encode("utf-8"), nodeid, discoveryType.value)
@@ -2058,7 +2062,7 @@ async def CommissionIP(self, ipaddr: str, setupPinCode: int, nodeid: int) -> int
20582062
self.CheckIsActive()
20592063

20602064
self._enablePairingCompleteCallback(True)
2061-
with self._commissioning_context as ctx:
2065+
async with self._commissioning_context as ctx:
20622066
res = await self._ChipStack.CallAsync(
20632067
lambda: self._dmLib.pychip_DeviceController_ConnectIP(
20642068
self.devCtrl, ipaddr.encode("utf-8"), setupPinCode, nodeid)
@@ -2079,7 +2083,7 @@ async def IssueNOCChain(self, csr: Clusters.OperationalCredentials.Commands.CSRR
20792083
The NOC chain will be provided in TLV cert format."""
20802084
self.CheckIsActive()
20812085

2082-
with self._issue_node_chain_context as ctx:
2086+
async with self._issue_node_chain_context as ctx:
20832087
res = await self._ChipStack.CallAsync(
20842088
lambda: self._dmLib.pychip_DeviceController_IssueNOCChain(
20852089
self.devCtrl, py_object(self), csr.NOCSRElements, len(csr.NOCSRElements), nodeId)

0 commit comments

Comments
 (0)