Hi,
I'd like to summarize my thoughts about an implementation of overlapped I/O in Wine that will offer full Windows functionality. I havent't thought it through in all detail, but I have a clear enough picture to write an RFC.
I regard the following points as crucial:
1 "Generic" routines like ReadFileEx() need to work with files of almost arbitrary type, i.e. normal files, pipes, sockets, comm ports, ...
2 Different file types imply different semantics for overlapped I/O. In particular:
a Files that don't support positioning (i.e. FIFOs, or basically everything except regular files) need to be accessed sequentially, i.e. there may only be one reader and one writer at a time, and these must be scheduled in the same order in which they were submitted by the application. Otherwise, the data read or written will be potentially scrambled. For regular files, on the other hand, spawning several I/O requests in parallel may be beneficial.
b Files on some devices (in particular, block devices on Linux) need to spawn a separate thread for the async I/O operation, because I/O on such devices is always blocking, which is undesirable. Neither the process itself nor the service thread should be blocked by a time-consuming floppy I/O operation, for example.
3 The semantics of individual asynchronous I/O requests depend on the function that was called by the application, e.g. ReadFile() or WSARecv(), which take different arguments. Such calls may be mixed by the application, although it's certainly not good programming practice to do so.
Based on these arguments, I'd suggest the following:
A) The generic functions need to find out what the properties of a file descriptor wrt asynch I/O are. The most general way to implement this is to have an element
int (*get_async) (struct object*, struct file_async*)
in the obj_ops array. For non-file objects, this element would be NULL or no_get_async(). For non-overlapped file descriptors, the function would assign NULL to the file_async* argument, and return an error, a valid pointer would be assigned to file_async* otherwise.
struct file_async would contain per-file information about asynch I/O, e.g.
struct file_async { struct async_private *readers; /* list of async read requests */ struct async_private *writers; /* list of async write requests */ int flags /* supports positioning? Is I/O blocking on host OS ? etc. */ int (*get_timeout) (struct object *obj, int type, int count); /* function that returns timeout for device, needed e.g. for serial comm */ }
I currently see no need for other file-type dependedent functions; It should be possible to write generic routines that handle the requests correctly according to the flags variable.
The Wine server API would containe a file_get_async() request that returns the file_async structure associated with the file by calling the get_async() element of obj_ops.
The async_private struct would look pretty similar as it looks now, with a generic part and a part that depends on the function invoked for doing async I/O (e.g. for sockets, whether ReadFile() or WSARecv() was called on the socket fd). IMO the async_private should not contain file type-dependent information which is per-file. It should rather contain a pointer to the respective file_async struct. Most importantly, the async_private struct it carries a pointer to a function that does the actual I/O, and another function pointer that handles the termination of the request (invocation of user completion routine etc.)
B) The check_async() function in synchro.c operates on file descriptors rather than async requests. Once an overlapped I/O request is issued by the application, it is queued either in the readers or writers list of the async object (which is obtained from the server). If both the readers and writers lists were NULL beforehand, the fd is inserted into the list for check_async to work on.
check_async() activates the first element(s) of both the readers and writers lists (where activate means that the actual read/write call is triggered). For FIFOS, only one element would be activated; for regular files several requests up to a given limit (e.g. 4).
If the file is on a device where normal I/O would be blocking, a new thread is created for each activated request to satisfy it.
When the requests' I/O routine (separate thread or not) is completed, it activates an event that triggers the generic "finish" routine.
The finish routine calls the finish routine of the request, cleans up, and activates a new request if there is one that was queued but not activated. If both the readers and writers queue are empty after finishing the request, the file descriptor is removed from the list of handles to check in check_async().
C) If CancelIo() is called or a file is closed, all pending IO requests are removed from the file's async readers and writers lists; potential subthreads are terminated.
With this approach, the wineserver would only be needed for retreiving the file's async I/O properties. The biggest change to the wine core would be the addition of a new function to obj_ops.
I am looking forward to comments.
Regards, Martin
PS. A minor remark: The above approach would make it unnecessary to check for the return value of xyz_get_info() to be FD_TYPE_OVERLAPPED when initiating overlapped I/O. Instead, one could simply check if the async object returned by get_async() is non-NULL.