Finding a resource leak, is it the background thread or somethingelse?

C

Cartoper

My application appears to have a recourse leak. When the user starts
a background process, the handle count in Process Explorer (PE) goes
up by about 10, sometime 1 or 2 more, sometimes 1 or 2 less. When the
task is completed, there are somewhere between 4 and 7 handles still
open.

My process is loading images and shrinking them down. I went through
all the code and found some objects that implement the IDisposable
interface which I was not calling Dispose or Close on. Objects such
as AutoResetEvent, Graphic, and Image. I have either encapsulated
them in a using clause or called Dispose on the object before setting
to null and it seems to have no effect.

So I am now wondering where might I find this leak? In the unmanaged
thread handles need to be closed when no longer needed. I am not
seeing any way of cleaning up a managed thread, once it is completed.
Is there some cleanup procedure I am missing or are there other
objects out there that I am not cleaning up correctly?

Are there any good tools out there that might help me in finding this
resource leak?

Cartoper
 
P

Peter Ritchie [C# MVP]

There are classes in the framework that use system handles that don't also
implement IDisposable. So, using one of those classes will increase your
handle count by one and won't decrease until the next garbage collection.

Thread is an example of this. If you create a thread and run some code it
it, the handle won't automatically be freed.

If you want to be sure you're getting a "leak", try calling GC.Collect()
(temporarily). If the handle count goes back down to the original count the
handle increase is due to classes like Thread, which means they're safe go
ignore.
 
W

Willy Denoyette [MVP]

Cartoper said:
My application appears to have a recourse leak. When the user starts
a background process, the handle count in Process Explorer (PE) goes
up by about 10, sometime 1 or 2 more, sometimes 1 or 2 less. When the
task is completed, there are somewhere between 4 and 7 handles still
open.

My process is loading images and shrinking them down. I went through
all the code and found some objects that implement the IDisposable
interface which I was not calling Dispose or Close on. Objects such
as AutoResetEvent, Graphic, and Image. I have either encapsulated
them in a using clause or called Dispose on the object before setting
to null and it seems to have no effect.

So I am now wondering where might I find this leak? In the unmanaged
thread handles need to be closed when no longer needed. I am not
seeing any way of cleaning up a managed thread, once it is completed.
Is there some cleanup procedure I am missing or are there other
objects out there that I am not cleaning up correctly?

Are there any good tools out there that might help me in finding this
resource leak?

Cartoper



Using Process Explorer you can get a list of the OS handles owned by the
process, so it's not that hard to see whether the "leaked" handles are the
thread handles. My guess is that they are no thread handles, but event
handles.

Willy.
 
C

Cartoper

Using Process Explorer you can get a list of the OS handles owned by the
process, so it's not that hard to see whether the "leaked" handles are the
thread handles. My guess is that they are no thread handles, but event
handles.

Willy,

I am new to Process Explorer, I only started using it last night on
this bug. When you say OS handles, is that the:

OS handles = Handles - ( GDI Handles + USER Handles)

And when you say event, are you refering to the AutoResetEvent type of
event or are you refering to the event/delegate type? I am cleaning
up all one of the AutoResetEvent by calling Close() in the Dispose of
the class that manages the thread. If you are refering to the
delegate type of event, what exactly should I being doing to clean it
up?

Cartoper
 
C

Cartoper

Using Process Explorer you can get a list of the OS handles owned by the
process, so it's not that hard to see whether the "leaked" handles are the
thread handles. My guess is that they are no thread handles, but event
handles.

Willy,

I am developing on VS2005 SP1, if that matters. I have done some more
testing and monitoring of handles a bit closer. Here is the code that
starts the thread:

ThreadStart threadDelegate = new ThreadStart(ProcessFileList);
this.webUpdateThread = new Thread(threadDelegate);
this.webUpdateThread.Start();

The call to start increases the handle count by 4 and it never goes
down. This all happens in a class called WebUpdate which is the
container for the thread. The class does contain one AutoResetEvent
that is closed in WebUpdate.Dispose() method and two (2) events
handlers. When the thread is finished one of the event handlers is
called to tell the main thread to clean up the WebUpdate class. After
the clean up, which is both calling WebUpdate.Dispose() and setting it
to null, GC.Collect() is called and the handle count is still 4 higher
then when it all began.

I have checked the handle count right before the end of the thread and
it is 5 higher then when everything started, after WebUpdate.Dispose()
is called it is back to 4 higher.

What am I missing?

Cartoper
 
W

Willy Denoyette [MVP]

Using Process Explorer you can get a list of the OS handles owned by the
process, so it's not that hard to see whether the "leaked" handles are the
thread handles. My guess is that they are no thread handles, but event
handles.

Willy,

I am new to Process Explorer, I only started using it last night on
this bug. When you say OS handles, is that the:

OS handles = Handles - ( GDI Handles + USER Handles)

And when you say event, are you refering to the AutoResetEvent type of
event or are you refering to the event/delegate type? I am cleaning
up all one of the AutoResetEvent by calling Close() in the Dispose of
the class that manages the thread. If you are refering to the
delegate type of event, what exactly should I being doing to clean it
up?

Cartoper



No, Handles are OS Handles, GDI and USER have separate counters in PE.
What I'm talking about is the Process Handles List in the lower pane, to get
this list you need to select a process and click the Handles button in the
toolbox.
All Event types in .NET are wrappers around OS object handles, object
handles are released explicitly by a Dispose call or implicitly by the
Finalizer.

Willy.
 
W

Willy Denoyette [MVP]

Using Process Explorer you can get a list of the OS handles owned by the
process, so it's not that hard to see whether the "leaked" handles are the
thread handles. My guess is that they are no thread handles, but event
handles.

Willy,

I am developing on VS2005 SP1, if that matters. I have done some more
testing and monitoring of handles a bit closer. Here is the code that
starts the thread:

ThreadStart threadDelegate = new ThreadStart(ProcessFileList);
this.webUpdateThread = new Thread(threadDelegate);
this.webUpdateThread.Start();

The call to start increases the handle count by 4 and it never goes
down. This all happens in a class called WebUpdate which is the
container for the thread. The class does contain one AutoResetEvent
that is closed in WebUpdate.Dispose() method and two (2) events
handlers. When the thread is finished one of the event handlers is
called to tell the main thread to clean up the WebUpdate class. After
the clean up, which is both calling WebUpdate.Dispose() and setting it
to null, GC.Collect() is called and the handle count is still 4 higher
then when it all began.

I have checked the handle count right before the end of the thread and
it is 5 higher then when everything started, after WebUpdate.Dispose()
is called it is back to 4 higher.

What am I missing?

Cartoper

This doesn't mean you are leaking handles, an handle leak would mean that
the handle count keeps increasing without ever going down during the
lifetime of the process, this is not what's happening here I guess, please
correct me if I'm wrong. Also don't forget that the CLR creates it's own OS
objects, the CLR is responsible to release these handles, also don't call
GC.Collect without having a serious reason to do so.

Willy.
 
C

Cartoper

This doesn't mean you are leaking handles, an handle leak would mean that
the handle count keeps increasing without ever going down during the
lifetime of the process, this is not what's happening here I guess, please
correct me if I'm wrong.

Please forgive me, but I thought I cleared stated that BEFORE starting
the thread the handle count was say 309, AFTER the thread starts the
count is above 313, once it stops and time goes by, the handle count
stays at 313. The handle count NEVER EVER returns to 309 once the
thread is started, absolutely NEVER returns. The next time the the
thread is started the count rises above 317 and settles at 317 once
the thread has exited, again NEVER EVER to return to 313 or 309. I am
pretty darn sure this would be considered a handle leak. If it isn't
I most definitly need to find another way to makine a living (like
writing code in good old unmanaged C++).
Also don't forget that the CLR creates it's own OS
objects, the CLR is responsible to release these handles, also don't call
GC.Collect without having a serious reason to do so.

The GC.Collect was for DEBUGGING purposes ONLY. It is quite clearn
that the OS is creating these handles, the only question is, am I not
doing something that will make then get cleaned up?

Cartoper
 
W

Willy Denoyette [MVP]

This doesn't mean you are leaking handles, an handle leak would mean that
the handle count keeps increasing without ever going down during the
lifetime of the process, this is not what's happening here I guess, please
correct me if I'm wrong.

Please forgive me, but I thought I cleared stated that BEFORE starting
the thread the handle count was say 309, AFTER the thread starts the
count is above 313, once it stops and time goes by, the handle count
stays at 313. The handle count NEVER EVER returns to 309 once the
thread is started, absolutely NEVER returns. The next time the the
thread is started the count rises above 317 and settles at 317 once
the thread has exited, again NEVER EVER to return to 313 or 309. I am
pretty darn sure this would be considered a handle leak. If it isn't
I most definitly need to find another way to makine a living (like
writing code in good old unmanaged C++).
Also don't forget that the CLR creates it's own OS
objects, the CLR is responsible to release these handles, also don't call
GC.Collect without having a serious reason to do so.

The GC.Collect was for DEBUGGING purposes ONLY. It is quite clearn
that the OS is creating these handles, the only question is, am I not
doing something that will make then get cleaned up?

Cartoper



There is no reason to shout. That said, it's impossible for us to tell what
causes the "handle leak", without seeing more code.
Also we need to know the handle type of these 4 "leaked" handles, this what
PE can be used for, but much better is use a native debugger to chase down
handle leaks.
Questions I would like to see answered by you:
- what kind of application is this, interactive, service console etc...
- what framework classes are you using , what methods are there called on
these classes, some class methods can create threads and Handles too you
know!
- what version of the framework you are running on.
And if possible post a minimal but complete sample that illustrates the
issue.

Willy.
 
C

Cartoper

It's impossible for us to tell what causes the "handle leak", without seeing more code.
Also we need to know the handle type of these 4 "leaked" handles,

If I knew what handles where leaking, I could fix it! I am trying to
figure out how to find the handles.
this what PE can be used for, but much better is use a native debugger to chase down
handle leaks.

Ok, I am quite at home in C/C++, where might I go for some tips/tricks
on tracking down handle leaks in native code?
Questions I would like to see answered by you:
- what kind of application is this, interactive, service console etc...

The project type in VS2005 is "Windows Application" and "Class
LIbrary"
- what framework classes are you using ,

what methods are there called on these classes, some class methods can
create threads and Handles too you know!

Like I said before, the code in question is down sizing image files.
It loops through a SortedDictionary of images. It is opening them
with a FileStream, loading them into an Image Object, creating a
Graphics object to downsize them, along with assorted other GDI+
calls.

As I have already stated, I spend quite a bit of time yesterday check
and debuging all that code. The Handles count before entering the
loop that downsized images is 100% identical to the number of Handles
once it is finished looping through all the images that need to be
downsized.
- what version of the framework you are running on.

C#.Net v2.0.50727
And if possible post a minimal but complete sample that illustrates the
issue.

I wish.
 
C

Cartoper

I am going NUTS here! I just changed the code so the thread does not
actually die, simply goes into a WaitHandle.WaitAny until there are
more images to downsize and the handles STILL are going up and not
coming down!!!!!!!!!!!!!!!!

I have looked in MSDN and don't see anything about having to do any
clean up on WaitHandle. Am I missing something? Come on, this has to
be something brain dead simple, I know how to code and debug, from
everything I can tell, I am crossing all the T's and dotting all the
i's in my clean up code.

Willy, in your very first post of this thread, you said: "My guess is
that they are no thread handles, but event handles." It HAS to be
event handles, the problem is I cannot find any information on
cleaning up event handles in managed code. Can you elaborate on the
concept of leaking event handles?

Cartoper

Here is my complete WebUpdate class:

/// <summary>
/// Updates the web site images
/// </summary>
public class WebUpdate : IDisposable
{
private static readonly log4net.ILog log =
log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

private readonly string rootDir;
private readonly string webRoot;

private IDictionary<string, IFileProcessor> filesToProcess = new
SortedDictionary<string, IFileProcessor> ();
private Thread webUpdateThread = null;
private ReaderWriterLock filesToProcessRWL = new
ReaderWriterLock();
private AutoResetEvent goEvent = new AutoResetEvent(false);
private AutoResetEvent stopEvent = new AutoResetEvent(false);
private AutoResetEvent killEvent = new AutoResetEvent(false);
private volatile bool isUpdating = false;

private ImageFolder thumbnailFld = null;
private ImageFolder imageFld = null;

public event ProcessStepHandler FilesUpdateProgress;
public event ProcessCompleteHandler FileUpdateComplete;

internal WebUpdate(string rootDir, string webRoot)
{
this.rootDir = rootDir;
this.webRoot = webRoot;
}

public void Dispose()
{
if (this.ThreadingStopped == false)
{
this.killEvent.Set();
this.webUpdateThread.Join();
}
goEvent.Close();
stopEvent.Close();
killEvent.Close();
}

/// <summary>
/// This is true when the thread is actively updating the web site
/// </summary>
public bool IsUpdating
{
get { return isUpdating; }
}

/// <summary>
///
/// </summary>
/// <param name="filesToProcess"></param>
internal void SetFilesToProcess(IDictionary<string,
IFileProcessor> filesToProcess)
{
this.filesToProcessRWL.AcquireWriterLock(-1);
try
{
foreach(KeyValuePair<string, IFileProcessor> kvp in
filesToProcess)
{
this.filesToProcess[kvp.Key] = kvp.Value;
}
}
finally
{
this.filesToProcessRWL.ReleaseWriterLock();
}
}

/// <summary>
/// The total number of images to process
/// </summary>
public int TotalCount
{
get {
this.filesToProcessRWL.AcquireReaderLock(-1);
try
{
return this.filesToProcess.Count;
}
finally
{
this.filesToProcessRWL.ReleaseReaderLock();
}
}
}

/// <summary>
/// Deletes a folder from the web server system
/// </summary>
static public void DeleteWebFolder(string webRoot, string
relativePath)
{
string fullpath = webRoot + relativePath;
DirectoryInfo di = new DirectoryInfo(fullpath);

if (di.Exists)
{
di.Delete(true);
}
}

/// <summary>
/// Starts the background thread that will update the images.
/// </summary>
/// <param name="thumbnail">This size of the thumbnail image</
param>
/// <param name="image">The size of the large preview image</
param>
public void Start(int thumbnail, int image)
{
if (this.IsUpdating == false)
{
log.InfoFormat("Start({0}, {1})", thumbnail, image);

this.thumbnailFld = new ImageFolder("tn", thumbnail);
this.imageFld = new ImageFolder("imgs", image);

if (this.webUpdateThread == null)
{
// Debug.WriteLine(String.Format("1 handle count
before new ThreadStart(): {0}", General.HandleNThreadCount));
this.webUpdateThread = new Thread(new
ThreadStart(ProcessFileList));
this.webUpdateThread.Start();
// Debug.WriteLine(String.Format("2 handle count after
this.webUpdateThread.Start(): {0}", General.HandleNThreadCount));
}
else
{
Debug.Assert(ThreadingStopped == false);
this.stopEvent.Reset();
this.goEvent.Set();
}
}
}

/// <summary>
///
/// </summary>
public void Stop()
{
Debug.Assert(ThreadingStopped == false);
if (this.IsUpdating)
{
this.stopEvent.Set();
}
}

/// <summary>
/// true if the background thread is *NOT* running
/// </summary>
private bool ThreadingStopped
{
get
{
return this.webUpdateThread == null ||
(this.webUpdateThread.ThreadState &
System.Threading.ThreadState.Stopped) ==
System.Threading.ThreadState.Stopped;
}
}

/// <summary>
/// The background thread
/// </summary>
private void ProcessFileList()
{
while (true)
{
isUpdating = true;
List<IFileProcessDataError> errorList = new
List<IFileProcessDataError>();
long totalTicks = 0;
int cnt = 0;

log.Info("ProcessFileList() started");
while(this.filesToProcess.Count > 0)
{
string filepath = string.Empty;
IFileProcessor fileProcessor = null;

this.filesToProcessRWL.AcquireWriterLock(-1);
try
{
IEnumerator<KeyValuePair<string, IFileProcessor>>
enumerator = this.filesToProcess.GetEnumerator();

enumerator.Reset();
enumerator.MoveNext();

KeyValuePair<string, IFileProcessor> kvp =
enumerator.Current;

this.filesToProcess.Remove(kvp);

filepath = kvp.Key;
fileProcessor = kvp.Value;
}
finally
{
this.filesToProcessRWL.ReleaseWriterLock();
}

DateTime start = DateTime.Now;

FileInfo fi = new FileInfo(filepath);

string srcFolder = fi.FullName.Substring(0,
fi.FullName.Length - fi.Name.Length - 1);
string dstFolder = this.webRoot +
fi.Directory.FullName.Substring(this.rootDir.Length);

// log.DebugFormat("{0} --> file: [{1}] src := [{2}]
dest := [{3}]", General.GetClassNameWONS(fileProcessor), fi.Name,
srcFolder, dstFolder);

FileProcessData fpd = new FileProcessData(srcFolder,
dstFolder, fi.Name);
try
{
fileProcessor.Process(fpd, this.ImageFolderList);
}
catch (Exception ex)
{
log.FatalFormat("Process Image Exception: {0} -->
{1}\n srcFolder => {2}\n dstFolder => {3} \n filename => {4}",
General.GetClassNameWONS(ex), ex.Message, srcFolder, dstFolder,
fpd.Filename);
errorList.Add(new FileProcessDataError(fpd,
fileProcessor, ex));
}

TimeSpan processTime = DateTime.Now - start;

totalTicks += processTime.Ticks;

long averageTicks = totalTicks / ++cnt;

TimeSpan averageTime = new TimeSpan(averageTicks);

FireFilesUpdateProgress(this.TotalCount, averageTime);
if (this.stopEvent.WaitOne(1, false))
{
break;
}
}

isUpdating = false;

if (FileUpdateComplete != null)
{
FileUpdateComplete(this, new
CrunchImageErrorEventArgs(cnt, errorList));
}

WaitHandle[] waitHandles = new WaitHandle[2];

waitHandles[0] = this.killEvent;
waitHandles[1] = this.goEvent;

if (WaitHandle.WaitAny(waitHandles) == 0)
return;
}
//log.Info("ProcessFileList() ended.");
}

/// <summary>
/// The collection of folders in which to copy the images to
/// </summary>
/// <remarks>
/// It is very important that the order of these folders go from
largest image size to smallest.
/// </remarks>
private IEnumerable<ImageFolder> ImageFolderList
{
get
{
yield return this.imageFld;
yield return this.thumbnailFld;
}
}

/// <summary>
///
/// </summary>
/// <param name="count"></param>
private void FireFilesUpdateProgress(int count, TimeSpan
processTime)
{
if (FilesUpdateProgress != null)
{
FilesUpdateProgress(this, new CruchImageEventArgs(count,
processTime));
}
}
}
 
