N
Norman Bullen
I've always believed that, with respect to Kernel objects, reference
counting meant that the Kernel kept track of the number of handles
(unique 32-bit numbers) that it issued for each Kernel object. In most
cases, when that handle comes back to the Kernel in a call to
CloseHandle() or similar, the count is decremented and, if now zero, the
Kernel object is destroyed.
A recent experience seems to indicate that this belief is incorrect.
Please observe the following code. It's part of the preamble to a call
to CreateProcess() using pipes to receive the output of a command line
program. Two pipes are created and the handles of the input ends are
duplicated to make them inheritable. DUPLICATE_CLOSE_SOURCE should cause
the original non-inheritable handle to be closed.
CreatePipe(&hpipeStdOut, &hPipe, NULL, BUFFER_SIZE);
DuplicateHandle(hProcess, hPipe, hProcess, &startupInfo.hStdOutput,
0, TRUE, DUPLICATE_CLOSE_SOURCE|DUPLICATE_SAME_ACCESS
);
assert(!CloseHandle(hPipe));
CreatePipe(&hpipeStdErr, &hPipe, NULL, BUFFER_SIZE);
DuplicateHandle(hProcess, hPipe, hProcess, &startupInfo.hStdError,
0, TRUE, DUPLICATE_CLOSE_SOURCE|DUPLICATE_SAME_ACCESS
);
assert(!CloseHandle(hPipe));
Originally, it was written without the assert() statements and, as far
as I can tell, worked correctly.
I added the assert() statements as part of an effort to assure myself
that the program was no leaking handles. (The application of which this
a small part will pass through this code many times during its execution.)
I was surprised to find that the assertions failed, meaning that
CloseHandle() was returning a non-zero value indicating success--it was
able to the handles being passed and that in turn meant that the
original handles were _not_ closed by DuplicateHandle().
Further, I found that when my application attempted to read from the
output end of the pipes the ReadFile() failed with ERROR_BROKEN_PIPE
indicating that the input handle had been closed even though the command
line program had not had a chance to terminate. It looks like
CloseHandle() with the original handle closes both the original handle
and the duplicated handle.
I moved the assert() statement to a point after the call to
CreateProcess() and after the calls to CloseHandle() that close the two
startupInfo handles. (I don't need them anymore since they've been
inherited into the command line program by this point.) Now I get a
first chance exception from the CloseHandle() in the assert() and the
assertion fails; assert() pops up a message box.
Here's what I now believe to be happening: the Kernel is not counting
the number of unique handles that have been passed out, but is instead
counting the number of processes to which those handles have been
passed. The Kernel treats any handle owned by a process as equivalent,
at least in the context of CloseHandle(). (Any handle passed to
CloseHandle() closes all handles to that Kernel object that are owned by
the calling process.) I may, if I can find time, do some more
investigation to see whether all handles are treated as equivalent with
respect to access and inheritance.
Any thoughts on this? Is this behavior documented somewhere?
Norm
counting meant that the Kernel kept track of the number of handles
(unique 32-bit numbers) that it issued for each Kernel object. In most
cases, when that handle comes back to the Kernel in a call to
CloseHandle() or similar, the count is decremented and, if now zero, the
Kernel object is destroyed.
A recent experience seems to indicate that this belief is incorrect.
Please observe the following code. It's part of the preamble to a call
to CreateProcess() using pipes to receive the output of a command line
program. Two pipes are created and the handles of the input ends are
duplicated to make them inheritable. DUPLICATE_CLOSE_SOURCE should cause
the original non-inheritable handle to be closed.
CreatePipe(&hpipeStdOut, &hPipe, NULL, BUFFER_SIZE);
DuplicateHandle(hProcess, hPipe, hProcess, &startupInfo.hStdOutput,
0, TRUE, DUPLICATE_CLOSE_SOURCE|DUPLICATE_SAME_ACCESS
);
assert(!CloseHandle(hPipe));
CreatePipe(&hpipeStdErr, &hPipe, NULL, BUFFER_SIZE);
DuplicateHandle(hProcess, hPipe, hProcess, &startupInfo.hStdError,
0, TRUE, DUPLICATE_CLOSE_SOURCE|DUPLICATE_SAME_ACCESS
);
assert(!CloseHandle(hPipe));
Originally, it was written without the assert() statements and, as far
as I can tell, worked correctly.
I added the assert() statements as part of an effort to assure myself
that the program was no leaking handles. (The application of which this
a small part will pass through this code many times during its execution.)
I was surprised to find that the assertions failed, meaning that
CloseHandle() was returning a non-zero value indicating success--it was
able to the handles being passed and that in turn meant that the
original handles were _not_ closed by DuplicateHandle().
Further, I found that when my application attempted to read from the
output end of the pipes the ReadFile() failed with ERROR_BROKEN_PIPE
indicating that the input handle had been closed even though the command
line program had not had a chance to terminate. It looks like
CloseHandle() with the original handle closes both the original handle
and the duplicated handle.
I moved the assert() statement to a point after the call to
CreateProcess() and after the calls to CloseHandle() that close the two
startupInfo handles. (I don't need them anymore since they've been
inherited into the command line program by this point.) Now I get a
first chance exception from the CloseHandle() in the assert() and the
assertion fails; assert() pops up a message box.
Here's what I now believe to be happening: the Kernel is not counting
the number of unique handles that have been passed out, but is instead
counting the number of processes to which those handles have been
passed. The Kernel treats any handle owned by a process as equivalent,
at least in the context of CloseHandle(). (Any handle passed to
CloseHandle() closes all handles to that Kernel object that are owned by
the calling process.) I may, if I can find time, do some more
investigation to see whether all handles are treated as equivalent with
respect to access and inheritance.
Any thoughts on this? Is this behavior documented somewhere?
Norm