Skip to main content
gamedev.net
🔒 Locked

ReadFile blocking?

Started by BradBobak_68669 3 replies 6.8k views
Original Post
BradBobak_68669
BradBobak_68669
Will ReadFile / WriteFile ever block when the HANDLE passed in refers to a file?

In linux, I can poll on a bunch of fd's to be sure I can read / write something. Unfortunately, I can't find this equivalent in windows.

So in my app, I have a thread that watches a bunch of fd's and notifies some other object when data is available / can be written. These fd's can be pipes / etc.

I'll deal with the pipes in windows later, but for now, I'm thinking that when some object wants to be notified if it can read / write to a file in windows I'll just immediately post to it that it can. Unless ReadFile / WriteFile from / to a file can block, then I guess I'll have to try some other approach.

Thanks, Brad.

Edit : I guess I could do the WriteFile by making it overlappable and having the user fill a buffer, then when that buffer is written asynchronously, I could post that the stream is writeable again. Unfortunately, I can't think of a way to read like this, unless I read byte-by-byte.
Antheus
Antheus
See overlapped IO for non-blocking interface.

To monitor for file system changes, use the appropriate API. Unfortunately, this API comes with a bit of a design flaw, meaning it can miss some changes, look around for existing solutions.


But the use case is unusual. How could a file become "available" for writing. If file exists, it's available. Using files as streams is typically undesirable, that is what pipes and other forms of IPC are for. A file might be currently locked since something else is updating it, but that is different problem and usually solved differently, perhaps via .lock file or some other coordination mechanism, if file is in use, it's unusuable by others, unless opened appropriately.

Blocking in relation to WriteFile also doesn't make much sense as such. WriteFile is constant time operation without any guarantees on time. So it always "blocks", but never indefinitely. For example, writing 1 byte or 1 gigabyte will block in exactly the same way, just the duration will be different.

With writing, things get trickier if using async IO, since data is written out of thread and if process happens or machine crashes, the data submitted will be lost.

Reading a file is also "blocking" in same way. One can read available data, start to end. You cannot wait for more data, since files are not streams and may be modified in different ways. Individual reads always take time proportional to amount of data read, but the upper bound is known and limited by maximum file size for given FS.
BradBobak_68669
BradBobak_68669
I guess I may have to rethink my design. My goal in posix was to have a single thread monitoring all types of open descriptors (pipes, files, sockets) and notifying appropriately when a read or write will succeed. Since I wanted the same interface in windows, I figured I would allow file descriptors, I just didn't know how I would notify the appropriate object that data could be written or read from that descriptor. If a write takes a small amount of time (or system buffers won't fill up) I guess I could always just say 'you can write now', and do the same with a read.
My current idea for writing is to have a buffer the object that writes can fill up, then asynchronously write that buffer. When the write is done, I can notify the object that it can write some more (this would probably work well with pipes as well).
Seeing how reading from a file will probably not block long (get data from disk, write to buffer), I could always just send a 'you_can_read' event to the object wanting that event. Of course, the file could be on another computer and the connection could have been lost, but that is a job for another time.
Sockets will probably be easier, since I can then do a WaitForMultipleObjects and WSAEnumNetworkEvents to determine if the HANDLE can be read from / written to.
Antheus
Antheus

I guess I may have to rethink my design. My goal in posix was to have a single thread monitoring all types of open descriptors (pipes, files, sockets) and notifying appropriately when a read or write will succeed. Since I wanted the same interface in windows, I figured I would allow file descriptors, I just didn't know how I would notify the appropriate object that data could be written or read from that descriptor. If a write takes a small amount of time (or system buffers won't fill up) I guess I could always just say 'you can write now', and do the same with a read.


What are you trying to do?

My current idea for writing is to have a buffer the object that writes can fill up, then asynchronously write that buffer. When the write is done, I can notify the object that it can write some more (this would probably work well with pipes as well).[/quote]

For same handle, within same process, calling multiple writes in succession achieves precisely that.

Seeing how reading from a file will probably not block long (get data from disk, write to buffer)[/quote]

These operations are, by definition, blocking (or non-blocking, depends). WriteFile and ReadFile will not return until they complete. The tricky part is defining completion. Due to all the layers of buffering, writes are particularly problematic, since anything, from OS, file cache or even disk controller may report completion before write has actually completed. All writes are also journaled on recent file systems or performed as a transaction. A typical solution is using fsync or equivalent, but those destroy throughput.

If using asynchronous reads or writes, the burden of managing pending operations falls on you. At which point, to ensure integrity, you need to journal these transactions. To disk. Ensuring they succeed before proceeding. And then you have reinvented journaling file system and ACID database, just without 30 years of expertise that goes into such systems usable in production.