W

Willy Denoyette [MVP]

It's impossible for us to tell what causes the "handle leak", without
seeing more code.
Also we need to know the handle type of these 4 "leaked" handles,

If I knew what handles where leaking, I could fix it! I am trying to
figure out how to find the handles.


this what PE can be used for, but much better is use a native debugger to
chase down
handle leaks.

Ok, I am quite at home in C/C++, where might I go for some tips/tricks
on tracking down handle leaks in native code?

PE can list all handles created by your process, just select View Handles in
the toolbar and select your Process, the lower pane will list all OS handles
currently owned.
The native debugging tools (Windbg, nsdb etc.. are the right tools to go
hunting for handle leaks, if you run your code under widbg,
Questions I would like to see answered by you:
- what kind of application is this, interactive, service console etc...

The project type in VS2005 is "Windows Application" and "Class
LIbrary"
- what framework classes are you using ,

what methods are there called on these classes, some class methods can
create threads and Handles too you know!

Like I said before, the code in question is down sizing image files.
It loops through a SortedDictionary of images. It is opening them
with a FileStream, loading them into an Image Object, creating a
Graphics object to downsize them, along with assorted other GDI+
calls.

As I have already stated, I spend quite a bit of time yesterday check
and debuging all that code. The Handles count before entering the
loop that downsized images is 100% identical to the number of Handles
once it is finished looping through all the images that need to be
downsized.
- what version of the framework you are running on.

C#.Net v2.0.50727
And if possible post a minimal but complete sample that illustrates the
issue.

I wish.



You keep assuming these handles are leaked, well I'm not.
Keep in mind that Handles are native objects, some may only get released
after the GC/Finalizer has run. So, while it's possible to see this count
going up with every thread cycle (start/stop) , it doesn't mean that at some
point in time, these handles won't get collected. And, as I said before, the
CLR also creates "event" and other kind of OS objects, the CLR is the owner
and only the CLR is controlling the life cycle of these objects.
And that's what you are probably having issues with.
For each managed Thread object you create, the CLR creates 5 objects:
- 1 "OS thread" object and
- 4 "Manual Reset event" objects.
These objects are not necessarily deleted when the GC/Finalizer runs, they
are not under control of the GC, they are managed by the CLR, the CLR will
delete them when he sees fit.

Just create a small program which starts a "do nothing thread" and watch the
handle count going up for each thread you start, until the CLR decides it's
enough and starts releasing it's internally owned handles (CLR/Framework
version dependent). At that point you will see the Handle count going down.


