log_contexts.html 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. <!DOCTYPE HTML>
  2. <html lang="en" class="sidebar-visible no-js light">
  3. <head>
  4. <!-- Book generated using mdBook -->
  5. <meta charset="UTF-8">
  6. <title>Log Contexts - Synapse</title>
  7. <!-- Custom HTML head -->
  8. <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  9. <meta name="description" content="">
  10. <meta name="viewport" content="width=device-width, initial-scale=1">
  11. <meta name="theme-color" content="#ffffff" />
  12. <link rel="icon" href="favicon.svg">
  13. <link rel="shortcut icon" href="favicon.png">
  14. <link rel="stylesheet" href="css/variables.css">
  15. <link rel="stylesheet" href="css/general.css">
  16. <link rel="stylesheet" href="css/chrome.css">
  17. <link rel="stylesheet" href="css/print.css" media="print">
  18. <!-- Fonts -->
  19. <link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
  20. <link rel="stylesheet" href="fonts/fonts.css">
  21. <!-- Highlight.js Stylesheets -->
  22. <link rel="stylesheet" href="highlight.css">
  23. <link rel="stylesheet" href="tomorrow-night.css">
  24. <link rel="stylesheet" href="ayu-highlight.css">
  25. <!-- Custom theme stylesheets -->
  26. <link rel="stylesheet" href="docs/website_files/table-of-contents.css">
  27. <link rel="stylesheet" href="docs/website_files/remove-nav-buttons.css">
  28. <link rel="stylesheet" href="docs/website_files/indent-section-headers.css">
  29. </head>
  30. <body>
  31. <!-- Provide site root to javascript -->
  32. <script type="text/javascript">
  33. var path_to_root = "";
  34. var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
  35. </script>
  36. <!-- Work around some values being stored in localStorage wrapped in quotes -->
  37. <script type="text/javascript">
  38. try {
  39. var theme = localStorage.getItem('mdbook-theme');
  40. var sidebar = localStorage.getItem('mdbook-sidebar');
  41. if (theme.startsWith('"') && theme.endsWith('"')) {
  42. localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
  43. }
  44. if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
  45. localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
  46. }
  47. } catch (e) { }
  48. </script>
  49. <!-- Set the theme before any content is loaded, prevents flash -->
  50. <script type="text/javascript">
  51. var theme;
  52. try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
  53. if (theme === null || theme === undefined) { theme = default_theme; }
  54. var html = document.querySelector('html');
  55. html.classList.remove('no-js')
  56. html.classList.remove('light')
  57. html.classList.add(theme);
  58. html.classList.add('js');
  59. </script>
  60. <!-- Hide / unhide sidebar before it is displayed -->
  61. <script type="text/javascript">
  62. var html = document.querySelector('html');
  63. var sidebar = 'hidden';
  64. if (document.body.clientWidth >= 1080) {
  65. try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
  66. sidebar = sidebar || 'visible';
  67. }
  68. html.classList.remove('sidebar-visible');
  69. html.classList.add("sidebar-" + sidebar);
  70. </script>
  71. <nav id="sidebar" class="sidebar" aria-label="Table of contents">
  72. <div class="sidebar-scrollbox">
  73. <ol class="chapter"><li class="chapter-item expanded affix "><li class="part-title">Introduction</li><li class="chapter-item expanded "><a href="welcome_and_overview.html">Welcome and Overview</a></li><li class="chapter-item expanded affix "><li class="part-title">Setup</li><li class="chapter-item expanded "><a href="setup/installation.html">Installation</a></li><li class="chapter-item expanded "><a href="postgres.html">Using Postgres</a></li><li class="chapter-item expanded "><a href="reverse_proxy.html">Configuring a Reverse Proxy</a></li><li class="chapter-item expanded "><a href="turn-howto.html">Configuring a Turn Server</a></li><li class="chapter-item expanded "><a href="delegate.html">Delegation</a></li><li class="chapter-item expanded affix "><li class="part-title">Upgrading</li><li class="chapter-item expanded "><a href="upgrade.html">Upgrading between Synapse Versions</a></li><li class="chapter-item expanded "><a href="MSC1711_certificates_FAQ.html">Upgrading from pre-Synapse 1.0</a></li><li class="chapter-item expanded affix "><li class="part-title">Usage</li><li class="chapter-item expanded "><a href="federate.html">Federation</a></li><li class="chapter-item expanded "><a href="usage/configuration/index.html">Configuration</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="usage/configuration/homeserver_sample_config.html">Homeserver Sample Config File</a></li><li class="chapter-item expanded "><a href="usage/configuration/logging_sample_config.html">Logging Sample Config File</a></li><li class="chapter-item expanded "><a href="structured_logging.html">Structured Logging</a></li><li class="chapter-item expanded "><a href="usage/configuration/user_authentication/index.html">User Authentication</a></li><li><ol class="section"><li class="chapter-item expanded "><div>Single-Sign On</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="openid.html">OpenID Connect</a></li><li class="chapter-item expanded "><div>SAML</div></li><li class="chapter-item expanded "><div>CAS</div></li><li class="chapter-item expanded "><a href="sso_mapping_providers.html">SSO Mapping Providers</a></li></ol></li><li class="chapter-item expanded "><a href="password_auth_providers.html">Password Auth Providers</a></li><li class="chapter-item expanded "><a href="jwt.html">JSON Web Tokens</a></li></ol></li><li class="chapter-item expanded "><a href="CAPTCHA_SETUP.html">Registration Captcha</a></li><li class="chapter-item expanded "><a href="application_services.html">Application Services</a></li><li class="chapter-item expanded "><a href="server_notices.html">Server Notices</a></li><li class="chapter-item expanded "><a href="consent_tracking.html">Consent Tracking</a></li><li class="chapter-item expanded "><a href="url_previews.html">URL Previews</a></li><li class="chapter-item expanded "><a href="user_directory.html">User Directory</a></li><li class="chapter-item expanded "><a href="message_retention_policies.html">Message Retention Policies</a></li><li class="chapter-item expanded "><a href="modules.html">Pluggable Modules</a></li><li><ol class="section"><li class="chapter-item expanded "><div>Third Party Rules</div></li><li class="chapter-item expanded "><a href="spam_checker.html">Spam Checker</a></li><li class="chapter-item expanded "><a href="presence_router_module.html">Presence Router</a></li><li class="chapter-item expanded "><div>Media Storage Providers</div></li></ol></li><li class="chapter-item expanded "><a href="workers.html">Workers</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="synctl_workers.html">Using synctl with Workers</a></li><li class="chapter-item expanded "><a href="systemd-with-workers/index.html">Systemd</a></li></ol></li></ol></li><li class="chapter-item expanded "><a href="usage/administration/index.html">Administration</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="usage/administration/admin_api/index.html">Admin API</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="admin_api/account_validity.html">Account Validity</a></li><li class="chapter-item expanded "><a href="admin_api/delete_group.html">Delete Group</a></li><li class="chapter-item expanded "><a href="admin_api/event_reports.html">Event Reports</a></li><li class="chapter-item expanded "><a href="admin_api/media_admin_api.html">Media</a></li><li class="chapter-item expanded "><a href="admin_api/purge_history_api.html">Purge History</a></li><li class="chapter-item expanded "><a href="admin_api/purge_room.html">Purge Rooms</a></li><li class="chapter-item expanded "><a href="admin_api/register_api.html">Register Users</a></li><li class="chapter-item expanded "><a href="admin_api/room_membership.html">Manipulate Room Membership</a></li><li class="chapter-item expanded "><a href="admin_api/rooms.html">Rooms</a></li><li class="chapter-item expanded "><a href="admin_api/server_notices.html">Server Notices</a></li><li class="chapter-item expanded "><a href="admin_api/shutdown_room.html">Shutdown Room</a></li><li class="chapter-item expanded "><a href="admin_api/statistics.html">Statistics</a></li><li class="chapter-item expanded "><a href="admin_api/user_admin_api.html">Users</a></li><li class="chapter-item expanded "><a href="admin_api/version_api.html">Server Version</a></li></ol></li><li class="chapter-item expanded "><a href="manhole.html">Manhole</a></li><li class="chapter-item expanded "><a href="metrics-howto.html">Monitoring</a></li><li class="chapter-item expanded "><a href="usage/administration/request_log.html">Request log format</a></li><li class="chapter-item expanded "><div>Scripts</div></li></ol></li><li class="chapter-item expanded "><li class="part-title">Development</li><li class="chapter-item expanded "><a href="development/contributing_guide.html">Contributing Guide</a></li><li class="chapter-item expanded "><a href="code_style.html">Code Style</a></li><li class="chapter-item expanded "><a href="dev/git.html">Git Usage</a></li><li class="chapter-item expanded "><div>Testing</div></li><li class="chapter-item expanded "><a href="opentracing.html">OpenTracing</a></li><li class="chapter-item expanded "><a href="development/database_schema.html">Database Schemas</a></li><li class="chapter-item expanded "><div>Synapse Architecture</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="log_contexts.html" class="active">Log Contexts</a></li><li class="chapter-item expanded "><a href="replication.html">Replication</a></li><li class="chapter-item expanded "><a href="tcp_replication.html">TCP Replication</a></li></ol></li><li class="chapter-item expanded "><a href="development/internal_documentation/index.html">Internal Documentation</a></li><li><ol class="section"><li class="chapter-item expanded "><div>Single Sign-On</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="dev/saml.html">SAML</a></li><li class="chapter-item expanded "><a href="dev/cas.html">CAS</a></li></ol></li><li class="chapter-item expanded "><div>State Resolution</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="auth_chain_difference_algorithm.html">The Auth Chain Difference Algorithm</a></li></ol></li><li class="chapter-item expanded "><a href="media_repository.html">Media Repository</a></li><li class="chapter-item expanded "><a href="room_and_user_statistics.html">Room and User Statistics</a></li></ol></li><li class="chapter-item expanded "><div>Scripts</div></li><li class="chapter-item expanded affix "><li class="part-title">Other</li><li class="chapter-item expanded "><a href="deprecation_policy.html">Dependency Deprecation Policy</a></li></ol>
  74. </div>
  75. <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
  76. </nav>
  77. <div id="page-wrapper" class="page-wrapper">
  78. <div class="page">
  79. <div id="menu-bar-hover-placeholder"></div>
  80. <div id="menu-bar" class="menu-bar sticky bordered">
  81. <div class="left-buttons">
  82. <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
  83. <i class="fa fa-bars"></i>
  84. </button>
  85. <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
  86. <i class="fa fa-paint-brush"></i>
  87. </button>
  88. <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
  89. <li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
  90. <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
  91. <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
  92. <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
  93. <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
  94. </ul>
  95. <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
  96. <i class="fa fa-search"></i>
  97. </button>
  98. </div>
  99. <h1 class="menu-title">Synapse</h1>
  100. <div class="right-buttons">
  101. <a href="print.html" title="Print this book" aria-label="Print this book">
  102. <i id="print-button" class="fa fa-print"></i>
  103. </a>
  104. <a href="https://github.com/matrix-org/synapse" title="Git repository" aria-label="Git repository">
  105. <i id="git-repository-button" class="fa fa-github"></i>
  106. </a>
  107. <a href="https://github.com/matrix-org/synapse/edit/develop/docs/log_contexts.md" title="Suggest an edit" aria-label="Suggest an edit">
  108. <i id="git-edit-button" class="fa fa-edit"></i>
  109. </a>
  110. </div>
  111. </div>
  112. <div id="search-wrapper" class="hidden">
  113. <form id="searchbar-outer" class="searchbar-outer">
  114. <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
  115. </form>
  116. <div id="searchresults-outer" class="searchresults-outer hidden">
  117. <div id="searchresults-header" class="searchresults-header"></div>
  118. <ul id="searchresults">
  119. </ul>
  120. </div>
  121. </div>
  122. <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
  123. <script type="text/javascript">
  124. document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
  125. document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
  126. Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
  127. link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
  128. });
  129. </script>
  130. <div id="content" class="content">
  131. <main>
  132. <!-- Page table of contents -->
  133. <div class="sidetoc">
  134. <nav class="pagetoc"></nav>
  135. </div>
  136. <h1 id="log-contexts"><a class="header" href="#log-contexts">Log Contexts</a></h1>
  137. <p>To help track the processing of individual requests, synapse uses a
  138. '<code>log context</code>' to track which request it is handling at any given
  139. moment. This is done via a thread-local variable; a <code>logging.Filter</code> is
  140. then used to fish the information back out of the thread-local variable
  141. and add it to each log record.</p>
  142. <p>Logcontexts are also used for CPU and database accounting, so that we
  143. can track which requests were responsible for high CPU use or database
  144. activity.</p>
  145. <p>The <code>synapse.logging.context</code> module provides a facilities for managing
  146. the current log context (as well as providing the <code>LoggingContextFilter</code>
  147. class).</p>
  148. <p>Deferreds make the whole thing complicated, so this document describes
  149. how it all works, and how to write code which follows the rules.</p>
  150. <p>##Logcontexts without Deferreds</p>
  151. <p>In the absence of any Deferred voodoo, things are simple enough. As with
  152. any code of this nature, the rule is that our function should leave
  153. things as it found them:</p>
  154. <pre><code class="language-python">from synapse.logging import context # omitted from future snippets
  155. def handle_request(request_id):
  156. request_context = context.LoggingContext()
  157. calling_context = context.set_current_context(request_context)
  158. try:
  159. request_context.request = request_id
  160. do_request_handling()
  161. logger.debug(&quot;finished&quot;)
  162. finally:
  163. context.set_current_context(calling_context)
  164. def do_request_handling():
  165. logger.debug(&quot;phew&quot;) # this will be logged against request_id
  166. </code></pre>
  167. <p>LoggingContext implements the context management methods, so the above
  168. can be written much more succinctly as:</p>
  169. <pre><code class="language-python">def handle_request(request_id):
  170. with context.LoggingContext() as request_context:
  171. request_context.request = request_id
  172. do_request_handling()
  173. logger.debug(&quot;finished&quot;)
  174. def do_request_handling():
  175. logger.debug(&quot;phew&quot;)
  176. </code></pre>
  177. <h2 id="using-logcontexts-with-deferreds"><a class="header" href="#using-logcontexts-with-deferreds">Using logcontexts with Deferreds</a></h2>
  178. <p>Deferreds --- and in particular, <code>defer.inlineCallbacks</code> --- break the
  179. linear flow of code so that there is no longer a single entry point
  180. where we should set the logcontext and a single exit point where we
  181. should remove it.</p>
  182. <p>Consider the example above, where <code>do_request_handling</code> needs to do some
  183. blocking operation, and returns a deferred:</p>
  184. <pre><code class="language-python">@defer.inlineCallbacks
  185. def handle_request(request_id):
  186. with context.LoggingContext() as request_context:
  187. request_context.request = request_id
  188. yield do_request_handling()
  189. logger.debug(&quot;finished&quot;)
  190. </code></pre>
  191. <p>In the above flow:</p>
  192. <ul>
  193. <li>The logcontext is set</li>
  194. <li><code>do_request_handling</code> is called, and returns a deferred</li>
  195. <li><code>handle_request</code> yields the deferred</li>
  196. <li>The <code>inlineCallbacks</code> wrapper of <code>handle_request</code> returns a deferred</li>
  197. </ul>
  198. <p>So we have stopped processing the request (and will probably go on to
  199. start processing the next), without clearing the logcontext.</p>
  200. <p>To circumvent this problem, synapse code assumes that, wherever you have
  201. a deferred, you will want to yield on it. To that end, whereever
  202. functions return a deferred, we adopt the following conventions:</p>
  203. <p><strong>Rules for functions returning deferreds:</strong></p>
  204. <blockquote>
  205. <ul>
  206. <li>If the deferred is already complete, the function returns with the
  207. same logcontext it started with.</li>
  208. <li>If the deferred is incomplete, the function clears the logcontext
  209. before returning; when the deferred completes, it restores the
  210. logcontext before running any callbacks.</li>
  211. </ul>
  212. </blockquote>
  213. <p>That sounds complicated, but actually it means a lot of code (including
  214. the example above) &quot;just works&quot;. There are two cases:</p>
  215. <ul>
  216. <li>
  217. <p>If <code>do_request_handling</code> returns a completed deferred, then the
  218. logcontext will still be in place. In this case, execution will
  219. continue immediately after the <code>yield</code>; the &quot;finished&quot; line will
  220. be logged against the right context, and the <code>with</code> block restores
  221. the original context before we return to the caller.</p>
  222. </li>
  223. <li>
  224. <p>If the returned deferred is incomplete, <code>do_request_handling</code> clears
  225. the logcontext before returning. The logcontext is therefore clear
  226. when <code>handle_request</code> yields the deferred. At that point, the
  227. <code>inlineCallbacks</code> wrapper adds a callback to the deferred, and
  228. returns another (incomplete) deferred to the caller, and it is safe
  229. to begin processing the next request.</p>
  230. <p>Once <code>do_request_handling</code>'s deferred completes, it will reinstate
  231. the logcontext, before running the callback added by the
  232. <code>inlineCallbacks</code> wrapper. That callback runs the second half of
  233. <code>handle_request</code>, so again the &quot;finished&quot; line will be logged
  234. against the right context, and the <code>with</code> block restores the
  235. original context.</p>
  236. </li>
  237. </ul>
  238. <p>As an aside, it's worth noting that <code>handle_request</code> follows our rules
  239. -though that only matters if the caller has its own logcontext which it
  240. cares about.</p>
  241. <p>The following sections describe pitfalls and helpful patterns when
  242. implementing these rules.</p>
  243. <h2 id="always-yield-your-deferreds"><a class="header" href="#always-yield-your-deferreds">Always yield your deferreds</a></h2>
  244. <p>Whenever you get a deferred back from a function, you should <code>yield</code> on
  245. it as soon as possible. (Returning it directly to your caller is ok too,
  246. if you're not doing <code>inlineCallbacks</code>.) Do not pass go; do not do any
  247. logging; do not call any other functions.</p>
  248. <pre><code class="language-python">@defer.inlineCallbacks
  249. def fun():
  250. logger.debug(&quot;starting&quot;)
  251. yield do_some_stuff() # just like this
  252. d = more_stuff()
  253. result = yield d # also fine, of course
  254. return result
  255. def nonInlineCallbacksFun():
  256. logger.debug(&quot;just a wrapper really&quot;)
  257. return do_some_stuff() # this is ok too - the caller will yield on
  258. # it anyway.
  259. </code></pre>
  260. <p>Provided this pattern is followed all the way back up to the callchain
  261. to where the logcontext was set, this will make things work out ok:
  262. provided <code>do_some_stuff</code> and <code>more_stuff</code> follow the rules above, then
  263. so will <code>fun</code> (as wrapped by <code>inlineCallbacks</code>) and
  264. <code>nonInlineCallbacksFun</code>.</p>
  265. <p>It's all too easy to forget to <code>yield</code>: for instance if we forgot that
  266. <code>do_some_stuff</code> returned a deferred, we might plough on regardless. This
  267. leads to a mess; it will probably work itself out eventually, but not
  268. before a load of stuff has been logged against the wrong context.
  269. (Normally, other things will break, more obviously, if you forget to
  270. <code>yield</code>, so this tends not to be a major problem in practice.)</p>
  271. <p>Of course sometimes you need to do something a bit fancier with your
  272. Deferreds - not all code follows the linear A-then-B-then-C pattern.
  273. Notes on implementing more complex patterns are in later sections.</p>
  274. <h2 id="where-you-create-a-new-deferred-make-it-follow-the-rules"><a class="header" href="#where-you-create-a-new-deferred-make-it-follow-the-rules">Where you create a new Deferred, make it follow the rules</a></h2>
  275. <p>Most of the time, a Deferred comes from another synapse function.
  276. Sometimes, though, we need to make up a new Deferred, or we get a
  277. Deferred back from external code. We need to make it follow our rules.</p>
  278. <p>The easy way to do it is with a combination of <code>defer.inlineCallbacks</code>,
  279. and <code>context.PreserveLoggingContext</code>. Suppose we want to implement
  280. <code>sleep</code>, which returns a deferred which will run its callbacks after a
  281. given number of seconds. That might look like:</p>
  282. <pre><code class="language-python"># not a logcontext-rules-compliant function
  283. def get_sleep_deferred(seconds):
  284. d = defer.Deferred()
  285. reactor.callLater(seconds, d.callback, None)
  286. return d
  287. </code></pre>
  288. <p>That doesn't follow the rules, but we can fix it by wrapping it with
  289. <code>PreserveLoggingContext</code> and <code>yield</code> ing on it:</p>
  290. <pre><code class="language-python">@defer.inlineCallbacks
  291. def sleep(seconds):
  292. with PreserveLoggingContext():
  293. yield get_sleep_deferred(seconds)
  294. </code></pre>
  295. <p>This technique works equally for external functions which return
  296. deferreds, or deferreds we have made ourselves.</p>
  297. <p>You can also use <code>context.make_deferred_yieldable</code>, which just does the
  298. boilerplate for you, so the above could be written:</p>
  299. <pre><code class="language-python">def sleep(seconds):
  300. return context.make_deferred_yieldable(get_sleep_deferred(seconds))
  301. </code></pre>
  302. <h2 id="fire-and-forget"><a class="header" href="#fire-and-forget">Fire-and-forget</a></h2>
  303. <p>Sometimes you want to fire off a chain of execution, but not wait for
  304. its result. That might look a bit like this:</p>
  305. <pre><code class="language-python">@defer.inlineCallbacks
  306. def do_request_handling():
  307. yield foreground_operation()
  308. # *don't* do this
  309. background_operation()
  310. logger.debug(&quot;Request handling complete&quot;)
  311. @defer.inlineCallbacks
  312. def background_operation():
  313. yield first_background_step()
  314. logger.debug(&quot;Completed first step&quot;)
  315. yield second_background_step()
  316. logger.debug(&quot;Completed second step&quot;)
  317. </code></pre>
  318. <p>The above code does a couple of steps in the background after
  319. <code>do_request_handling</code> has finished. The log lines are still logged
  320. against the <code>request_context</code> logcontext, which may or may not be
  321. desirable. There are two big problems with the above, however. The first
  322. problem is that, if <code>background_operation</code> returns an incomplete
  323. Deferred, it will expect its caller to <code>yield</code> immediately, so will have
  324. cleared the logcontext. In this example, that means that 'Request
  325. handling complete' will be logged without any context.</p>
  326. <p>The second problem, which is potentially even worse, is that when the
  327. Deferred returned by <code>background_operation</code> completes, it will restore
  328. the original logcontext. There is nothing waiting on that Deferred, so
  329. the logcontext will leak into the reactor and possibly get attached to
  330. some arbitrary future operation.</p>
  331. <p>There are two potential solutions to this.</p>
  332. <p>One option is to surround the call to <code>background_operation</code> with a
  333. <code>PreserveLoggingContext</code> call. That will reset the logcontext before
  334. starting <code>background_operation</code> (so the context restored when the
  335. deferred completes will be the empty logcontext), and will restore the
  336. current logcontext before continuing the foreground process:</p>
  337. <pre><code class="language-python">@defer.inlineCallbacks
  338. def do_request_handling():
  339. yield foreground_operation()
  340. # start background_operation off in the empty logcontext, to
  341. # avoid leaking the current context into the reactor.
  342. with PreserveLoggingContext():
  343. background_operation()
  344. # this will now be logged against the request context
  345. logger.debug(&quot;Request handling complete&quot;)
  346. </code></pre>
  347. <p>Obviously that option means that the operations done in
  348. <code>background_operation</code> would be not be logged against a logcontext
  349. (though that might be fixed by setting a different logcontext via a
  350. <code>with LoggingContext(...)</code> in <code>background_operation</code>).</p>
  351. <p>The second option is to use <code>context.run_in_background</code>, which wraps a
  352. function so that it doesn't reset the logcontext even when it returns
  353. an incomplete deferred, and adds a callback to the returned deferred to
  354. reset the logcontext. In other words, it turns a function that follows
  355. the Synapse rules about logcontexts and Deferreds into one which behaves
  356. more like an external function --- the opposite operation to that
  357. described in the previous section. It can be used like this:</p>
  358. <pre><code class="language-python">@defer.inlineCallbacks
  359. def do_request_handling():
  360. yield foreground_operation()
  361. context.run_in_background(background_operation)
  362. # this will now be logged against the request context
  363. logger.debug(&quot;Request handling complete&quot;)
  364. </code></pre>
  365. <h2 id="passing-synapse-deferreds-into-third-party-functions"><a class="header" href="#passing-synapse-deferreds-into-third-party-functions">Passing synapse deferreds into third-party functions</a></h2>
  366. <p>A typical example of this is where we want to collect together two or
  367. more deferred via <code>defer.gatherResults</code>:</p>
  368. <pre><code class="language-python">d1 = operation1()
  369. d2 = operation2()
  370. d3 = defer.gatherResults([d1, d2])
  371. </code></pre>
  372. <p>This is really a variation of the fire-and-forget problem above, in that
  373. we are firing off <code>d1</code> and <code>d2</code> without yielding on them. The difference
  374. is that we now have third-party code attached to their callbacks. Anyway
  375. either technique given in the <a href="#fire-and-forget">Fire-and-forget</a>
  376. section will work.</p>
  377. <p>Of course, the new Deferred returned by <code>gatherResults</code> needs to be
  378. wrapped in order to make it follow the logcontext rules before we can
  379. yield it, as described in <a href="#where-you-create-a-new-deferred-make-it-follow-the-rules">Where you create a new Deferred, make it
  380. follow the
  381. rules</a>.</p>
  382. <p>So, option one: reset the logcontext before starting the operations to
  383. be gathered:</p>
  384. <pre><code class="language-python">@defer.inlineCallbacks
  385. def do_request_handling():
  386. with PreserveLoggingContext():
  387. d1 = operation1()
  388. d2 = operation2()
  389. result = yield defer.gatherResults([d1, d2])
  390. </code></pre>
  391. <p>In this case particularly, though, option two, of using
  392. <code>context.preserve_fn</code> almost certainly makes more sense, so that
  393. <code>operation1</code> and <code>operation2</code> are both logged against the original
  394. logcontext. This looks like:</p>
  395. <pre><code class="language-python">@defer.inlineCallbacks
  396. def do_request_handling():
  397. d1 = context.preserve_fn(operation1)()
  398. d2 = context.preserve_fn(operation2)()
  399. with PreserveLoggingContext():
  400. result = yield defer.gatherResults([d1, d2])
  401. </code></pre>
  402. <h2 id="was-all-this-really-necessary"><a class="header" href="#was-all-this-really-necessary">Was all this really necessary?</a></h2>
  403. <p>The conventions used work fine for a linear flow where everything
  404. happens in series via <code>defer.inlineCallbacks</code> and <code>yield</code>, but are
  405. certainly tricky to follow for any more exotic flows. It's hard not to
  406. wonder if we could have done something else.</p>
  407. <p>We're not going to rewrite Synapse now, so the following is entirely of
  408. academic interest, but I'd like to record some thoughts on an
  409. alternative approach.</p>
  410. <p>I briefly prototyped some code following an alternative set of rules. I
  411. think it would work, but I certainly didn't get as far as thinking how
  412. it would interact with concepts as complicated as the cache descriptors.</p>
  413. <p>My alternative rules were:</p>
  414. <ul>
  415. <li>functions always preserve the logcontext of their caller, whether or
  416. not they are returning a Deferred.</li>
  417. <li>Deferreds returned by synapse functions run their callbacks in the
  418. same context as the function was orignally called in.</li>
  419. </ul>
  420. <p>The main point of this scheme is that everywhere that sets the
  421. logcontext is responsible for clearing it before returning control to
  422. the reactor.</p>
  423. <p>So, for example, if you were the function which started a
  424. <code>with LoggingContext</code> block, you wouldn't <code>yield</code> within it --- instead
  425. you'd start off the background process, and then leave the <code>with</code> block
  426. to wait for it:</p>
  427. <pre><code class="language-python">def handle_request(request_id):
  428. with context.LoggingContext() as request_context:
  429. request_context.request = request_id
  430. d = do_request_handling()
  431. def cb(r):
  432. logger.debug(&quot;finished&quot;)
  433. d.addCallback(cb)
  434. return d
  435. </code></pre>
  436. <p>(in general, mixing <code>with LoggingContext</code> blocks and
  437. <code>defer.inlineCallbacks</code> in the same function leads to slighly
  438. counter-intuitive code, under this scheme).</p>
  439. <p>Because we leave the original <code>with</code> block as soon as the Deferred is
  440. returned (as opposed to waiting for it to be resolved, as we do today),
  441. the logcontext is cleared before control passes back to the reactor; so
  442. if there is some code within <code>do_request_handling</code> which needs to wait
  443. for a Deferred to complete, there is no need for it to worry about
  444. clearing the logcontext before doing so:</p>
  445. <pre><code class="language-python">def handle_request():
  446. r = do_some_stuff()
  447. r.addCallback(do_some_more_stuff)
  448. return r
  449. </code></pre>
  450. <p>--- and provided <code>do_some_stuff</code> follows the rules of returning a
  451. Deferred which runs its callbacks in the original logcontext, all is
  452. happy.</p>
  453. <p>The business of a Deferred which runs its callbacks in the original
  454. logcontext isn't hard to achieve --- we have it today, in the shape of
  455. <code>context._PreservingContextDeferred</code>:</p>
  456. <pre><code class="language-python">def do_some_stuff():
  457. deferred = do_some_io()
  458. pcd = _PreservingContextDeferred(LoggingContext.current_context())
  459. deferred.chainDeferred(pcd)
  460. return pcd
  461. </code></pre>
  462. <p>It turns out that, thanks to the way that Deferreds chain together, we
  463. automatically get the property of a context-preserving deferred with
  464. <code>defer.inlineCallbacks</code>, provided the final Defered the function
  465. <code>yields</code> on has that property. So we can just write:</p>
  466. <pre><code class="language-python">@defer.inlineCallbacks
  467. def handle_request():
  468. yield do_some_stuff()
  469. yield do_some_more_stuff()
  470. </code></pre>
  471. <p>To conclude: I think this scheme would have worked equally well, with
  472. less danger of messing it up, and probably made some more esoteric code
  473. easier to write. But again --- changing the conventions of the entire
  474. Synapse codebase is not a sensible option for the marginal improvement
  475. offered.</p>
  476. <h2 id="a-note-on-garbage-collection-of-deferred-chains"><a class="header" href="#a-note-on-garbage-collection-of-deferred-chains">A note on garbage-collection of Deferred chains</a></h2>
  477. <p>It turns out that our logcontext rules do not play nicely with Deferred
  478. chains which get orphaned and garbage-collected.</p>
  479. <p>Imagine we have some code that looks like this:</p>
  480. <pre><code class="language-python">listener_queue = []
  481. def on_something_interesting():
  482. for d in listener_queue:
  483. d.callback(&quot;foo&quot;)
  484. @defer.inlineCallbacks
  485. def await_something_interesting():
  486. new_deferred = defer.Deferred()
  487. listener_queue.append(new_deferred)
  488. with PreserveLoggingContext():
  489. yield new_deferred
  490. </code></pre>
  491. <p>Obviously, the idea here is that we have a bunch of things which are
  492. waiting for an event. (It's just an example of the problem here, but a
  493. relatively common one.)</p>
  494. <p>Now let's imagine two further things happen. First of all, whatever was
  495. waiting for the interesting thing goes away. (Perhaps the request times
  496. out, or something <em>even more</em> interesting happens.)</p>
  497. <p>Secondly, let's suppose that we decide that the interesting thing is
  498. never going to happen, and we reset the listener queue:</p>
  499. <pre><code class="language-python">def reset_listener_queue():
  500. listener_queue.clear()
  501. </code></pre>
  502. <p>So, both ends of the deferred chain have now dropped their references,
  503. and the deferred chain is now orphaned, and will be garbage-collected at
  504. some point. Note that <code>await_something_interesting</code> is a generator
  505. function, and when Python garbage-collects generator functions, it gives
  506. them a chance to clean up by making the <code>yield</code> raise a <code>GeneratorExit</code>
  507. exception. In our case, that means that the <code>__exit__</code> handler of
  508. <code>PreserveLoggingContext</code> will carefully restore the request context, but
  509. there is now nothing waiting for its return, so the request context is
  510. never cleared.</p>
  511. <p>To reiterate, this problem only arises when <em>both</em> ends of a deferred
  512. chain are dropped. Dropping the the reference to a deferred you're
  513. supposed to be calling is probably bad practice, so this doesn't
  514. actually happen too much. Unfortunately, when it does happen, it will
  515. lead to leaked logcontexts which are incredibly hard to track down.</p>
  516. </main>
  517. <nav class="nav-wrapper" aria-label="Page navigation">
  518. <!-- Mobile navigation buttons -->
  519. <a rel="prev" href="development/database_schema.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
  520. <i class="fa fa-angle-left"></i>
  521. </a>
  522. <a rel="next" href="replication.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
  523. <i class="fa fa-angle-right"></i>
  524. </a>
  525. <div style="clear: both"></div>
  526. </nav>
  527. </div>
  528. </div>
  529. <nav class="nav-wide-wrapper" aria-label="Page navigation">
  530. <a rel="prev" href="development/database_schema.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
  531. <i class="fa fa-angle-left"></i>
  532. </a>
  533. <a rel="next" href="replication.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
  534. <i class="fa fa-angle-right"></i>
  535. </a>
  536. </nav>
  537. </div>
  538. <script type="text/javascript">
  539. window.playground_copyable = true;
  540. </script>
  541. <script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
  542. <script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
  543. <script src="searcher.js" type="text/javascript" charset="utf-8"></script>
  544. <script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
  545. <script src="highlight.js" type="text/javascript" charset="utf-8"></script>
  546. <script src="book.js" type="text/javascript" charset="utf-8"></script>
  547. <!-- Custom JS scripts -->
  548. <script type="text/javascript" src="docs/website_files/table-of-contents.js"></script>
  549. </body>
  550. </html>