<div dir="ltr">To add more evidence for this, here's a small repro:<div><br></div><div><div>import sys</div><div><br></div><div>print "sys.exc_info() = ", "Empty" if sys.exc_info() == (None, None, None) else "Valid"</div><div>try:</div><div> raise Exception</div><div>except Exception, e:</div><div> print "sys.exc_info() = ", "Empty" if sys.exc_info() == (None, None, None) else "Valid"</div><div> pass</div><div><br></div><div>print "sys.exc_info() = ", "Empty" if sys.exc_info() == (None, None, None) else "Valid"</div><div>print "e = ", "Bound" if 'e' in vars() else "Unbound"</div><div>pass</div></div><div><br></div><div>For me this prints</div><div><div>sys.exc_info() = Empty</div><div>sys.exc_info() = Valid</div><div>sys.exc_info() = Valid</div><div>e = Bound</div></div></div><br><div class="gmail_quote"><div dir="ltr">On Thu, Oct 15, 2015 at 11:21 AM Zachary Turner <<a href="mailto:zturner@google.com">zturner@google.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr">We actually do already to the self.dbg.DeleteTarget(target), and that's the line that's failing. The reason it's failing is because the 'sc' reference is still alive, which is holding an mmap, which causes a mandatory file lock on Windows.<div><br></div><div>The diagnostics went pretty deep into python internals, but I think we might have figured it out. I don't know if this is a bug in Python, but I think we'd probably need to ask Guido to be sure :)</div><div><br></div><div>As far as we can tell, what happens is that on the exceptional codepath (e.g the assert fails), you walk back up the stack until you get to the except handler. This exception handler is in TestCase.run(). After it handles the exception it goes and runs teardown. However, for some reason, Python is still holding a strong reference to the *traceback*, even though we're completely out of the finally block. What this means is that if you call `sys.exc_info()` *even after you've exited the finally block, it still returns info about the previous exception that's not even being handled anymore. I would have expected this to be gone since there's no exception in-fligth anymore. So basically, Python is still holding a reference to the active exception, the exception holds the stack frame, the stack frame holds the test method, the test method has locals, one of which is a SymbolList, a member of which is symbol context, which has the file locked.</div><div><br></div><div>Our best guess is that if you have something like this:</div><div><br></div><div>def foo():</div><div> try:</div><div> # Do stuff</div><div> except Exception, e:</div><div> pass</div><div><span style="line-height:1.5"> # Do more stuff</span><br></div><div><br></div><div>that if the exceptional path is executed, then both e and sys.exc_info() are alive *while* do more stuff is happening. We've found two ways to fixthis:</div><div><br></div><div>1) Change to this:</div><div><div>def foo():</div><div> try:</div><div> # Do stuff</div><div> except Exception, e:</div><div> pass</div><div> del e</div><div> sys.exc_clear()</div><div><span style="line-height:1.5"> # Do more stuff</span><br></div></div><div><br></div><div><span style="line-height:1.5">2) Put the try / except inside a function. When the function returns, sys.exc_info() is cleared. </span></div><div><span style="line-height:1.5"><br></span></div><div><span style="line-height:1.5">I like 2 better, but we're still testing some more to make sure this really fixes it 100% of the time.</span></div></div><br><div class="gmail_quote"><div dir="ltr">On Thu, Oct 15, 2015 at 10:25 AM Greg Clayton via lldb-dev <<a href="mailto:lldb-dev@lists.llvm.org" target="_blank">lldb-dev@lists.llvm.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><br>
> On Oct 15, 2015, at 8:50 AM, Adrian McCarthy via lldb-dev <<a href="mailto:lldb-dev@lists.llvm.org" target="_blank">lldb-dev@lists.llvm.org</a>> wrote:<br>
><br>
> I've tracked down a source of flakiness in tests on Windows to Python object lifetimes and the SB interface, and I'm wondering how best to handle it.<br>
><br>
> Consider this portion of a test from TestTargetAPI:<br>
><br>
> def find_functions(self, exe_name):<br>
> """Exercise SBTaget.FindFunctions() API."""<br>
> exe = os.path.join(os.getcwd(), exe_name)<br>
><br>
> # Create a target by the debugger.<br>
> target = self.dbg.CreateTarget(exe)<br>
> self.assertTrue(target, VALID_TARGET)<br>
> list = target.FindFunctions('c', lldb.eFunctionNameTypeAuto)<br>
> self.assertTrue(list.GetSize() == 1)<br>
><br>
> for sc in list:<br>
> self.assertTrue(sc.GetModule().GetFileSpec().GetFilename() == exe_name)<br>
> self.assertTrue(sc.GetSymbol().GetName() == 'c')<br>
><br>
> The local variables go out of scope when the function exits, but the SB (C++) objects they represent aren't (always) immediately destroyed. At least some of these objects keep references to the executable module in the shared module list, so when the test framework cleans up and calls `SBDebugger::DeleteTarget`, the module isn't orphaned, so LLDB maintains an open handle to the executable.<br>
<br>
Creating a target with:<br>
<br>
target = self.dbg.CreateTarget(exe)<br>
<br>
Will give you a SBTarget object that has a strong reference to the target, but the debugger still has a copy in its target list, so the SBTarget isn't designed to delete the object when the target variable goes out of scope. If you want the target to be deleted, you actually have to call through to the debugger with:<br>
<br>
<br>
bool<br>
SBDebugger:DeleteTarget (lldb::SBTarget &target);<br>
<br>
<br>
So the right way to clean up the target is:<br>
<br>
self.dbg.DeleteTarget(target);<br>
<br>
Even though there might be code within LLDB that has a valid shared pointer to the lldb_private::Target still, it calls lldb_private::Target::Destroy() which clears out most instance variable (the module list, the process, any plug-ins, etc).<br>
<br>
SBTarget objects have strong references so that they _can_ keep the object alive if needed in case someone else destroys the target on another thread, but they don't control the lifetime of the target.<br>
<br>
Other objects have weak references to the objects: SBProcess, SBThread, SBFrame. If the objects are actually destroyed already, the weak pointer won't be able to get a valid shared pointer to the underlying object<br>
and any SB API calls on these objects will return error, none, zero, etc...<br>
<br>
><br>
> The result of the lingering handle is that, when the next test case in the test suite tries to re-build the executable, it fails because the file is not writable. (This is problematic on Windows because the file system works differently in this regard than Unix derivatives.) Every subsequent case in the test suite fails.<br>
><br>
> I managed to make the test work reliably by rewriting it like this:<br>
><br>
> def find_functions(self, exe_name):<br>
> """Exercise SBTaget.FindFunctions() API."""<br>
> exe = os.path.join(os.getcwd(), exe_name)<br>
><br>
> # Create a target by the debugger.<br>
> target = self.dbg.CreateTarget(exe)<br>
> self.assertTrue(target, VALID_TARGET)<br>
><br>
> try:<br>
> list = target.FindFunctions('c', lldb.eFunctionNameTypeAuto)<br>
> self.assertTrue(list.GetSize() == 1)<br>
><br>
> for sc in list:<br>
> try:<br>
> self.assertTrue(sc.GetModule().GetFileSpec().GetFilename() == exe_name)<br>
> self.assertTrue(sc.GetSymbol().GetName() == 'c')<br>
> finally:<br>
> del sc<br>
><br>
> finally:<br>
> del list<br>
><br>
> The finally blocks ensure that the corresponding C++ objects are destroyed, even if the function exits as a result of a Python exception (e.g., if one of the assertion expressions is false and the code throws an exception). Since the objects are destroyed, the reference counts are back to where they should be, and the orphaned module is closed when the target is deleted.<br>
><br>
> But this is ugly and maintaining it would be error prone. Is there a better way to address this?<br>
<br>
So you should be able to fix this by deleting the target with "self.dbg.DeleteTarget(target)"<br>
<br>
We could change all tests over to always store any targets they create in the test object itself:<br>
<br>
self.target = self.dbg.CreateTarget(exe)<br>
<br>
Then the test suite could check for the existance of "self.target" and if it exists, it could call "self.dbg.DeleteTarget(self.target)" automatically to avoid such issues?<br>
<br>
<br>
<br>
_______________________________________________<br>
lldb-dev mailing list<br>
<a href="mailto:lldb-dev@lists.llvm.org" target="_blank">lldb-dev@lists.llvm.org</a><br>
<a href="http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-dev" rel="noreferrer" target="_blank">http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-dev</a><br>
</blockquote></div></blockquote></div>