Also, keep in mind that PE is only a tool that takes snapshots of a running
external process, that means that the handle count does not necessarily
represent the actual count. In order to watch the real count, you have to
freeze the process by running your code under a debugger. This means that PE
is not a debugging tool.
In order to watch handles life time, you need to start your program under a
native debugger like nsdb or windbg (See Debugging Tools For Windows).
Issuing the !handle debugger command will show you the list of handles owned
by the process.

Willy.
 
C

Cartoper

PE can list all handles created by your process, just select View Handles in
the toolbar and select your Process, the lower pane will list all OS handles
currently owned.

I need to make sure we are both talking about the same program. When
I say PE, I am refering to Process Explorer v11.11 from Sysinternals.
I am only seeing two different buttons on the toobar relating to
handles:

"Kill Process/Close Handle"
"Find Handle or DLL"

I am not seeing anything about viewing handles, nor do I see anything
like that in the menus.
For each managed Thread object you create, the CLR creates 5 objects:
- 1 "OS thread" object and
- 4 "Manual Reset event" objects.
These objects are not necessarily deleted when the GC/Finalizer runs, they
are not under control of the GC, they are managed by the CLR, the CLR will
delete them when he sees fit.

Thank you, I did not realize the CLR created 4 manual reset events
with every new thread, which explains why I kept seeing the count go
up by 4 initally when I was always starting the thread anew.
Just create a small program which starts a "do nothing thread" and watch the
handle count going up for each thread you start, until the CLR decides it's
enough and starts releasing it's internally owned handles (CLR/Framework
version dependent). At that point you will see the Handle count going down..

