Asynchronous I/O

Asynchronous I/O APIs provide a method for threaded client/server models to perform highly concurrent and memory-efficient I/O.

In previous threaded client/server models, typically two I/O models have prevailed. The first model dedicates one thread per client connection. The first model consumes too many threads and might incur a substantial sleep and wake-up cost. The second model minimizes the number of threads by issuing the select() API on a large set of client connections and delegating a readied client connection or request to a thread. In the second model, you must select or mark on each subsequent select, which might cause a substantial amount of redundant work.

Asynchronous I/O and overlapped I/O resolve both these dilemmas by passing data to and from user buffers after control has been returned to the user application. Asynchronous I/O notifies these worker threads when data is available to be read or when a connection has become ready to transmit data.

Asynchronous I/O advantages

  • Asynchronous I/O uses system resources more efficiently. Data copies from and to user buffers are asynchronous to the application that initiates the request. This overlapped processing makes efficient use of multiple processors and in many cases improves paging rates because system buffers are freed for reuse when data arrives.
  • Asynchronous I/O minimizes process/thread wait time.
  • Asynchronous I/O provides immediate service to client requests.
  • Asynchronous I/O decreases the sleep and wake-up cost on average.
  • Asynchronous I/O handles bursty application efficiently.
  • Asynchronous I/O provides better scalability.
  • Asynchronous I/O provides the most efficient method of handling large data transfers. The fillBuffer flag on the QsoStartRecv() API informs the operating system to acquire a large amount of data before completing the Asynchronous I/O. Large amounts of data can also be sent with one asynchronous operation.
  • Asynchronous I/O minimizes the number of threads that are needed.
  • Asynchronous I/O optionally can use timers to specify the maximum time allowed for this operation to complete asynchronously. Servers close a client connection if it has been idle for a set amount of time. The asynchronous timers allow the server to enforce this time limit.
  • Asynchronous I/O initiates secure session asynchronously with the gsk_secure_soc_startInit() API.
Table 1. Asynchronous I/O APIs
API Description
gsk_secure_soc_startInit() Starts an asynchronous negotiation of a secure session, using the attributes set for the secure environment and the secure session.
Note: This API supports only sockets with address family AF_INET or AF_INET6 and type SOCK_STREAM.
gsk_secure_soc_startRecv() Starts an asynchronous receive operation on a secure session.
Note: This API supports only sockets with address family AF_INET or AF_INET6 and type SOCK_STREAM.
gsk_secure_soc_startSend() Starts an asynchronous send operation on a secure session.
Note: This API supports only sockets with address family AF_INET or AF_INET6 and type SOCK_STREAM.
QsoCreateIOCompletionPort() Creates a common wait point for completed asynchronous overlapped I/O operations. The QsoCreateIOCompletionPort() API returns a port handle that represents the wait point. This handle is specified on the QsoStartRecv(), QsoStartSend(), QsoStartAccept(), gsk_secure_soc_startRecv(), or gsk_secure_soc_startSend() APIs to initiate asynchronous overlapped I/O operations. Also this handle can be used with QsoPostIOCompletion() to post an event on the associated I/O completion port.
QsoDestroyIOCompletionPort() Destroys an I/O completion port.
QsoWaitForIOCompletionPort() Waits for completed overlapped I/O operation. The I/O completion port represents this wait point.
QsoStartAccept() Starts an asynchronous accept operation.
Note: This API supports only sockets with address family AF_INET or AF_INET6 and type SOCK_STREAM.
QsoStartRecv() Starts an asynchronous receive operation.
Note: This API supports only sockets with address family AF_INET or AF_INET6 and type SOCK_STREAM.
QsoStartSend() Starts an asynchronous send operation.
Note: This API supports only sockets with the AF_INET or AF_INET6 address families with the SOCK_STREAM socket type.
QsoPostIOCompletion() Allows an application to notify a completion port that some API or activity has occurred.
QsoGenerateOperationId() Allows an application to associate an operation identifier that is unique for a socket.
QsoIsOperationPending() Allows an application to check if one or more asynchronous I/O operations are pending on the socket.
QsoCancelOperation() Allows an application to cancel one or more asynchronous I/O operations that are pending on the socket.

How asynchronous I/O works

An application creates an I/O completion port using the QsoCreateIOCompletionPort() API. This API returns a handle that can be used to schedule and wait for completion of asynchronous I/O requests. The application starts an input or an output function, specifying an I/O completion port handle. When the I/O is completed, status information and an application-defined handle is posted to the specified I/O completion port. The post to the I/O completion port wakes up exactly one of possibly many threads that are waiting. The application receives the following items:

  • A buffer that was supplied on the original request
  • The length of data that was processed to or from that buffer
  • An indication of what type of I/O operation has been completed
  • Application-defined handle that was passed on the initial I/O request

This application handle can be the socket descriptor identifying the client connection, or a pointer to storage that contains extensive information about the state of the client connection. Since the operation was completed and the application handle was passed, the worker thread determines the next step to complete the client connection. Worker threads that process these completed asynchronous operations can handle many different client requests and are not tied to just one. Because copying to and from user buffers occurs asynchronously to the server processes, the wait time for client request diminishes. This can be beneficial on systems where there are multiple processors.

Asynchronous I/O structure

An application that uses asynchronous I/O has the structure demonstrated by the following code fragment.

#include <qsoasync.h>
struct Qso_OverlappedIO_t
{
  Qso_DescriptorHandle_t descriptorHandle;
		void *buffer;
		size_t	 bufferLength;
		int	postFlag : 1;
		int	fillBuffer : 1;
		int	postFlagResult : 1;
		int	reserved1 : 29;
		int	returnValue;
		int	errnoValue;
		int	operationCompleted;
		int	secureDataTransferSize;
		unsigned int	bytesAvailable;
		struct timeval operationWaitTime;
		int	postedDescriptor;
		char reserved2[40];
}