Skip to content

Commit c862a24

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 5bc5ad7 commit c862a24

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):
@@ -377,10 +380,11 @@ def __init__(self, name: str = ''):
377380

378381
self._Cluster = ChipClusters(builtins.chipStack)
379382
self._Cluster.InitLib(self._dmLib)
380-
self._commissioning_context: CommissioningContext = CommissioningContext(self)
381-
self._open_window_context: CallbackContext = CallbackContext()
382-
self._unpair_device_context: CallbackContext = CallbackContext()
383-
self._pase_establishment_context: CallbackContext = CallbackContext()
383+
self._commissioning_lock: asyncio.Lock = asyncio.Lock()
384+
self._commissioning_context: CommissioningContext = CommissioningContext(self, self._commissioning_lock)
385+
self._open_window_context: CallbackContext = CallbackContext(asyncio.Lock())
386+
self._unpair_device_context: CallbackContext = CallbackContext(asyncio.Lock())
387+
self._pase_establishment_context: CallbackContext = CallbackContext(self._commissioning_lock)
384388

385389
def _set_dev_ctrl(self, devCtrl, pairingDelegate):
386390
def HandleCommissioningComplete(nodeId: int, err: PyChipError):
@@ -584,7 +588,7 @@ async def ConnectBLE(self, discriminator: int, setupPinCode: int, nodeid: int, i
584588
self.CheckIsActive()
585589

586590
self._enablePairingCompleteCallback(True)
587-
with self._commissioning_context as ctx:
591+
async with self._commissioning_context as ctx:
588592
res = await self._ChipStack.CallAsync(
589593
lambda: self._dmLib.pychip_DeviceController_ConnectBLE(
590594
self.devCtrl, discriminator, isShortDiscriminator, setupPinCode, nodeid)
@@ -596,7 +600,7 @@ async def ConnectBLE(self, discriminator: int, setupPinCode: int, nodeid: int, i
596600
async def UnpairDevice(self, nodeid: int) -> None:
597601
self.CheckIsActive()
598602

599-
with self._unpair_device_context as ctx:
603+
async with self._unpair_device_context as ctx:
600604
res = await self._ChipStack.CallAsync(
601605
lambda: self._dmLib.pychip_DeviceController_UnpairDevice(
602606
self.devCtrl, nodeid, self.cbHandleDeviceUnpairCompleteFunct)
@@ -636,7 +640,7 @@ def CloseSession(self, nodeid):
636640
async def _establishPASESession(self, callFunct):
637641
self.CheckIsActive()
638642

639-
with self._pase_establishment_context as ctx:
643+
async with self._pase_establishment_context as ctx:
640644
res = await self._ChipStack.CallAsync(callFunct)
641645
res.raise_on_error()
642646
await asyncio.futures.wrap_future(ctx.future)
@@ -799,7 +803,7 @@ async def OpenCommissioningWindow(self, nodeid: int, timeout: int, iteration: in
799803
'''
800804
self.CheckIsActive()
801805

802-
with self._open_window_context as ctx:
806+
async with self._open_window_context as ctx:
803807
res = await self._ChipStack.CallAsync(
804808
lambda: self._dmLib.pychip_DeviceController_OpenCommissioningWindow(
805809
self.devCtrl, self.pairingDelegate, nodeid, timeout, iteration, discriminator, option)
@@ -1834,7 +1838,7 @@ def __init__(self, opCredsContext: ctypes.c_void_p, fabricId: int, nodeId: int,
18341838
f"caIndex({fabricAdmin.caIndex:x})/fabricId(0x{fabricId:016X})/nodeId(0x{nodeId:016X})"
18351839
)
18361840

1837-
self._issue_node_chain_context: CallbackContext = CallbackContext()
1841+
self._issue_node_chain_context: CallbackContext = CallbackContext(asyncio.Lock())
18381842
self._dmLib.pychip_DeviceController_SetIssueNOCChainCallbackPythonCallback(_IssueNOCChainCallbackPythonCallback)
18391843

18401844
pairingDelegate = c_void_p(None)
@@ -1889,7 +1893,7 @@ async def Commission(self, nodeid) -> int:
18891893
self.CheckIsActive()
18901894

18911895
self._enablePairingCompleteCallback(False)
1892-
with self._commissioning_context as ctx:
1896+
async with self._commissioning_context as ctx:
18931897
res = await self._ChipStack.CallAsync(
18941898
lambda: self._dmLib.pychip_DeviceController_Commission(
18951899
self.devCtrl, nodeid)
@@ -2037,7 +2041,7 @@ async def CommissionOnNetwork(self, nodeId: int, setupPinCode: int,
20372041
filter = str(filter)
20382042

20392043
self._enablePairingCompleteCallback(True)
2040-
with self._commissioning_context as ctx:
2044+
async with self._commissioning_context as ctx:
20412045
res = await self._ChipStack.CallAsync(
20422046
lambda: self._dmLib.pychip_DeviceController_OnNetworkCommission(
20432047
self.devCtrl, self.pairingDelegate, nodeId, setupPinCode, int(filterType), str(filter).encode("utf-8") if filter is not None else None, discoveryTimeoutMsec)
@@ -2058,7 +2062,7 @@ async def CommissionWithCode(self, setupPayload: str, nodeid: int, discoveryType
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_ConnectWithCode(
20642068
self.devCtrl, setupPayload.encode("utf-8"), nodeid, discoveryType.value)
@@ -2078,7 +2082,7 @@ async def CommissionIP(self, ipaddr: str, setupPinCode: int, nodeid: int) -> int
20782082
self.CheckIsActive()
20792083

20802084
self._enablePairingCompleteCallback(True)
2081-
with self._commissioning_context as ctx:
2085+
async with self._commissioning_context as ctx:
20822086
res = await self._ChipStack.CallAsync(
20832087
lambda: self._dmLib.pychip_DeviceController_ConnectIP(
20842088
self.devCtrl, ipaddr.encode("utf-8"), setupPinCode, nodeid)
@@ -2099,7 +2103,7 @@ async def IssueNOCChain(self, csr: Clusters.OperationalCredentials.Commands.CSRR
20992103
The NOC chain will be provided in TLV cert format."""
21002104
self.CheckIsActive()
21012105

2102-
with self._issue_node_chain_context as ctx:
2106+
async with self._issue_node_chain_context as ctx:
21032107
res = await self._ChipStack.CallAsync(
21042108
lambda: self._dmLib.pychip_DeviceController_IssueNOCChain(
21052109
self.devCtrl, py_object(self), csr.NOCSRElements, len(csr.NOCSRElements), nodeId)

0 commit comments

Comments
 (0)