| 1 | <html xmlns="http://www.w3.org/1999/xhtml"> |
|---|
| 2 | <head> |
|---|
| 3 | <title>Foolscap Failure Reporting</title> |
|---|
| 4 | <style src="stylesheet-unprocessed.css"></style> |
|---|
| 5 | </head> |
|---|
| 6 | |
|---|
| 7 | <body> |
|---|
| 8 | <h1>Foolscap Failure Reporting</h1> |
|---|
| 9 | |
|---|
| 10 | <h2>Signalling Remote Exceptions</h2> |
|---|
| 11 | |
|---|
| 12 | <p>The <code>remote_</code> -prefixed methods which Foolscap invokes, just |
|---|
| 13 | like their local counterparts, can either return a value or raise an |
|---|
| 14 | exception. Foolscap callers can use the normal Twisted conventions for |
|---|
| 15 | handling asyncronous failures: <code>callRemote</code> returns a Deferred |
|---|
| 16 | object, which will eventually either fire its callback function (if the |
|---|
| 17 | remote method returned a normal value), or its errback function (if the |
|---|
| 18 | remote method raised an exception).</p> |
|---|
| 19 | |
|---|
| 20 | <p>There are several reasons that the Deferred returned |
|---|
| 21 | by <code>callRemote</code> might fire its errback:</p> |
|---|
| 22 | |
|---|
| 23 | <ul> |
|---|
| 24 | <li>local outbound schema violation: the outbound method arguments did not |
|---|
| 25 | match the <code>RemoteInterface</code> that is in force. This is an |
|---|
| 26 | optional form of typechecking for remote calls, and is activated when |
|---|
| 27 | the remote object describes itself as conforming to a named |
|---|
| 28 | <code>RemoteInterface</code> which is also declared in a local class. |
|---|
| 29 | The local constraints are checked before the message is transmitted over |
|---|
| 30 | the wire. A constraint violation is indicated by |
|---|
| 31 | raising <code>foolscap.schema.Violation</code>, which is delivered |
|---|
| 32 | through the Deferred's errback.</li> |
|---|
| 33 | <li>network partition: if the underlying TCP connection is lost before the |
|---|
| 34 | response has been received, the Deferred will errback with |
|---|
| 35 | a <code>foolscap.ipb.DeadReferenceError</code> exception. Several things |
|---|
| 36 | can cause this: the remote process shutting down (intentionally or |
|---|
| 37 | otherwise), a network partition or timeout, or the local process |
|---|
| 38 | shutting down (<code>Tub.stopService</code> will terminate all |
|---|
| 39 | outstanding remote messages before shutdown).</li> |
|---|
| 40 | <li>remote inbound schema violation: as the serialized method arguments were |
|---|
| 41 | unpacked by the remote process, one of them violated that processes |
|---|
| 42 | inbound <code>RemoteInterface</code>. This check serves to protect each |
|---|
| 43 | process from incorrect types which might either confuse the subsequent |
|---|
| 44 | code or consume a lot of memory. These constraints are enforced as the |
|---|
| 45 | tokens are read off the wire, and are signalled with the |
|---|
| 46 | same <code>Violation</code> exception as above (but this may be wrapped |
|---|
| 47 | in a <code>RemoteException</code>: see below).</li> |
|---|
| 48 | <li>remote method exception: if the <code>remote_</code> method raises an |
|---|
| 49 | exception, or returns a Deferred which subsequently fires its errback, |
|---|
| 50 | the remote side will send the caller that an exception occurred, and may |
|---|
| 51 | attempt to provide some information about this exception. The caller |
|---|
| 52 | will see an errback that may or may not attempt to replicate the remote |
|---|
| 53 | exception. This may be wrapped in a <code>RemoteException</code>. See |
|---|
| 54 | below for more details.</li> |
|---|
| 55 | <li>remote outbound schema violation: as the remote method's return value is |
|---|
| 56 | serialized and put on the wire, the values are compared against the |
|---|
| 57 | return-value constraint (if a <code>RemoteInterface</code> is in |
|---|
| 58 | effect). If it does not match the constraint, a Violation will be raised |
|---|
| 59 | (but may be wrapped in a <code>RemoteException</code>).</li> |
|---|
| 60 | <li>local inbound schema violation: when the serialized return value arrives |
|---|
| 61 | on the original caller's side of the wire, the return-value constraint |
|---|
| 62 | of any effective <code>RemoteInterface</code> will be applied. This |
|---|
| 63 | protects the caller's response code from unexpected values. Any |
|---|
| 64 | mismatches will be signalled with a Violation exception.</li> |
|---|
| 65 | </ul> |
|---|
| 66 | |
|---|
| 67 | <h2>Distinguishing Remote Exceptions</h2> |
|---|
| 68 | |
|---|
| 69 | <p>When a remote call fails, what should you do about it? There are several |
|---|
| 70 | factors to consider. Raising exceptions may be part of your remote API: |
|---|
| 71 | easy-to-use exceptions are a big part of Python's success, and Foolscap |
|---|
| 72 | provides the tools to use them in a remote-calling environment as well. |
|---|
| 73 | Exceptions which are not meant to be part of the API frequently indicate |
|---|
| 74 | bugs, sometimes as precondition assertions (of which schema Violations are a |
|---|
| 75 | subset). It might be useful to react to the specific type of remote |
|---|
| 76 | exception, and/or it might be important to log as much information as |
|---|
| 77 | possible so a programmer can find out what went wrong, and in either case it |
|---|
| 78 | might be appropriate to react by falling back to some alternative code |
|---|
| 79 | path.</p> |
|---|
| 80 | |
|---|
| 81 | <p>Good debuggability frequently requires at least one side of the connection |
|---|
| 82 | to get lots of information about errors that indicate possible bugs. Note |
|---|
| 83 | that the <code>Tub.setOption("logLocalFailures", True)</code> |
|---|
| 84 | and <code>Tub.setOption("logRemoteFailures", True)</code> options are |
|---|
| 85 | relevant: when these options are enabled, exceptions that are sent over the |
|---|
| 86 | wire (in one direction or the other) are recorded in the Foolscap log stream. |
|---|
| 87 | If you use exceptions as part of your regular remote-object API, you may want |
|---|
| 88 | to consider disabling both options. Otherwise the logs may be cluttered with |
|---|
| 89 | perfectly harmless exceptions.</p> |
|---|
| 90 | |
|---|
| 91 | <p>Should your code pay attention to the details of a remote exception (other |
|---|
| 92 | than the fact that an exception happened at all)? There are roughly two |
|---|
| 93 | schools of thought:</p> |
|---|
| 94 | |
|---|
| 95 | <ul> |
|---|
| 96 | <li>Distrust Outsiders: assume, like any sensible program which connects to |
|---|
| 97 | the internet, that the entire world is out to get you. Use external |
|---|
| 98 | services to the extent you can, but don't allow them to confuse you or |
|---|
| 99 | trick you into some code path that will expose a vulnerability. Treat all |
|---|
| 100 | remote exceptions as identical.</li> |
|---|
| 101 | |
|---|
| 102 | <li>"E" mode: treat external code with the same level of trust or distrust |
|---|
| 103 | that you would apply to local code. In the "E" programming language (which |
|---|
| 104 | inspires much of Foolscap's feature set), each object is a separate trust |
|---|
| 105 | domain, and the only distinction made between "local" and "remote" objects |
|---|
| 106 | is that the former may be called synchronously, while the latter may become |
|---|
| 107 | partitioned. Treat remote exceptions just like local ones, interpreting |
|---|
| 108 | their type as best you can.</li> |
|---|
| 109 | </ul> |
|---|
| 110 | |
|---|
| 111 | <p>From Foolscap's point of view, what we care about is how to handle |
|---|
| 112 | exceptions raised by the remote code. When operating in the first mode, |
|---|
| 113 | Foolscap will merge all remote exceptions into a single exception type |
|---|
| 114 | named <code>foolscap.api.RemoteException</code>, which cannot be confused |
|---|
| 115 | with regular Python exceptions like <code>KeyError</code> |
|---|
| 116 | and <code>AttributeError</code>. In the second mode, Foolscap will try to |
|---|
| 117 | convert each remote exception into a corresponding local object, so that |
|---|
| 118 | error-handling code can catch e.g. <code>KeyError</code> and use it as part |
|---|
| 119 | of the remote API.</p> |
|---|
| 120 | |
|---|
| 121 | <p>To tell Foolscap which mode you want to use, |
|---|
| 122 | call <code>tub.setOption("expose-remote-exception-types", BOOL)</code>, where |
|---|
| 123 | BOOL is either True (for the "E mode") or False (for the "Distrust Outsiders" |
|---|
| 124 | mode). The default is True.</p> |
|---|
| 125 | |
|---|
| 126 | <p>In "Distrust Outsiders" mode, a remote exception will cause the caller's |
|---|
| 127 | errback handler to be called with a regular <code>Failure</code> object which |
|---|
| 128 | contains a <code>foolscap.api.RemoteException</code>, effectively hiding all |
|---|
| 129 | information about the nature of the problem except that it was caused by some |
|---|
| 130 | other system. Caller code can test for this with <code>f.check</code> |
|---|
| 131 | and <code>f.trap</code> as usual. If the caller's code decides to investigate |
|---|
| 132 | further, it can use <code>f.value.failure</code> to obtain |
|---|
| 133 | the <code>CopiedFailure</code> (see below) that arrived from the remote |
|---|
| 134 | system. Note that schema Violations which are caught on the local system are |
|---|
| 135 | reported normally, whereas Violations which are caught on the remote system |
|---|
| 136 | are reported as RemoteExceptions.</p> |
|---|
| 137 | |
|---|
| 138 | <p>In "E mode", a remote exception will cause the errback handler to be |
|---|
| 139 | called with a <code>CopiedFailure</code> object. |
|---|
| 140 | This <code>CopiedFailure</code> will behave as much as possible like the |
|---|
| 141 | corresponding Failure from the remote side, given the limitations of the |
|---|
| 142 | serialization process (see below for details). In particular, if the remote |
|---|
| 143 | side raises e.g. a standard Python <code>IndexError</code>, the local side |
|---|
| 144 | can use <code>f.trap(IndexError)</code> to catch it. However, this same |
|---|
| 145 | f.trap call would also catch locally-generated IndexErrors, which could be |
|---|
| 146 | confusing.</p> |
|---|
| 147 | |
|---|
| 148 | <h3>Examples: Distrust Outsiders</h3> |
|---|
| 149 | |
|---|
| 150 | <p>Since Deferreds can be chained, it is quite common to see remote calls |
|---|
| 151 | sandwiched in the middle of two (possibly asynchronous) local calls. The |
|---|
| 152 | following snippet performs a local processing step, then asks a remote server |
|---|
| 153 | for information, then adds that information into a local database. All three |
|---|
| 154 | steps are asynchronous.</p> |
|---|
| 155 | |
|---|
| 156 | <pre class="python"> |
|---|
| 157 | # Example 1 |
|---|
| 158 | def get_and_store_record(name): |
|---|
| 159 | d = local_db.getIDNumber(name) |
|---|
| 160 | d.addCallback(lambda idnum: rref.callRemote("get_record", idnum)) |
|---|
| 161 | d.addCallback(lambda record: local_db.storeRecord(name)) |
|---|
| 162 | return d |
|---|
| 163 | </pre> |
|---|
| 164 | |
|---|
| 165 | <p>To motivate an examination of error handling, we'll extend this example to |
|---|
| 166 | use two separate servers for the record: if one of them doesn't have it, we |
|---|
| 167 | ask the other. The first server might raise <code>KeyError</code> to tell us |
|---|
| 168 | it can't find the record, or it might experience some other internal error, |
|---|
| 169 | or we might lose the connection to that server before it can get us an |
|---|
| 170 | answer: all three cases should prompt us to talk to the second server.</p> |
|---|
| 171 | |
|---|
| 172 | <pre class="python"> |
|---|
| 173 | # Example 2 |
|---|
| 174 | from foolscap.api import Tub, RemoteException |
|---|
| 175 | t = Tub() |
|---|
| 176 | t.setOption("expose-remote-exception-types", False) # Distrust Outsiders |
|---|
| 177 | ... |
|---|
| 178 | |
|---|
| 179 | def get_and_store_record(name): |
|---|
| 180 | d = local_db.getIDNumber(name) |
|---|
| 181 | def get_record(idnum): |
|---|
| 182 | d2 = server1.callRemote("get_record", idnum) # could raise KeyError |
|---|
| 183 | def maybe_try_server2(f): |
|---|
| 184 | f.trap(RemoteException) |
|---|
| 185 | return server2.callRemote("get_record", idnum) # or KeyError |
|---|
| 186 | d2.addErrback(maybe_try_server2) |
|---|
| 187 | return d2 |
|---|
| 188 | d.addCallback(get_record) |
|---|
| 189 | d.addCallback(lambda record: local_db.storeRecord(name)) |
|---|
| 190 | return d |
|---|
| 191 | </pre> |
|---|
| 192 | |
|---|
| 193 | <p>In this example, only a failure that occurs on server1 will cause the code |
|---|
| 194 | to attempt to use server2. A locally-triggered error will be trapped by the |
|---|
| 195 | first line of <code>maybe_try_server2</code> and will not proceed to the |
|---|
| 196 | second <code>callRemote</code>. This allows a more complex control flow like |
|---|
| 197 | the following:</p> |
|---|
| 198 | |
|---|
| 199 | <pre class="python"> |
|---|
| 200 | # Example 3 |
|---|
| 201 | def get_and_store_record(name): |
|---|
| 202 | d = local_db.getIDNumber(name) # could raise IndexError |
|---|
| 203 | |
|---|
| 204 | def get_record(idnum): |
|---|
| 205 | d2 = server1.callRemote("get_record", idnum) # or KeyError |
|---|
| 206 | def maybe_try_server2(f): |
|---|
| 207 | f.trap(RemoteException) |
|---|
| 208 | return server2.callRemote("get_record", idnum) # or KeyError |
|---|
| 209 | d2.addErrback(maybe_try_server2) |
|---|
| 210 | return d2 |
|---|
| 211 | d.addCallback(get_record) |
|---|
| 212 | |
|---|
| 213 | d.addCallback(lambda record: local_db.storeRecord(name)) |
|---|
| 214 | |
|---|
| 215 | def ignore_unknown_names(f): |
|---|
| 216 | f.trap(IndexError) |
|---|
| 217 | print "Couldn't get ID for name, ignoring" |
|---|
| 218 | return None |
|---|
| 219 | d.addErrback(ignore_unknown_names) |
|---|
| 220 | |
|---|
| 221 | def failed(f): |
|---|
| 222 | print "didn't get data!" |
|---|
| 223 | if f.check(RemoteException): |
|---|
| 224 | if f.value.failure.check(KeyError): |
|---|
| 225 | print "both servers claim to not have the record" |
|---|
| 226 | else: |
|---|
| 227 | print "both servers had error" |
|---|
| 228 | else: |
|---|
| 229 | print "local error" |
|---|
| 230 | print "error details:", f |
|---|
| 231 | d.addErrback(failed) |
|---|
| 232 | |
|---|
| 233 | return d |
|---|
| 234 | </pre> |
|---|
| 235 | |
|---|
| 236 | <p>The final <code>failed</code> method will catch any unexpected error: this |
|---|
| 237 | is the place where you want to log enough information to diagnose a code bug. |
|---|
| 238 | For example, if the database fetch had returned a string, but the |
|---|
| 239 | RemoteInterface had declared <code>get_record</code> as taking an integer, |
|---|
| 240 | then the <code>callRemote</code> would signal a (local) Violation exception, |
|---|
| 241 | causing control to drop directly to the <code>failed()</code> error handler. |
|---|
| 242 | On the other hand, if the first server decided to throw a Violation on its |
|---|
| 243 | inbound argument, the <code>callRemote</code> would signal a RemoteException |
|---|
| 244 | (wrapping a Violation), and control would flow to |
|---|
| 245 | the <code>maybe_try_server2</code> fallback.</p> |
|---|
| 246 | |
|---|
| 247 | <p>It is usually best to put the errback as close as possible to the call |
|---|
| 248 | which might fail, since this provides the highest "signal to noise ratio" |
|---|
| 249 | (i.e. it reduces the number of possibilities that the error-handler code must |
|---|
| 250 | handle). But it is frequently more convenient to place the errback later in |
|---|
| 251 | the Deferred chain, so it can be useful to distinguish between the |
|---|
| 252 | local <code>IndexError</code> and a remote exception of the same type. This |
|---|
| 253 | is the same decision that needs to be made with synchronous code: whether to |
|---|
| 254 | use lots of <code>try:/except:</code> blocks wrapped around individual method |
|---|
| 255 | calls, or to use one big block around a whole sequence of calls. Smaller |
|---|
| 256 | blocks will catch an exception sooner, but larger blocks are less effort to |
|---|
| 257 | write, and can be more appropriate, especially if you do not expect |
|---|
| 258 | exceptions to happen very often.</p> |
|---|
| 259 | |
|---|
| 260 | <p>Note that if this example had used "E mode" and the first remote server |
|---|
| 261 | decided (perhaps maliciously) to raise <code>IndexError</code>, then the |
|---|
| 262 | client could be tricked into following the same ignore-unknown-names code |
|---|
| 263 | path that was meant to be reserved for a local database miss.</p> |
|---|
| 264 | |
|---|
| 265 | <p>To examine the type of failure more closely, the error-handling code |
|---|
| 266 | should access the <code>RemoteException</code>'s <code>.value.failure</code> |
|---|
| 267 | attribute. By making the following change to <code>maybe_try_server2</code>, |
|---|
| 268 | the behavior is changed to only query the second server in the specific case |
|---|
| 269 | of a remote <code>KeyError</code>. Other remote exceptions (and all local |
|---|
| 270 | exceptions) will skip the second query and signal an error |
|---|
| 271 | to <code>failed()</code>. You might want to do this if you believe that a |
|---|
| 272 | remote failure like <code>AttributeError</code> is worthy of error-logging |
|---|
| 273 | rather than fallback behavior.</p> |
|---|
| 274 | |
|---|
| 275 | <pre class="python"> |
|---|
| 276 | # Example 4 |
|---|
| 277 | def maybe_try_server2(f): |
|---|
| 278 | f.trap(RemoteException) |
|---|
| 279 | if f.value.failure.check(KeyError): |
|---|
| 280 | return server2.callRemote("get_record", idnum) # or KeyError |
|---|
| 281 | return f |
|---|
| 282 | </pre> |
|---|
| 283 | |
|---|
| 284 | <p>Note that you should probably not use <code>f.value.failure.trap</code>, |
|---|
| 285 | since if the exception type does not match, that will raise the inner |
|---|
| 286 | exception (i.e. the <code>KeyError</code>) instead of |
|---|
| 287 | the <code>RemoteException</code>, potentially confusing subsequent |
|---|
| 288 | error-handling code.</p> |
|---|
| 289 | |
|---|
| 290 | |
|---|
| 291 | <h3>Examples: E Mode</h3> |
|---|
| 292 | |
|---|
| 293 | <p>Systems which use a lot of remote exceptions as part of their |
|---|
| 294 | inter-process API can reduce the size of the remote-error-handling code by |
|---|
| 295 | switching modes, at the expense of risking confusion between local and remote |
|---|
| 296 | occurrences of the same exception type. In the following example, we use "E |
|---|
| 297 | Mode" and look for <code>KeyError</code> to indicate a |
|---|
| 298 | remote <code>get_record</code> miss.</p> |
|---|
| 299 | |
|---|
| 300 | <pre class="python"> |
|---|
| 301 | # Example 5 |
|---|
| 302 | from foolscap.api import Tub |
|---|
| 303 | t = Tub() |
|---|
| 304 | t.setOption("expose-remote-exception-types", True) # E Mode |
|---|
| 305 | ... |
|---|
| 306 | |
|---|
| 307 | def get_and_store_record(name): |
|---|
| 308 | d = local_db.getIDNumber(name) |
|---|
| 309 | |
|---|
| 310 | def get_record(idnum): |
|---|
| 311 | d2 = server1.callRemote("get_record", idnum) # or KeyError |
|---|
| 312 | def maybe_try_server2(f): |
|---|
| 313 | f.trap(KeyError) |
|---|
| 314 | return server2.callRemote("get_record", idnum) # or KeyError |
|---|
| 315 | d2.addErrback(maybe_try_server2) |
|---|
| 316 | return d2 |
|---|
| 317 | d.addCallback(get_record) |
|---|
| 318 | |
|---|
| 319 | d.addCallback(lambda record: local_db.storeRecord(name)) |
|---|
| 320 | |
|---|
| 321 | def ignore_unknown_names(f): |
|---|
| 322 | f.trap(IndexError) |
|---|
| 323 | print "Couldn't get ID for name, ignoring" |
|---|
| 324 | return None |
|---|
| 325 | d.addErrback(ignore_unknown_names) |
|---|
| 326 | |
|---|
| 327 | def failed(f): |
|---|
| 328 | print "didn't get data!" |
|---|
| 329 | if f.check(KeyError): |
|---|
| 330 | # don't bother showing details |
|---|
| 331 | print "both servers claim to not have the record" |
|---|
| 332 | else: |
|---|
| 333 | # show details by printing "f", the Failure instance |
|---|
| 334 | print "other error", f |
|---|
| 335 | d.addErrback(failed) |
|---|
| 336 | |
|---|
| 337 | return d |
|---|
| 338 | </pre> |
|---|
| 339 | |
|---|
| 340 | <p>In this example, <code>KeyError</code> is part of the |
|---|
| 341 | remote <code>get_record</code> method's API: it either returns the data, or |
|---|
| 342 | it raises KeyError, and anything else indicates a bug. The caller explicitly |
|---|
| 343 | catches KeyError and responds by either falling back to the second server |
|---|
| 344 | (the first time) or announcing a servers-have-no-record error (if the |
|---|
| 345 | fallback failed too). But if something else goes wrong, the client indicates |
|---|
| 346 | a different error, along with the exception that triggered it, so that a |
|---|
| 347 | programmer can investigate.</p> |
|---|
| 348 | |
|---|
| 349 | <p>The remote error-handling code is slightly simpler, relative to the |
|---|
| 350 | identical behavior expressed in Example 4, |
|---|
| 351 | since <code>maybe_try_server2</code> only needs to |
|---|
| 352 | use <code>f.trap(KeyError)</code>, instead of needing to unwrap |
|---|
| 353 | a <code>RemoteException</code> first. But when this error-handling code is at |
|---|
| 354 | the end of a larger block (such as the <code>f.trap(IndexError)</code> |
|---|
| 355 | in <code>ignore_unknown_names()</code>, or the <code>f.check(KeyError)</code> |
|---|
| 356 | in <code>failed()</code>), it is vulnerable to confusion: |
|---|
| 357 | if <code>local_db.getIDNumber</code> raised <code>KeyError</code> (instead of |
|---|
| 358 | the expected <code>IndexError</code>), or if the remote server |
|---|
| 359 | raised <code>IndexError</code> (instead of <code>KeyError</code>), then the |
|---|
| 360 | error-handling logic would follow the wrong path.</p> |
|---|
| 361 | |
|---|
| 362 | <h3>Default Mode</h3> |
|---|
| 363 | |
|---|
| 364 | <p>Exception modes were introduced in Foolscap-0.4.0 . Releases before that |
|---|
| 365 | only offered "E mode". The default in 0.4.0 is "E mode" |
|---|
| 366 | (expose-remote-exception-types=True), to retain compatibility with the |
|---|
| 367 | exception-handling code in existing applications. A future release of |
|---|
| 368 | Foolscap may change the default mode to expose-remote-exception-types=False, |
|---|
| 369 | since it seems likely that apps written in this style are less likely to be |
|---|
| 370 | confused by remote exceptions of unexpected types.</p> |
|---|
| 371 | |
|---|
| 372 | <h2>CopiedFailures</h2> |
|---|
| 373 | |
|---|
| 374 | <p>Twisted uses the <code>twisted.python.failure.Failure</code> class to |
|---|
| 375 | encapsulate Python exceptions in an instance which can be passed around, |
|---|
| 376 | tested, and examined in an asynchronous fashion. It does this by copying much |
|---|
| 377 | of the information out of the original exception context (including a stack |
|---|
| 378 | trace and the exception instance itself) into the <code>Failure</code> |
|---|
| 379 | instance. When an exception is raised during a Deferred callback function, it |
|---|
| 380 | is converted into a Failure instance and passed to the next errback handler |
|---|
| 381 | in the chain.</p> |
|---|
| 382 | |
|---|
| 383 | <p>When <code>RemoteReference.callRemote</code> needs to transport |
|---|
| 384 | information about a remote exception over the wire, it uses the same |
|---|
| 385 | convention. However, Failure objects cannot be cleanly serialized and sent |
|---|
| 386 | over the wire, because they contain references to local state which cannot be |
|---|
| 387 | precisely replicated on a different system (stack frames and exception |
|---|
| 388 | classes). So, when an exception happens on the remote side of |
|---|
| 389 | a <code>callRemote</code> invocation, and the exception-handling mode passes |
|---|
| 390 | the remote exception back to the calling code somehow, that code will receive |
|---|
| 391 | a <code>CopiedFailure</code> instance instead.</p> |
|---|
| 392 | |
|---|
| 393 | <p>In "E mode", the <code>callRemote</code>'s errback function will receive |
|---|
| 394 | a <code>CopiedFailure</code> in response to a remote exception, and will |
|---|
| 395 | receive a regular <code>Failure</code> in response to locally-generated |
|---|
| 396 | exceptions. In "Distrust Outsiders" mode, the errback will always receive a |
|---|
| 397 | regular <code>Failure</code>, but |
|---|
| 398 | if <code>f.check(foolscap.api.RemoteException)</code> is True, then |
|---|
| 399 | the <code>CopiedFailure</code> can be obtained |
|---|
| 400 | with <code>f.value.failure</code> and examined further.</p> |
|---|
| 401 | |
|---|
| 402 | <p><code>CopiedFailure</code> is designed to behave very much like a |
|---|
| 403 | regular <code>Failure</code> object. The <code>check</code> |
|---|
| 404 | and <code>trap</code> methods work on <code>CopiedFailure</code>s just like |
|---|
| 405 | they do on <code>Failure</code>s.</p> |
|---|
| 406 | |
|---|
| 407 | <p>However, all of the Failure's attributes must be converted into strings |
|---|
| 408 | for serialization. As a result, the original <code>.value</code> attribute |
|---|
| 409 | (which contains the exception instance, which might contain additional |
|---|
| 410 | information about the problem) is replaced by a stringified representation, |
|---|
| 411 | which tends to lose information. The frames of the original stack trace are |
|---|
| 412 | also replaced with a string, so they can be printed but not examined. The |
|---|
| 413 | exception class is also passed as a string (using |
|---|
| 414 | Twisted's <code>reflect.qual</code> fully-qualified-name utility), |
|---|
| 415 | but <code>check</code> and <code>trap</code> both compare by string name |
|---|
| 416 | instead of object equality, so most applications won't notice the |
|---|
| 417 | difference.</p> |
|---|
| 418 | |
|---|
| 419 | <p>The default behavior of CopiedFailure is to include a string copy of the |
|---|
| 420 | stack trace, generated with <code>printTraceback()</code>, which will include |
|---|
| 421 | lines of source code when available. To reduce the amount of information sent |
|---|
| 422 | over the wire, stack trace strings larger than about 2000 bytes are truncated |
|---|
| 423 | in a fashion that tries to preserve the top and bottom of the stack.</p> |
|---|
| 424 | |
|---|
| 425 | <h3>unsafeTracebacks</h3> |
|---|
| 426 | |
|---|
| 427 | <p>Applications which consider their lines of source code or their |
|---|
| 428 | exceptions' list of (filename, line number) tuples to be sensitive |
|---|
| 429 | information can set the "unsafeTracebacks" flag in their Tub to False; the |
|---|
| 430 | server will then remove stack information from the CopiedFailure objects it |
|---|
| 431 | sends to other systems.</p> |
|---|
| 432 | |
|---|
| 433 | <pre class="python"> |
|---|
| 434 | t = Tub() |
|---|
| 435 | t.unsafeTracebacks = False |
|---|
| 436 | </pre> |
|---|
| 437 | |
|---|
| 438 | <p>When unsafeTracebacks is False, the <code>CopiedFailure</code> will only |
|---|
| 439 | contain the stringified exception type, value, and parent class names.</p> |
|---|
| 440 | |
|---|
| 441 | </body> |
|---|
| 442 | </html> |
|---|