When performing file read and write operations in the Tokio asynchronous context, a commonly used module is tokio::fs
, which contains the asynchronous version of the standard library file API with the same name.
Most file methods have the same semantics as the methods in the standard library, but the flush
method is slightly different.
Starting from the tokio::fs
documentation#
As of writing this article, the latest version of Tokio is 1.43.0. Clicking on the documentation link provides a brief introduction to the module:
According to the documentation, since most operating systems do not provide asynchronous file system APIs, the asynchronous file operations of tokio::fs
actually execute general synchronous file operations in the spawn_blocking
thread pool.
In the future, it may switch to asynchronous file system APIs like io_uring
, but at least currently, it is implemented using a thread pool on all platforms.
Special note on the flush method in the documentation#
Scrolling down a bit, we can see the following description:
The documentation specifically uses Note
to remind users that it is important to use flush
when writing to a Tokio File
, because calls to the write
method will return before the write is complete, and flush
must be explicitly called to wait for the write to finish.
This is different from the standard library's File
, which is caused by the underlying implementation of spawn_blocking
.
Standard library's flush#
Why does Tokio need to remind users to call flush
? Is there no flush
in the standard library or is it unnecessary to call flush
? This requires looking back at the standard library documentation.
Indeed! Since the File
structure does not contain a buffer, the flush
method on Unix
and Windows
does not perform any operations. In other words, forgetting to flush
when writing with the standard library's File
will not affect any program logic on 99.99% of platforms.
Of course, the documentation also adds that this is just a lower-level explanation to aid understanding and is not binding; the behavior in subsequent versions may also change.
Additional explanation#
During my research, I found a reply from alice, a main author of Tokio, in the forum, which may help with understanding.
A completed write on a tokio::fs::File means that it has been handed off to the threadpool to be written, but that does not necessarily mean that the threadpool has written it yet. Calling flush allows you to wait for that to happen.
It's not possible to change this. The AsyncWrite trait's signature fundamentally forces us to do it this way.
A completed write operation on tokio::fs::File
means it has been handed off to the thread pool for writing, but this does not necessarily mean that the thread pool has completed the write. Calling flush
allows you to wait for the write operation to actually finish.
This cannot be changed. The signature of the AsyncWrite
trait fundamentally forces us to handle it this way.
Conclusion#
Putting aside the details, we can draw a simple conclusion:
Not calling flush
after writing to a file may lead to errors, but calling flush
definitely won't!
Flushing the buffer after writing a file is a good habit; it may not have an effect, but it certainly won't cause any problems. 😋