@@ -227,7 +227,16 @@ class ContextRequest:
227
227
228
228
229
229
class _Sentinel :
230
- """Sentinel to represent the root context"""
230
+ """
231
+ Sentinel to represent the root context
232
+
233
+ This should only be used for tasks outside of Synapse like when we yield control
234
+ back to the Twisted reactor (event loop) so we don't leak the current logging
235
+ context to other tasks that are scheduled next in the event loop.
236
+
237
+ Nothing from the Synapse homeserver should be logged with the sentinel context. i.e.
238
+ we should always know which server the logs are coming from.
239
+ """
231
240
232
241
__slots__ = ["previous_context" , "finished" , "request" , "tag" ]
233
242
@@ -616,9 +625,17 @@ def filter(self, record: logging.LogRecord) -> Literal[True]:
616
625
617
626
618
627
class PreserveLoggingContext :
619
- """Context manager which replaces the logging context
628
+ """
629
+ Context manager which replaces the logging context
630
+
631
+ The previous logging context is restored on exit.
620
632
621
- The previous logging context is restored on exit."""
633
+ `make_deferred_yieldable` is pretty equivalent to using `with
634
+ PreserveLoggingContext():` (using the default sentinel context), i.e. it clears the
635
+ logcontext before awaiting (and so before execution passes back to the reactor) and
636
+ restores the old context once the awaitable completes (execution passes from the
637
+ reactor back to the code).
638
+ """
622
639
623
640
__slots__ = ["_old_context" , "_new_context" ]
624
641
@@ -784,6 +801,14 @@ def run_in_background(
784
801
return from the function, and that the sentinel context is set once the
785
802
deferred returned by the function completes.
786
803
804
+ To explain how the log contexts work here:
805
+ - When this function is called, the current context is stored ("original"), we kick
806
+ off the background task, and we restore that original context before returning
807
+ - When the background task finishes, we don't want to leak our context into the
808
+ reactor which would erroneously get attached to the next operation picked up by
809
+ the event loop. We add a callback to the deferred which will clear the logging
810
+ context after it finishes and yields control back to the reactor.
811
+
787
812
Useful for wrapping functions that return a deferred or coroutine, which you don't
788
813
yield or await on (for instance because you want to pass it to
789
814
deferred.gatherResults()).
@@ -795,8 +820,13 @@ def run_in_background(
795
820
`f` doesn't raise any deferred exceptions, otherwise a scary-looking
796
821
CRITICAL error about an unhandled error will be logged without much
797
822
indication about where it came from.
823
+
824
+ Returns:
825
+ Deferred which returns the result of func, or `None` if func raises.
826
+ Note that the returned Deferred does not follow the synapse logcontext
827
+ rules.
798
828
"""
799
- current = current_context ()
829
+ calling_context = current_context ()
800
830
try :
801
831
res = f (* args , ** kwargs )
802
832
except Exception :
@@ -806,6 +836,9 @@ def run_in_background(
806
836
807
837
# `res` may be a coroutine, `Deferred`, some other kind of awaitable, or a plain
808
838
# value. Convert it to a `Deferred`.
839
+ #
840
+ # Wrapping the value in a deferred has the side effect of executing the coroutine,
841
+ # if it is one. If it's already a deferred, then we can just use that.
809
842
d : "defer.Deferred[R]"
810
843
if isinstance (res , typing .Coroutine ):
811
844
# Wrap the coroutine in a `Deferred`.
@@ -820,28 +853,32 @@ def run_in_background(
820
853
# `res` is a plain value. Wrap it in a `Deferred`.
821
854
d = defer .succeed (res )
822
855
856
+ # The deferred has already completed
823
857
if d .called and not d .paused :
824
858
# The function should have maintained the logcontext, so we can
825
859
# optimise out the messing about
826
860
return d
827
861
828
- # The function may have reset the context before returning, so
829
- # we need to restore it now.
830
- ctx = set_current_context (current )
831
-
832
- # The original context will be restored when the deferred
833
- # completes, but there is nothing waiting for it, so it will
834
- # get leaked into the reactor or some other function which
835
- # wasn't expecting it. We therefore need to reset the context
836
- # here.
862
+ # The function may have reset the context before returning, so we need to restore it
863
+ # now.
864
+ #
865
+ # Our goal is to have the caller logcontext unchanged after firing off the
866
+ # background task and returning.
867
+ set_current_context (calling_context )
868
+
869
+ # The original logcontext will be restored when the deferred completes, but
870
+ # there is nothing waiting for it, so it will get leaked into the reactor (which
871
+ # would then get picked up by the next thing the reactor does). We therefore
872
+ # need to reset the logcontext here (set the `sentinel` logcontext) before
873
+ # yielding control back to the reactor.
837
874
#
838
875
# (If this feels asymmetric, consider it this way: we are
839
876
# effectively forking a new thread of execution. We are
840
877
# probably currently within a ``with LoggingContext()`` block,
841
878
# which is supposed to have a single entry and exit point. But
842
879
# by spawning off another deferred, we are effectively
843
880
# adding a new exit point.)
844
- d .addBoth (_set_context_cb , ctx )
881
+ d .addBoth (_set_context_cb , SENTINEL_CONTEXT )
845
882
return d
846
883
847
884
@@ -859,53 +896,86 @@ def run_coroutine_in_background(
859
896
coroutine directly rather than a function. We can do this because coroutines
860
897
do not run until called, and so calling an async function without awaiting
861
898
cannot change the log contexts.
899
+
900
+ This is an ergonomic helper so we can do this:
901
+ ```python
902
+ run_coroutine_in_background(func1(arg1))
903
+ ```
904
+ Rather than having to do this:
905
+ ```python
906
+ run_in_background(lambda: func1(arg1))
907
+ ```
862
908
"""
909
+ calling_context = current_context ()
863
910
864
- current = current_context ()
911
+ # Wrap the coroutine in a deferred, which will have the side effect of executing the
912
+ # coroutine in the background.
865
913
d = defer .ensureDeferred (coroutine )
866
914
867
- # The function may have reset the context before returning, so
868
- # we need to restore it now.
869
- ctx = set_current_context (current )
870
-
871
- # The original context will be restored when the deferred
872
- # completes, but there is nothing waiting for it, so it will
873
- # get leaked into the reactor or some other function which
874
- # wasn't expecting it. We therefore need to reset the context
875
- # here.
915
+ # The function may have reset the context before returning, so we need to restore it
916
+ # now.
917
+ #
918
+ # Our goal is to have the caller logcontext unchanged after firing off the
919
+ # background task and returning.
920
+ set_current_context (calling_context )
921
+
922
+ # The original logcontext will be restored when the deferred completes, but
923
+ # there is nothing waiting for it, so it will get leaked into the reactor (which
924
+ # would then get picked up by the next thing the reactor does). We therefore
925
+ # need to reset the logcontext here (set the `sentinel` logcontext) before
926
+ # yielding control back to the reactor.
876
927
#
877
928
# (If this feels asymmetric, consider it this way: we are
878
929
# effectively forking a new thread of execution. We are
879
930
# probably currently within a ``with LoggingContext()`` block,
880
931
# which is supposed to have a single entry and exit point. But
881
932
# by spawning off another deferred, we are effectively
882
933
# adding a new exit point.)
883
- d .addBoth (_set_context_cb , ctx )
934
+ d .addBoth (_set_context_cb , SENTINEL_CONTEXT )
884
935
return d
885
936
886
937
887
938
T = TypeVar ("T" )
888
939
889
940
890
941
def make_deferred_yieldable (deferred : "defer.Deferred[T]" ) -> "defer.Deferred[T]" :
891
- """Given a deferred, make it follow the Synapse logcontext rules:
892
-
893
- If the deferred has completed, essentially does nothing (just returns another
894
- completed deferred with the result/failure).
895
-
896
- If the deferred has not yet completed, resets the logcontext before
897
- returning a deferred. Then, when the deferred completes, restores the
898
- current logcontext before running callbacks/errbacks.
899
-
900
- (This is more-or-less the opposite operation to run_in_background.)
901
942
"""
943
+ Given a deferred, make it follow the Synapse logcontext rules:
944
+
945
+ - If the deferred has completed, essentially does nothing (just returns another
946
+ completed deferred with the result/failure).
947
+ - If the deferred has not yet completed, resets the logcontext before returning a
948
+ incomplete deferred. Then, when the deferred completes, restores the current
949
+ logcontext before running callbacks/errbacks.
950
+
951
+ This means the resultant deferred can be awaited without leaking the current
952
+ logcontext to the reactor (which would then get erroneously picked up by the next
953
+ thing the reactor does), and also means that the logcontext is preserved when the
954
+ deferred completes.
955
+
956
+ (This is more-or-less the opposite operation to run_in_background in terms of how it
957
+ handles log contexts.)
958
+
959
+ Pretty much equivalent to using `with PreserveLoggingContext():`, i.e. it clears the
960
+ logcontext before awaiting (and so before execution passes back to the reactor) and
961
+ restores the old context once the awaitable completes (execution passes from the
962
+ reactor back to the code).
963
+ """
964
+ # The deferred has already completed
902
965
if deferred .called and not deferred .paused :
903
966
# it looks like this deferred is ready to run any callbacks we give it
904
967
# immediately. We may as well optimise out the logcontext faffery.
905
968
return deferred
906
969
907
- # ok, we can't be sure that a yield won't block, so let's reset the
908
- # logcontext, and add a callback to the deferred to restore it.
970
+ # Our goal is to have the caller logcontext unchanged after they yield/await the
971
+ # returned deferred.
972
+ #
973
+ # When the caller yield/await's the returned deferred, it may yield
974
+ # control back to the reactor. To avoid leaking the current logcontext to the
975
+ # reactor (which would then get erroneously picked up by the next thing the reactor
976
+ # does) while the deferred runs in the reactor event loop, we reset the logcontext
977
+ # and add a callback to the deferred to restore it so the caller's logcontext is
978
+ # active when the deferred completes.
909
979
prev_context = set_current_context (SENTINEL_CONTEXT )
910
980
deferred .addBoth (_set_context_cb , prev_context )
911
981
return deferred
0 commit comments