If I attach a file to an existing completion port, do I have to close the completion port handle a second time?

There are two ways of calling the Create­IO­Completion­Port function. You can pass a null pointer as the Existing­Completion­Port parameter, indicating that you would like to create a brand new completion port, associated with the file handle you passed (if you passed one). Or you can pass the handle of an existing completion port, and the file handle you passed will be associated with that port, in addition to whatever other file handles are already associated with that port.

In both cases, the return value on success is a handle to the I/O completion port, either the brand new one or the existing one. The question from a customer was, "In the case where I am associating a file handle to an existing completion port, do I need to close the completion port handle a second time?" In other words, is there a handle leak in this code:

BOOL CCompletionPort::Attach(HANDLE h, ULONG_PTR key)
 // you are required to have created the completion port first
 assert(m_hPort != nullptr);

 HANDLE hPort = CreateIoCompletionPort(h, m_hPort, key, 0);

 // line missing? if (hPort != nullptr) CloseHandle(hPort);

 return hPort != nullptr;

No, there is no handle leak. If you ask to associate a file with an existing completion port, the same handle you passed as the Existing­Completion­Port parameter is returned back on success. There is no new obligation to close the handle a second time because kernel handles are not reference-counted. Closing a kernel handle twice is always an error, because the first close closes the handle, and the second close attempts to use a handle that is already closed. There is no reference count on handles.¹

¹ There is a reference count on kernel objects, but handles are not reference-counted. The standard way to increment the reference count on a kernel object is... to create a new handle to it!

Comments (13)
  1. GByrkit says:

    I learn something every posting on this blog.  There is a minor typo in the first line.  Likely "the Create­IO­Completio­Port function" should be "the Create­IO­Completion­Port function".

    [Fixed, thanks. -Raymond]
  2. > If the ExistingCompletionPort parameter was a valid I/O completion port handle, the return value is that same handle

    Ick. Prefer creating multiple easy-to-call functions over creating a single function with complicated rules.

    HANDLE OnlyCreateIoCompletionPort(

     _In_      DWORD NumberOfConcurrentThreads


    BOOL WINAPI AssociateIoCompletionPortWithFile(

     _In_      HANDLE FileHandle,

     _In_opt_  HANDLE ExistingCompletionPort,

     _In_      ULONG_PTR CompletionKey


    HANDLE CreateIoCompletionPortAndAssociateWithFile(

     _In_      HANDLE FileHandle,

     _In_      ULONG_PTR CompletionKey,

     _In_      DWORD NumberOfConcurrentThreads


    [I agree. Whoever designed the function was trying to be too clever. -Raymond]
  3. Neglected to update annotation; _In_opt_ should now just be _In_ for AssociateIoCompletionPortWithFile's ExistingCompletionPort parameter.

  4. A handle is always a reference by definition (hence its name: something used to "grab" an object). IIRC, in the early versions of classic Mac OS, handles were actually indexes into a table that contained (pointer, reference count) pairs. Of course, documentation warned to manage them as opaque values, but in later versions (7.x and newer) many compatibility problems were caused by applications and extensions that tried to be too clever. Does it sound familiar? ;-)

  5. Nodir says:

    How can I deattach a file from the attached completion port?

  6. alegr1 says:


    No way.

  7. So the handle *is* the reference. Same handle, same reference.

  8. Neil says:

    @Antonio In 16-bit Windows an LMEM_MOVABLE handle happened to point to the pointer value that LocalLock returns. I wonder how many applications used this to avoid locking their movable allocations.

  9. Is it just me or the documentation for I/O Completion Ports is definitely written with "Feature-colored glasses" (technet.microsoft.com/…/jj992548.aspx)?

    In my ignorance of what the a "completion port" is or does I checked out the classical "topic introduction page" in the Platform SDK (msdn.microsoft.com/…/aa365198), which usually does a decent job at explaining what are we talking about. What I found is something along the lines of the CardSpace stuff – generic description with vagues hints of context ("provide an efficient threading model for processing multiple asynchronous I/O requests on a multiprocessor system") and detailed descriptions for people who already know what we are talking about.

    I still ignore what an IO completion port is or does.

    (sorry for the rant)

  10. Gabe says:

    Matteo: An I/O Completion Port is a queue of I/O completion packets. Once an asynchronous file handle is associated with a completion port, then whenever an I/O completes on that file, a completion packet is posted to the queue. The queued packets are consumed by calling GetQueuedCompletionStatus.

    Generally you will have a single IOCP to handle all the I/O in your process and a pool of threads that all call GetQueuedCompletionStatus in a loop. This allows asyncrhonous I/O to be handled as efficiently as possible, with a minimum of kernel mode transitions and context switches.

    Does that help?

  11. Killer{R} says:

    'kernel handles are not referenced-counted' – is it STRICTLY TRUE? IMHO better answer 'no' for both types on questions – 'Are kernel object handles are reference-counted?' and 'Are kernel object handles are NOT reference counted?'. The second type of question can raise if somebody will wish to rely on fact that for example DuplicateHandle always generates new unique handle value, even it has completely matches properties as initial one.

  12. @Gabe: definitely, thank you; if that page had a similar brief-but-complete explanation at top it would be _way_ more useful.

  13. Joshua says:

    I had a dream last night that Raymond made some particular outlandish response to somebody's comment on tomorrows post, and for some reason I called him on his phone (never mind how I had his number) to explain it rather than post a reply.

Comments are closed.