Thank you, probably you are right.
It is not easy to verify if flushing will fix the issue because I cannot reproduce the issue even without flush. Will keep trying.
On the other hand I don't understand why flush is necessary here. I expected it shoud be done automatically on file close.
The way tokio::fs::File works is by off-loading the operation to the spawn_blocking pool, and flushing will make it wait for the spawn_blocking task to finish. If you immediately check the file contents after writing, then it's possible that the spawn_blocking task has yet to finish (the destructor can't wait for it because it's not async).
Still, even if you don't flush, the operation will continue running and probably finish a few ms later.
The function write_all returns a result of type (), not a length. So "written" is always "()". That doesn't explain why the write is not working though.
No, I don't check content after writing. In fact the call to this function is the last thing I do in my main.
Is it possible that in rare case my main finishes before spawn_blocking executes write operation? So operation is lost?
It looks to me as if this is simply a program that can terminate without flushing the file. If main terminates before write_timestamp finishes, the write is not going to be flushed.
Right, it does not drop dst_file explicitly. I supposed the file should be dropped automatically at the end of scope. If I am wrong then I need to search an explanation about how this works in async code.
" Typically, an executor will poll a future once to start off. When Future s indicate that they are ready to make progress by calling wake() , they are placed back onto a queue and poll is called again, repeating until the Future has completed."
It sounds to me as if "future completion" is not quite the same thing as the function finishing.
Obviously I'm not an expert too but I think here we have completed future because the main function awaited on it. Does that mean function finished too? Yes, I think so. I just cannot imagine how async function can be unfinished if its future is already completed.
I have read that an async function is implemented as a "state machine". It's variables are stored I think in some kind of structure, which perhaps has methods called by the "executor" (maybe called "poll" or something). When poll returns the "result" value, the structure may still exist, and the variables may not yet have been dropped. Don't take any of this as accurate, it's some kind of impression I have.
Ok, then the next question in this situation is: when those variables will be dropped? Those variables can contain some exclusive resources, like mutexes, file handles etc. That would lead to a lot of weird side effects. My understanding is that await protects us from those side effects.
By the way I modified my reproduction code to run it with async-std runtime and there is no problem when I run it without flush call.
I was exactly confused by the same thing reading your question and I reproduced the behavior your describe on my side. To me the fact that you awaited every future was enough to guarantee that the write actually took place but I went and checked the code and if you look at: tokio/file.rs at ee4b2ede83c661715c054d3cda170994a499c39f · tokio-rs/tokio · GitHub you can see that it seems possible that the future returns only once the bytes have been written to some underlying internal buffer (the return Ready(Ok(n)) line 662), and hence I don't see a guarantee that the spawn_blocking has finished running.
It is also super counter-intuitive to me, but given that @alice seems to be reading the thread, I hope she will clarify to us what exact guarantees File makes.
alice said ( and I didn't absorb it at the time : "the destructor can't wait for it because it's not async" ).
Based on that, the destructor may be running, but the flush is not done at that time but later. Overall, terminating an async program is a pretty tricky business, as there can be unfinished work. The async programs I have don't terminate by themselves a t all!