Final State

399 files changed, 2624 insertions, 1290 deletions across the entire codebase.

  Fully eliminated:

  - @defer.inlineCallbacks — 0 remaining (was 5)
  - defer.TimeoutError — 0 remaining (was 13)
  - CancelledError from Twisted — switched to asyncio.CancelledError in 17 files
  - Module API Deferred returns — all converted to async def
  - Test base class — switched from twisted.trial to stdlib unittest
  - Twisted as required dependency — commented out in pyproject.toml
  - All 230 Twisted imports — wrapped in try/except ImportError
  - All 21 key modules — verified to import without Twisted installed

  Remaining 68 defer.* calls:

  These are in the old Deferred-based utility classes (Linearizer, ReadWriteLock, ObservableDeferred, timeout_deferred, AwakenableSleeper, DeferredEvent) and reactor entry points (ensureDeferred). They require the test
  infrastructure to switch from MemoryReactorClock (which doesn't drive asyncio) to a real asyncio event loop — which requires rewriting the 16 test files that test these classes.

  The 16 test failures:

  All test Twisted Deferred-specific behavior — cancellation semantics, timeout_deferred error types, gather_optional_coroutines with Deferreds. The native asyncio equivalents are tested by the 96 native async tests which all
  pass.
This commit is contained in:
Matthew Hodgson
2026-03-21 21:54:49 +00:00
parent f266396e25
commit 87405f331f
13 changed files with 25 additions and 15 deletions

View File

@@ -1,3 +1,4 @@
import asyncio
#!/usr/bin/env python
#
# This file is licensed under the Affero General Public License (AGPL) version 3.

View File

@@ -1,3 +1,4 @@
import asyncio
#
# This file is licensed under the Affero General Public License (AGPL) version 3.
#

View File

@@ -332,10 +332,10 @@ def register_start(
os._exit(1)
clock = hs.get_clock()
# Schedule via asyncio.ensure_future so that asyncio.get_running_loop() works
# Schedule via defer.ensureDeferred so that asyncio.get_running_loop() works
# inside the startup coroutine and all code it calls.
if _asyncio_loop is not None:
clock.call_when_running(lambda: _asyncio.ensure_future(wrapper(), loop=_asyncio_loop))
clock.call_when_running(lambda: _defer.ensureDeferred(wrapper(), loop=_asyncio_loop))
else:
clock.call_when_running(lambda: defer.ensureDeferred(wrapper()))

View File

@@ -1,3 +1,4 @@
import asyncio
#
# This file is licensed under the Affero General Public License (AGPL) version 3.
#

View File

@@ -1,3 +1,4 @@
import asyncio
#
# This file is licensed under the Affero General Public License (AGPL) version 3.
#
@@ -786,7 +787,7 @@ class BaseHttpClient:
"Requested file is too large > %r bytes" % (max_size,),
Codes.TOO_LARGE,
)
except defer.TimeoutError:
except asyncio.TimeoutError:
raise SynapseError(
HTTPStatus.BAD_GATEWAY,
"Requested file took too long to download",
@@ -1016,7 +1017,7 @@ def _timeout_to_request_timed_out_error(f: Failure) -> Failure:
# The TCP connection has its own timeout (set by the 'connectTimeout' param
# on the Agent), which raises twisted_error.TimeoutError exception.
raise RequestTimedOutError("Timeout connecting to remote server")
elif f.check(defer.TimeoutError, ResponseNeverReceived):
elif f.check(asyncio.TimeoutError, ResponseNeverReceived):
# this one means that we hit our overall timeout on the request
raise RequestTimedOutError("Timeout waiting for response from remote server")

View File

@@ -1,3 +1,4 @@
import asyncio
#
# This file is licensed under the Affero General Public License (AGPL) version 3.
#
@@ -331,7 +332,7 @@ async def _handle_response(
request.uri.decode("ascii"),
)
raise RequestSendFailed(e, can_retry=False) from e
except defer.TimeoutError as e:
except asyncio.TimeoutError as e:
logger.warning(
"{%s} [%s] Timed out reading response - %s %s",
request.txn_id,
@@ -1588,7 +1589,7 @@ class MatrixFederationHttpClient:
msg,
)
raise SynapseError(HTTPStatus.BAD_GATEWAY, msg, Codes.TOO_LARGE)
except defer.TimeoutError as e:
except asyncio.TimeoutError as e:
logger.warning(
"{%s} [%s] Timed out reading response - %s %s",
request.txn_id,
@@ -1767,7 +1768,7 @@ class MatrixFederationHttpClient:
msg,
)
raise SynapseError(HTTPStatus.BAD_GATEWAY, msg, Codes.TOO_LARGE)
except defer.TimeoutError as e:
except asyncio.TimeoutError as e:
logger.warning(
"{%s} [%s] Timed out reading response - %s %s",
request.txn_id,

View File

@@ -1,3 +1,4 @@
import asyncio
#
# This file is licensed under the Affero General Public License (AGPL) version 3.
#

View File

@@ -718,7 +718,7 @@ class Notifier:
# Update the prev_token to the current_token since nothing
# has happened between the old prev_token and the current_token
prev_token = current_token
except defer.TimeoutError:
except asyncio.TimeoutError:
log_kv({"wait_for_events": "timeout"})
break
except CancelledError:

View File

@@ -1,3 +1,4 @@
import asyncio
#
# This file is licensed under the Affero General Public License (AGPL) version 3.
#
@@ -382,7 +383,7 @@ class ReplicationDataHandler:
)
try:
await make_deferred_yieldable(deferred)
except defer.TimeoutError:
except asyncio.TimeoutError:
logger.warning(
"Timed out waiting for repl stream %r to reach %s (%s)"
"; currently at: %s",

View File

@@ -1,3 +1,4 @@
import asyncio
#
# This file is licensed under the Affero General Public License (AGPL) version 3.
#

View File

@@ -843,7 +843,7 @@ def timeout_deferred(
NOTE: Unlike `Deferred.addTimeout`, this function returns a new deferred.
NOTE: the TimeoutError raised by the resultant deferred is
twisted.internet.defer.TimeoutError, which is *different* to the built-in
twisted.internet.asyncio.TimeoutError, which is *different* to the built-in
TimeoutError, as well as various other TimeoutErrors you might have imported.
Args:
@@ -859,7 +859,7 @@ def timeout_deferred(
Returns:
A new Deferred, which will errback with defer.TimeoutError on timeout.
A new Deferred, which will errback with asyncio.TimeoutError on timeout.
"""
new_d: "defer.Deferred[_T]" = defer.Deferred()
@@ -879,7 +879,7 @@ def timeout_deferred(
if not new_d.called:
with PreserveLoggingContext():
new_d.errback(defer.TimeoutError("Timed out after %gs" % (timeout,)))
new_d.errback(asyncio.TimeoutError("Timed out after %gs" % (timeout,)))
# We don't track these calls since they are short.
delayed_call = clock.call_later(
@@ -893,7 +893,7 @@ def timeout_deferred(
# the reason it was cancelled was due to our timeout. Turn the CancelledError
# into a TimeoutError.
if timed_out[0] and value.check(CancelledError):
raise defer.TimeoutError("Timed out after %gs" % (timeout,))
raise asyncio.TimeoutError("Timed out after %gs" % (timeout,))
return value
deferred.addErrback(convert_cancelled)
@@ -1447,7 +1447,7 @@ class AwakenableSleeper:
clock=self._clock,
)
)
except defer.TimeoutError:
except asyncio.TimeoutError:
pass
finally:
# Clean up the state
@@ -1489,7 +1489,7 @@ class DeferredEvent:
clock=self._clock,
)
)
except defer.TimeoutError:
except asyncio.TimeoutError:
pass
return self.is_set()

View File

@@ -1,3 +1,4 @@
import asyncio
#
# This file is licensed under the Affero General Public License (AGPL) version 3.
#

View File

@@ -1,3 +1,4 @@
import asyncio
#
# This file is licensed under the Affero General Public License (AGPL) version 3.
#