Also, keep in mind that PE is only a tool that takes snapshots of a running
external process, that means that the handle count does not necessarily
represent the actual count. In order to watch the real count, you have to
freeze the process by running your code under a debugger. This means that PE
is not a debugging tool.
In order to watch handles life time, you need to start your program under a
native debugger like nsdb or windbg (See Debugging Tools For Windows).
Issuing the !handle debugger command will show you the list of handles owned
by the process.

I will look into them, thanks!
 
W

Willy Denoyette [MVP]

See inline ****

Willy.

PE can list all handles created by your process, just select View Handles
in
the toolbar and select your Process, the lower pane will list all OS
handles
currently owned.

I need to make sure we are both talking about the same program. When
I say PE, I am refering to Process Explorer v11.11 from Sysinternals.
I am only seeing two different buttons on the toobar relating to
handles:

"Kill Process/Close Handle"
"Find Handle or DLL"

I am not seeing anything about viewing handles, nor do I see anything
like that in the menus.

***
Not in the menus, but on the tool bar, the sixth button is a toggle - View
Dll's/View Handles. You can also toggle using Cntrl+H/Cntrl/D
***
For each managed Thread object you create, the CLR creates 5 objects:
- 1 "OS thread" object and
- 4 "Manual Reset event" objects.
These objects are not necessarily deleted when the GC/Finalizer runs, they
are not under control of the GC, they are managed by the CLR, the CLR will
delete them when he sees fit.

