Completion Ports

Completion ports provide a scalable mechanism for managing I/O operations in high-performance network applications. They allow an application to associate I/O operations with a port, and then efficiently retrieve the results of those operations as they complete.

Introduction to Completion Ports

Traditional I/O models often involve blocking operations or complex callback mechanisms. Completion ports offer a more streamlined approach by decoupling the initiation of I/O from its completion. When an I/O operation is initiated with a completion port, the calling thread doesn't wait for the operation to finish. Instead, the operating system signals the completion port when the operation is done, and a worker thread can then retrieve the completion details and process them.

Key Concepts

Core Functions

The following functions are central to using completion ports:

CreateIoCompletionPort

This function is used to create a completion port and associate a file handle with it. If no file handle is provided, a new completion port is created. If a file handle is provided, it's associated with the port, and the number of concurrent threads that can process I/O operations can be specified.

HANDLE CreateIoCompletionPort(
  HANDLE ExistingCompletionPort,
  HANDLE FileHandle,
  ULONG_PTR CompletionKey,
  DWORD NumberOfConcurrentThreads
);

GetQueuedCompletionStatus

This function waits for an I/O operation to complete on the specified completion port. It blocks the calling thread until a completion packet is available or a timeout occurs.

BOOL GetQueuedCompletionStatus(
  HANDLE       CompletionPort,
  LPDWORD      lpNumberOfBytesTransferred,
  PULONG_PTR   lpCompletionKey,
  LPOVERLAPPED *lpOverlapped,
  DWORD        dwMilliseconds
);

PostQueuedCompletionStatus

This function posts a user-defined I/O completion packet to a specified completion port. This is useful for signaling events or passing custom messages between threads.

BOOL PostQueuedCompletionStatus(
  HANDLE       CompletionPort,
  DWORD        dwNumberOfBytesTransferred,
  ULONG_PTR    dwCompletionKey,
  LPOVERLAPPED lpOverlapped
);

Creating and Managing a Completion Port System

A typical setup involves:

  1. Creating a completion port using CreateIoCompletionPort.
  2. Creating a pool of worker threads. Each worker thread repeatedly calls GetQueuedCompletionStatus to wait for I/O operations to complete.
  3. Associating socket handles with the completion port using CreateIoCompletionPort, providing a unique CompletionKey for each socket.
  4. Initiating asynchronous I/O operations on the sockets using functions like WSARecv and WSASend, passing an OVERLAPPED structure that includes a pointer to a context structure.
  5. When an I/O operation completes, GetQueuedCompletionStatus returns in the worker thread. The worker thread uses the returned CompletionKey and OVERLAPPED structure to identify the socket and context, and then processes the results (e.g., handles received data, sends more data).

Advantages of Completion Ports

See Also