I/O Completion Ports (IOCP)

High-Performance Asynchronous I/O in Windows

Introduction to I/O Completion Ports

I/O Completion Ports (IOCP) are a Microsoft Windows API feature that provides a scalable, high-performance mechanism for asynchronous I/O operations. They are particularly useful in server applications that handle a large number of concurrent I/O requests, such as network servers or file servers. IOCP allows a small number of threads to manage a large number of I/O operations efficiently, significantly reducing the overhead associated with traditional thread-per-request models.

The core idea behind IOCP is to decouple the initiation of an I/O operation from its completion. Instead of waiting for an I/O operation to finish, an application can initiate multiple I/O operations and then efficiently wait for any of them to complete using a single thread.

Creating an I/O Completion Port

An I/O Completion Port is created using the CreateIoCompletionPort function. This function can either create a new IOCP or associate a file handle with an existing IOCP.


HANDLE hCompletionPort = CreateIoCompletionPort(
    INVALID_HANDLE_VALUE, // File handle to associate (or NULL to create a new port)
    NULL,                 // Existing completion port handle (or NULL to create a new one)
    0,                    // Completion key (context information)
    0                     // Number of threads to allow concurrency (0 means system-determined)
);

if (hCompletionPort == NULL) {
    // Handle error
    DWORD dwError = GetLastError();
    // ...
}
        

Associating File Handles with an IOCP

Once an IOCP is created, file handles that will perform asynchronous I/O must be associated with it. This is also done using CreateIoCompletionPort.


HANDLE hFile = OpenFile(...); // Assume this opens a file asynchronously
DWORD dwCompletionKey = 1;    // Unique key for this file handle

HANDLE hCompletionPort = CreateIoCompletionPort(
    hFile,               // File handle to associate
    hExistingCompletionPort, // Handle to the existing IOCP
    dwCompletionKey,     // Completion key for this handle
    dwNumberOfConcurrentThreads // Max concurrency for this port
);

if (hCompletionPort == NULL) {
    // Handle error
    DWORD dwError = GetLastError();
    // ...
}
        

Each file handle associated with the IOCP can have a unique CompletionKey. This key is returned when an I/O operation on that handle completes and is crucial for identifying which handle or context an I/O completion belongs to.

Processing I/O Completions

To wait for I/O operations to complete, threads call the GetQueuedCompletionStatus function. This function blocks until an I/O operation completes or a specified timeout occurs.


LPOVERLAPPED lpOverlapped = NULL;
ULONG_PTR CompletionKey;
DWORD dwBytesTransferred;
BOOL bSuccess;

while (TRUE) {
    bSuccess = GetQueuedCompletionStatus(
        hCompletionPort,
        &dwBytesTransferred,
        &CompletionKey,
        &lpOverlapped,
        INFINITE // Timeout in milliseconds (INFINITE means wait forever)
    );

    if (!bSuccess) {
        // Handle error. GetLastError() will provide more info.
        // If lpOverlapped is NULL, it might be an error closing the port or
        // an incomplete I/O operation.
        DWORD dwError = GetLastError();
        if (dwError == WAIT_TIMEOUT) {
            // Timeout occurred (if not INFINITE)
            continue;
        }
        // Handle other errors
        break;
    }

    // Process the completed I/O operation
    // Use CompletionKey to identify the handle/context
    // Use lpOverlapped to get details about the operation
    // Use dwBytesTransferred to know how much data was transferred

    // For example, if CompletionKey is a pointer to a context structure:
    // PCONTEXT_DATA pContext = (PCONTEXT_DATA)CompletionKey;
    // if (lpOverlapped == &pContext->Overlapped) { ... }

    // Free the OVERLAPPED structure if dynamically allocated
    // FreeOverlappedAndContext(pContext);
}
        

The lpOverlapped pointer is typically a pointer to an OVERLAPPED structure that was passed when the I/O operation was initiated. The CompletionKey associated with the handle is returned in the CompletionKey parameter.

Sample Code Snippet (Conceptual)

A typical IOCP server architecture involves:

  1. Creating an IOCP.
  2. Creating a pool of worker threads that call GetQueuedCompletionStatus.
  3. When a client connects or a file needs to be accessed, its handle is associated with the IOCP.
  4. Initiating asynchronous I/O operations (e.g., ReadFileEx, WriteFileEx, AcceptEx, WSARecv, WSASend) using the associated handle.
  5. The worker threads receive completion notifications via GetQueuedCompletionStatus and process the results, queuing new I/O operations as needed.

Note: Implementing IOCP often involves careful management of memory for OVERLAPPED structures and associated context data. Ensure these structures are properly allocated and deallocated to prevent leaks or crashes.

Best Practices

Important: IOCP is a powerful tool for high-performance I/O but requires a thorough understanding of asynchronous programming concepts and careful implementation to avoid common pitfalls.