Thank you, I did not realize the CLR created 4 manual reset events
with every new thread, which explains why I kept seeing the count go
up by 4 initally when I was always starting the thread anew.

***
It should be 5 actually, but all depends on the CLR version and run-time
behavior. The CLR uses his own heuristics to do some clean-up, but again all
depends on the current activity of the JIT, GC, Finalizer etc
***
Just create a small program which starts a "do nothing thread" and watch
the
handle count going up for each thread you start, until the CLR decides
it's
enough and starts releasing it's internally owned handles (CLR/Framework
version dependent). At that point you will see the Handle count going
down.

Also, keep in mind that PE is only a tool that takes snapshots of a
running
external process, that means that the handle count does not necessarily
represent the actual count. In order to watch the real count, you have to
freeze the process by running your code under a debugger. This means that
PE
is not a debugging tool.
In order to watch handles life time, you need to start your program under
a
native debugger like nsdb or windbg (See Debugging Tools For Windows).
Issuing the !handle debugger command will show you the list of handles
owned
by the process.

I will look into them, thanks!
 
C

Cartoper

***
Not in the menus, but on the tool bar, the sixth button is a toggle - View
Dll's/View Handles. You can also toggle using Cntrl+H/Cntrl/D
***

Sweat, that will hopefully make ALL the difference! I will let you
know.

Cartoper
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top