Skip to content

Commit 2091fef

Browse files
authored
Enforce best practices when using async in Python (project-chip#42095)
* Enforce best practices when using async in Python * Do not use time.sleep in async functions * Fix some more asyncio issues reported by ruff * Fix issues reported by copilot * Fix race between wait timeout and callback call
1 parent bacc207 commit 2091fef

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+237
-222
lines changed

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ exclude = [
2828
select = [
2929
# Standard errors and warnings
3030
"E", "W", "F",
31+
# Ensure that async is used properly
32+
"ASYNC",
3133
# Consistent commas
3234
"COM",
3335
# Simplify the code if possible
@@ -40,6 +42,8 @@ select = [
4042
"SLOT",
4143
]
4244
ignore = [
45+
"ASYNC109", # Sometimes we use "timeout" argument in async functions
46+
"ASYNC230", # Allow open() calls in async functions
4347
"COM812", # Do not enforce trailing commas in multi-line collections for now
4448
"LOG015", # Do not enforce non-root loggers in the entire codebase for now
4549
"E501", # Do not report line length issues (formatter should handle this)

scripts/py_matter_yamltests/matter/yamltests/pseudo_clusters/clusters/delay_commands.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515

16+
import asyncio
1617
import sys
17-
import time
1818

1919
from ..pseudo_cluster import PseudoCluster
2020
from .accessory_server_bridge import AccessoryServerBridge
@@ -59,7 +59,7 @@ async def WaitForMs(self, request):
5959
duration_in_ms = argument['value']
6060

6161
sys.stdout.flush()
62-
time.sleep(int(duration_in_ms) / 1000)
62+
await asyncio.sleep(int(duration_in_ms) / 1000)
6363

6464
async def WaitForMessage(self, request):
6565
AccessoryServerBridge.waitForMessage(request)

scripts/py_matter_yamltests/matter/yamltests/websocket_runner.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515

16+
import asyncio
1617
import logging
1718
import re
1819
import select
@@ -92,7 +93,7 @@ async def _start_client(self, url, max_retries=_CONNECT_MAX_RETRIES_DEFAULT, int
9293
duration = round((time.time() - start) * 1000, 0)
9394
self._hooks.failure(duration)
9495
self._hooks.retry(interval_between_retries)
95-
time.sleep(interval_between_retries)
96+
await asyncio.sleep(interval_between_retries)
9697
return await self._start_client(url, max_retries - 1, interval_between_retries + 1)
9798

9899
self._hooks.abort(url)
@@ -107,7 +108,7 @@ async def _start_server(self, command, url):
107108
if command:
108109
start_time = time.time()
109110

110-
instance = subprocess.Popen(
111+
instance = subprocess.Popen( # noqa: ASYNC220
111112
command,
112113
bufsize=0, # unbuffered
113114
text=False, # keep output as bytes

src/controller/python/matter/ChipDeviceCtrl.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,10 +1108,9 @@ async def DiscoverCommissionableNodes(self, filterType: discovery.FilterType = d
11081108
self.devCtrl, int(filterType), str(filter).encode("utf-8")))
11091109

11101110
async def _wait_discovery():
1111-
while not await self._ChipStack.CallAsyncWithResult(
1111+
while not await self._ChipStack.CallAsyncWithResult( # noqa: ASYNC110
11121112
lambda: self._dmLib.pychip_DeviceController_HasDiscoveredCommissionableNode(self.devCtrl)):
11131113
await asyncio.sleep(0.1)
1114-
return
11151114

11161115
try:
11171116
if stopOnFirst:

src/controller/python/matter/ChipStack.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ def future(self):
120120
return self._future
121121

122122
def _done(self):
123+
if self._future.cancelled():
124+
# If this helper is used with the asyncio.wait_for(), it might
125+
# happen that the future will be cancelled before the callback
126+
# is called. Do not raise an exception in this case.
127+
return
123128
if self._exception:
124129
self._future.set_exception(self._exception)
125130
else:

src/controller/python/tests/scripts/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1458,7 +1458,7 @@ async def TestSubscriptionResumptionCapacityStep2(self, nodeId: int, endpoint: i
14581458
restartRemoteThread.start()
14591459

14601460
# Wait for some time so that the device will be resolving the address of the first controller after restarting
1461-
time.sleep(8)
1461+
await asyncio.sleep(8)
14621462
restartRemoteThread.join(10.0)
14631463

14641464
self.logger.info("Send a new subscription request from the second controller")

src/python_testing/TC_ACL_2_10.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,9 @@
3232
# --endpoint 0
3333
# === END CI TEST ARGUMENTS ===
3434

35-
35+
import asyncio
3636
import logging
3737
import random
38-
import time
3938

4039
from mobly import asserts
4140

@@ -247,7 +246,7 @@ async def test_TC_ACL_2_10(self):
247246

248247
# The test runner will automatically wait for the app-ready-pattern before continuing
249248
# Waiting 1 second after the app-ready-pattern is detected as we need to wait a tad longer for the app to be ready and stable, otherwise TH2 connection fails later on in test step 14.
250-
time.sleep(1)
249+
await asyncio.sleep(1)
251250

252251
# Expire sessions and re-establish connections
253252
self.th1.ExpireSessions(self.dut_node_id)

src/python_testing/TC_CADMIN_1_11.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,9 @@
3131
# quiet: true
3232
# === END CI TEST ARGUMENTS ===
3333

34-
34+
import asyncio
3535
import logging
3636
import random
37-
from time import sleep
3837

3938
from mobly import asserts
4039
from support_modules.cadmin_support import CADMINBaseTest
@@ -144,7 +143,7 @@ async def test_TC_CADMIN_1_11(self):
144143
revokeCmd = Clusters.AdministratorCommissioning.Commands.RevokeCommissioning()
145144
await self.th1.SendCommand(nodeId=self.dut_node_id, endpoint=0, payload=revokeCmd, timedRequestTimeoutMs=6000)
146145
# The failsafe cleanup is scheduled after the command completes, so give it a bit of time to do that
147-
sleep(1)
146+
await asyncio.sleep(1)
148147

149148
self.step(9)
150149

@@ -178,7 +177,7 @@ async def test_TC_CADMIN_1_11(self):
178177
revokeCmd = Clusters.AdministratorCommissioning.Commands.RevokeCommissioning()
179178
await self.th1.SendCommand(nodeId=self.dut_node_id, endpoint=0, payload=revokeCmd, timedRequestTimeoutMs=6000)
180179
# The failsafe cleanup is scheduled after the command completes, so give it a bit of time to do that
181-
sleep(1)
180+
await asyncio.sleep(1)
182181

183182
else:
184183
self.skip_step("9a")

src/python_testing/TC_CADMIN_1_3_4.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@
4444
# quiet: true
4545
# === END CI TEST ARGUMENTS ===
4646

47+
import asyncio
4748
import logging
4849
import random
49-
from time import sleep
5050

5151
from mobly import asserts
5252
from support_modules.cadmin_support import CADMINBaseTest
@@ -221,7 +221,7 @@ async def combined_commission_val_steps(self, commission_type: str):
221221
revokeCmd = Clusters.AdministratorCommissioning.Commands.RevokeCommissioning()
222222
await self.th1.SendCommand(nodeId=self.dut_node_id, endpoint=0, payload=revokeCmd, timedRequestTimeoutMs=6000)
223223
# The failsafe cleanup is scheduled after the command completes, so give it a bit of time to do that
224-
sleep(1)
224+
await asyncio.sleep(1)
225225

226226
if commission_type == "ECM":
227227
self.step(13)

src/python_testing/TC_CADMIN_1_5.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@
3131
# quiet: true
3232
# === END CI TEST ARGUMENTS ===
3333

34-
import asyncio.exceptions as ae
34+
import asyncio
3535
import logging
36-
from time import sleep
3736

3837
from mobly import asserts
3938
from support_modules.cadmin_support import CADMINBaseTest
@@ -63,7 +62,7 @@ async def commission_on_network(self, setup_code: int, discriminator: int, expec
6362
nodeId=self.dut_node_id, setupPinCode=setup_code,
6463
filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=discriminator)
6564

66-
except ae.CancelledError:
65+
except asyncio.CancelledError:
6766
# This is expected to fail due to timeout, however there is no code to validate here, so just passing since the correct exception was raised to get to this point
6867
pass
6968

@@ -104,6 +103,10 @@ def steps_TC_CADMIN_1_5(self) -> list[TestStep]:
104103
def pics_TC_CADMIN_1_5(self) -> list[str]:
105104
return ["CADMIN.S"]
106105

106+
@property
107+
def default_timeout(self) -> int:
108+
return 5 * 60 # 5 minutes
109+
107110
@async_test_body
108111
async def test_TC_CADMIN_1_5(self):
109112
self.step(1)
@@ -125,7 +128,7 @@ async def test_TC_CADMIN_1_5(self):
125128
expected_discriminator=params.randomDiscriminator
126129
)
127130
logging.info(f"Successfully found service with CM={service.txt.get('CM')}, D={service.txt.get('D')}")
128-
sleep(190)
131+
await asyncio.sleep(190)
129132

130133
self.step(4)
131134
await self.commission_on_network(setup_code=params.commissioningParameters.setupPinCode, discriminator=params.randomDiscriminator)
@@ -136,7 +139,7 @@ async def test_TC_CADMIN_1_5(self):
136139
self.step(6)
137140
revokeCmd = Clusters.AdministratorCommissioning.Commands.RevokeCommissioning()
138141
await self.th1.SendCommand(nodeId=self.dut_node_id, endpoint=0, payload=revokeCmd, timedRequestTimeoutMs=6000)
139-
sleep(1)
142+
await asyncio.sleep(1)
140143

141144
self.step(7)
142145
await self.commission_on_network(setup_code=params2.commissioningParameters.setupPinCode, discriminator=params2.randomDiscriminator, expected_error=0x00000032)

0 commit comments

Comments
 (0)