I'm trying to use managed code from the MDbg sample to read source filenames and line numbers from PDB files. The problem is that something is opening the PDB file, establishing a file lock and then not closing the file again. Consequently I cannot overwrite the PDB file from another process (e.g. during a rebuild operation in Visual Studio) even after my PDB examination has completed and all of the managed variables have gone out of scope (and, for curiosity's sake, I even ran GC.Collect() to see if a finalizer would release the file lock but it didn't seem to).

The thing worrying me is that the documentation for IMetaDataDispenser::OpenScope() seems to imply that it opens the PDB file and locks it (e.g. http://msdn2.microsoft.com/en-us/library/ms231248.aspx) BUT I CAN SEE NOTHING IN MDBG THAT OBVIOUSLY CLOSES/UNLOCKS THIS FILE. This would be fine if the IMetaDataDispenser interface also featured an obviously-named method such as CloseScope() to release the file.

I can't seem to find any Close() or Dispose() or other such methods on any of the interfaces used (including IMetadataImport).

The code I am using from the MDbg sample is the same as that used by Mike Stall in his example at http://blogs.msdn.com/jmstall/archive/2005/08/25/pdb2xml.aspx. It seems that as his application closes as soon as it has finished its work, any open file handles are cleaned up by Windows anyway so the problem is not manifest.

In short, I suspect that line 87 of SymAccess.cs in the MDbg sample (the method GetReaderForFile(SymbolBinder binder, string pathModule, string searchPath) starts a method which results in the PDB file being locked, but nowhere in the MDbg sample is this lock ever released. This lock might be established at line 97 (the dispenser.OpenScope call) or at line 106 (the binder.GetReader call).

Has anyone else experienced this behaviour or is it just me Can anyone put my mind at rest as to exactly when the PDB file is locked and unlocked when the GetReaderForFile() method is called in the SymAccess class

Many thanks,

Re: Building Development and Diagnostic Tools for .Net MDbg sample code does not close PDB files or release file handle/lock?

Mike Stall - MSFT

We've definitely experience this, and it's a very nasty problem.

If in Mdbg.exe, you launch a process, and then the process exits (or you kill it), you should be able to delete the PDB of the former debuggee without exiting Mdbg.exe. However, this is only because of a hack in the shell that's not present in other apps (like pdb2xml) to handle the problem.

The problem in more detail:

  1. The unmanaged PDB interfaces can have file locks.
  2. The locks may not be released until the ref count goes to zero. There is no explicit Dispose() method on the interfaces.
  3. To make things worse, there's a transitive closure problem here because any object handed out from the interface may keep a ref to the object holding the file lock. So you have to fully release the all objects of the interface.
  4. At least Unmanaged code can cope with this because it fully controls the calls to Release(). However, if there's a leaking reference, then the file lock doesn't get released.
  5. When you use COM-interop to import an unmanaged interface into managed code, the releases become non-deterministic. The call to GC.Collect() is not enough to force this. The .NET libraries cope with this problem via IDisposable. (I would consider the lack of a Dispose() method a design shortsight in both the symbol interfaces and the metadata interfaces).
  6. MDbg actually has a very ugly hack to deal with. Search Mdbg for GC.Collect(), and you'll see a hacky section where it calls that function a few times, followed by GC.WaitForPendingFinalizers().

(This was a significant motivator when I wrote don't do complicated work in Release).

I'd say the root problem is a design flaw in the Symbol + Metadata interfaces (lack of deterministic explicit dispose method). But since these were unmanaged APIs originally designed for unmanaged usage, I can see how this happened.

The GC.WaitForPendingFinalizers() approach is a nasty hack for dealing with it. A cleaner (but more work) approach would be explicitly tracking all refences in managed code and using Marshal.Release() to forcibly clean things up.

Re: Building Development and Diagnostic Tools for .Net MDbg sample code does not close PDB files or release file handle/lock?


Many thanks. Feared it was just me at first. I thought MDbg was dealing with it somehow but couldn't figure out how. I'll investigate tracking the references as at the moment the PDB aspect of my project uses only a small number of managed classes so should be entirely self-contained. The fallback will be to utilise MDbg's approach (especially if your point 3 crops up once too often for me to track).

Managed code is so much easier .

Re: Building Development and Diagnostic Tools for .Net MDbg sample code does not close PDB files or release file handle/lock?

Rick Byers - MSFT

It turns out that there is actually a decent solution to this problem that we just missed. Although it's not explicit in the API, you can QueryInterface for ISymUnmanagedDispose on an ISymUnmanagedReader instance. Calling "Destroy" on the resulting object will release all the file locks. I've changed our MDbg code do to this (removing the ugly hack Mike mentions) and it's working well for us. The next Mdbg sample release will have this fix.

Sorry we didn't notice this sooner - I still agree with Mike that this is an API design flaw. ISymUnmanagedReader should explicitly have a Destroy method or extend ISymUnmanagedDispose to make it clear that you can QI for it. It was just luck that on my 100th time looking at corsym.idl I happened to stumble across the declaration for ISymUnmanagedDispose.