-
Notifications
You must be signed in to change notification settings - Fork 522
Fix bad deferred logcontext handling #19180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
315cab4
f6ea740
e60eb82
7da7726
ade9ef0
f123e09
8495f33
13dd110
e69f0ac
2665c5d
4160869
ffdc146
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Fix bad deferred logcontext handling across the codebase. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,7 +77,11 @@ | |
| from synapse.http.proxyagent import ProxyAgent | ||
| from synapse.http.replicationagent import ReplicationAgent | ||
| from synapse.http.types import QueryParams | ||
| from synapse.logging.context import make_deferred_yieldable, run_in_background | ||
| from synapse.logging.context import ( | ||
| PreserveLoggingContext, | ||
| make_deferred_yieldable, | ||
| run_in_background, | ||
| ) | ||
| from synapse.logging.opentracing import set_tag, start_active_span, tags | ||
| from synapse.metrics import SERVER_NAME_LABEL | ||
| from synapse.types import ISynapseReactor, StrSequence | ||
|
|
@@ -1036,7 +1040,8 @@ def _maybe_fail(self) -> None: | |
| Report a max size exceed error and disconnect the first time this is called. | ||
| """ | ||
| if not self.deferred.called: | ||
| self.deferred.errback(BodyExceededMaxSize()) | ||
| with PreserveLoggingContext(): | ||
| self.deferred.errback(BodyExceededMaxSize()) | ||
|
Comment on lines
+1043
to
+1044
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While I painted with a wide brush, I think this is necessary because the In practice, I don't think these really had callback chains to worry about which is why we probably didn't experience problems in this area before. Applies with a bunch of the Twisted HTTP machinery type stuff here. |
||
| # Close the connection (forcefully) since all the data will get | ||
| # discarded anyway. | ||
| assert self.transport is not None | ||
|
|
@@ -1135,7 +1140,8 @@ def on_part_data(data: bytes, start: int, end: int) -> None: | |
| logger.warning( | ||
| "Exception encountered writing file data to stream: %s", e | ||
| ) | ||
| self.deferred.errback() | ||
| with PreserveLoggingContext(): | ||
| self.deferred.errback() | ||
| self.file_length += end - start | ||
|
|
||
| callbacks: "multipart.MultipartCallbacks" = { | ||
|
|
@@ -1147,7 +1153,8 @@ def on_part_data(data: bytes, start: int, end: int) -> None: | |
|
|
||
| self.total_length += len(incoming_data) | ||
| if self.max_length is not None and self.total_length >= self.max_length: | ||
| self.deferred.errback(BodyExceededMaxSize()) | ||
| with PreserveLoggingContext(): | ||
| self.deferred.errback(BodyExceededMaxSize()) | ||
| # Close the connection (forcefully) since all the data will get | ||
| # discarded anyway. | ||
| assert self.transport is not None | ||
|
|
@@ -1157,7 +1164,8 @@ def on_part_data(data: bytes, start: int, end: int) -> None: | |
| self.parser.write(incoming_data) | ||
| except Exception as e: | ||
| logger.warning("Exception writing to multipart parser: %s", e) | ||
| self.deferred.errback() | ||
| with PreserveLoggingContext(): | ||
| self.deferred.errback() | ||
| return | ||
|
|
||
| def connectionLost(self, reason: Failure = connectionDone) -> None: | ||
|
|
@@ -1167,9 +1175,11 @@ def connectionLost(self, reason: Failure = connectionDone) -> None: | |
|
|
||
| if reason.check(ResponseDone): | ||
| self.multipart_response.length = self.file_length | ||
| self.deferred.callback(self.multipart_response) | ||
| with PreserveLoggingContext(): | ||
| self.deferred.callback(self.multipart_response) | ||
| else: | ||
| self.deferred.errback(reason) | ||
| with PreserveLoggingContext(): | ||
| self.deferred.errback(reason) | ||
|
|
||
|
|
||
| class _ReadBodyWithMaxSizeProtocol(protocol.Protocol): | ||
|
|
@@ -1193,15 +1203,17 @@ def dataReceived(self, data: bytes) -> None: | |
| try: | ||
| self.stream.write(data) | ||
| except Exception: | ||
| self.deferred.errback() | ||
| with PreserveLoggingContext(): | ||
| self.deferred.errback() | ||
| return | ||
|
|
||
| self.length += len(data) | ||
| # The first time the maximum size is exceeded, error and cancel the | ||
| # connection. dataReceived might be called again if data was received | ||
| # in the meantime. | ||
| if self.max_size is not None and self.length >= self.max_size: | ||
| self.deferred.errback(BodyExceededMaxSize()) | ||
| with PreserveLoggingContext(): | ||
| self.deferred.errback(BodyExceededMaxSize()) | ||
| # Close the connection (forcefully) since all the data will get | ||
| # discarded anyway. | ||
| assert self.transport is not None | ||
|
|
@@ -1213,7 +1225,8 @@ def connectionLost(self, reason: Failure = connectionDone) -> None: | |
| return | ||
|
|
||
| if reason.check(ResponseDone): | ||
| self.deferred.callback(self.length) | ||
| with PreserveLoggingContext(): | ||
| self.deferred.callback(self.length) | ||
| elif reason.check(PotentialDataLoss): | ||
| # This applies to requests which don't set `Content-Length` or a | ||
| # `Transfer-Encoding` in the response because in this case the end of the | ||
|
|
@@ -1222,9 +1235,11 @@ def connectionLost(self, reason: Failure = connectionDone) -> None: | |
| # behavior is expected of some servers (like YouTube), let's ignore it. | ||
| # Stolen from https://github.com/twisted/treq/pull/49/files | ||
| # http://twistedmatrix.com/trac/ticket/4840 | ||
| self.deferred.callback(self.length) | ||
| with PreserveLoggingContext(): | ||
| self.deferred.callback(self.length) | ||
| else: | ||
| self.deferred.errback(reason) | ||
| with PreserveLoggingContext(): | ||
| self.deferred.errback(reason) | ||
|
|
||
|
|
||
| def read_body_with_max_size( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -813,7 +813,8 @@ def time_it_out() -> None: | |
| # will have errbacked new_d, but in case it hasn't, errback it now. | ||
|
|
||
| if not new_d.called: | ||
| new_d.errback(defer.TimeoutError("Timed out after %gs" % (timeout,))) | ||
| with PreserveLoggingContext(): | ||
| new_d.errback(defer.TimeoutError("Timed out after %gs" % (timeout,))) | ||
|
Comment on lines
+816
to
+817
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems necessary as we return Usually, there are things chained that change the logcontext because of this pattern: |
||
|
|
||
| # We don't track these calls since they are short. | ||
| delayed_call = clock.call_later( | ||
|
|
@@ -840,11 +841,13 @@ def cancel_timeout(result: _T) -> _T: | |
|
|
||
| def success_cb(val: _T) -> None: | ||
| if not new_d.called: | ||
| new_d.callback(val) | ||
| with PreserveLoggingContext(): | ||
| new_d.callback(val) | ||
|
|
||
| def failure_cb(val: Failure) -> None: | ||
| if not new_d.called: | ||
| new_d.errback(val) | ||
| with PreserveLoggingContext(): | ||
| new_d.errback(val) | ||
|
|
||
| deferred.addCallbacks(success_cb, failure_cb) | ||
|
|
||
|
|
@@ -946,7 +949,8 @@ def handle_cancel(new_deferred: "defer.Deferred[T]") -> None: | |
| # propagating. we then `unpause` it once the wrapped deferred completes, to | ||
| # propagate the exception. | ||
| new_deferred.pause() | ||
| new_deferred.errback(Failure(CancelledError())) | ||
| with PreserveLoggingContext(): | ||
| new_deferred.errback(Failure(CancelledError())) | ||
|
|
||
| deferred.addBoth(lambda _: new_deferred.unpause()) | ||
|
|
||
|
|
@@ -978,15 +982,6 @@ async def sleep(self, name: str, delay_ms: int) -> None: | |
| """Sleep for the given number of milliseconds, or return if the given | ||
| `name` is explicitly woken up. | ||
| """ | ||
|
|
||
| # Create a deferred that gets called in N seconds | ||
| sleep_deferred: "defer.Deferred[None]" = defer.Deferred() | ||
| call = self._clock.call_later( | ||
| delay_ms / 1000, | ||
| sleep_deferred.callback, | ||
| None, | ||
| ) | ||
|
|
||
| # Create a deferred that will get called if `wake` is called with | ||
| # the same `name`. | ||
| stream_set = self._streams.setdefault(name, set()) | ||
|
|
@@ -996,13 +991,14 @@ async def sleep(self, name: str, delay_ms: int) -> None: | |
| try: | ||
| # Wait for either the delay or for `wake` to be called. | ||
| await make_deferred_yieldable( | ||
| defer.DeferredList( | ||
| [sleep_deferred, notify_deferred], | ||
| fireOnOneCallback=True, | ||
| fireOnOneErrback=True, | ||
| consumeErrors=True, | ||
| timeout_deferred( | ||
| deferred=stop_cancellation(notify_deferred), | ||
| timeout=delay_ms / 1000, | ||
| clock=self._clock, | ||
| ) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same fix as #19146
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a bunch of smoke here that fixing
|
||
| ) | ||
| except defer.TimeoutError: | ||
| pass | ||
| finally: | ||
| # Clean up the state | ||
| curr_stream_set = self._streams.get(name) | ||
|
|
@@ -1011,10 +1007,6 @@ async def sleep(self, name: str, delay_ms: int) -> None: | |
| if len(curr_stream_set) == 0: | ||
| self._streams.pop(name) | ||
|
|
||
| # Cancel the sleep if we were woken up | ||
| if call.active(): | ||
| call.cancel() | ||
|
|
||
|
|
||
| class DeferredEvent: | ||
| """Like threading.Event but for async code""" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on how often and easy it is to get this wrong, ideally, we'd have some lint for this. But not for now ⏩