Considering asynchronous mode - what happens if you schedule 20MB of writes, but there is only 2MB of disk free. By the time you get notified of failure, you have scheduled another 20MB. Now you have 38MB of unacknowledged data and your application is still moving along. Even if you stop it, you suddenly have to rewind its state to the point where the writes started failing.

All of these are hard problems and the reason why one hears a lot about "SQL doesn't scale". Of course, there is no equivalent alternative, just trade-offs which reduce reliability or attempt to use redundancy.

Of course, the file could be on another computer and the connection could have been lost, but that is a job for another time.[/quote]

NFS is accessed using some protocol, trying to abstract that away is never a good idea, it's one of 10 falacies of network programming. Windows Explorer in 7 and maybe Vista is so incredibly clumsy and pathetically slow because it ensures not only that, but also covers all other cases, such as random seek on a tape drive - all using same UI, resulting in minute-long timeouts.

Sockets will probably be easier, since I can then do a WaitForMultipleObjects and WSAEnumNetworkEvents to determine if the HANDLE can be read from / written to.[/quote]
Sockets are streams, that only support sequential access. Files are blobs with random access.

Sun figured out long ago that attempting to equate them via same interface results in worst of all worlds. RPC-based middleware like CORBA or even using random access over NFS also demonstrated the abysmal performance and downright useless degradation under load. Cascading failure is an ugly word that soon shows up.


What are you really trying to do? Not how, what.

monitoring all types of open descriptors (pipes, files, sockets) and notifying appropriately when a read or write will succeed.[/quote]

Let's say you have a TCP socket descriptor. Now you choose to write an int (0x01, 0x02, 0x03, 0x04 as bytes). But first we write 0x01 and 0x02 in one send(), then the other two bytes in another.

Socket will be writable during entire time. So knowing that we can write, another part of application writes 0xff in between, destroying the integrity of data. In some way, one could say that TCP sockets are not "thread-safe".

Notification of such type turns out to be not very useful, since the only one that cares about readability/writeability of certain socket is the state structure associated with it. And that one already knows since it's responsible for managing its own socket.

The only information one might use from such observations is determining how often the TCP buffers of a certain socket fill which might, but not necessarily does indicate congestion for that particular socket. Then again, if using non-blocking sockets, WOULDBLOCK error is normal and needs to be properly handled.
BradBobak_68669
BradBobak_68669
What are you trying to do? [/quote]

A single thread monitoring all streams (pipes / files / sockets)


My current idea for writing is to have a buffer the object that writes can fill up, then asynchronously write that buffer. When the write is done, I can notify the object that it can write some more (this would probably work well with pipes as well).[/quote]

For same handle, within same process, calling multiple writes in succession achieves precisely that.
[/quote]

But for something like a pipe, the write may block until a read occurs. I don't want the object to block, so it can write to a buffer, and the monitor thread can deal with writing it out. The object cannot write again until the buffer is flushed (in which case the monitor thread sends a 'can_write' event).


Considering asynchronous mode - what happens if you schedule 20MB of writes, but there is only 2MB of disk free. By the time you get notified of failure, you have scheduled another 20MB. Now you have 38MB of unacknowledged data and your application is still moving along. Even if you stop it, you suddenly have to rewind its state to the point where the writes started failing.
[/quote]

Only one asychronous write will be occuring at a time per stream. I'm thinking that if the write fails, I'll notify the writer and it can take its data back.

What are you really trying to do? Not how, what.[/quote]

Ultimately, an event-based interface to sockets, pipes, etc. Some object can open a pipe, socket, etc and tell my monitor thread to send an event when, say, 500 bytes are available in the incoming buffer. Or a <cr> occured in the input stream. Or the input buffer is full. Or the descriptor has been closed.


monitoring all types of open descriptors (pipes, files, sockets) and notifying appropriately when a read or write will succeed.[/quote]

Let's say you have a TCP socket descriptor. Now you choose to write an int (0x01, 0x02, 0x03, 0x04 as bytes). But first we write 0x01 and 0x02 in one send(), then the other two bytes in another.

Socket will be writable during entire time. So knowing that we can write, another part of application writes 0xff in between, destroying the integrity of data. In some way, one could say that TCP sockets are not "thread-safe".
[/quote]

Another part of the app will not write to the socket. It will be owned by one object only.

Notification of such type turns out to be not very useful, since the only one that cares about readability/writeability of certain socket is the state structure associated with it. And that one already knows since it's responsible for managing its own socket.[/quote]

Basically, I want this object to do its thing until it gets a socket event (could be from many sockets), at which point it handles the event, then continues doing its thing. It could be anything from crunching data, watching for other events, etc.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.