tokio: Dropping a File does not close it
Version
tokio v0.2.13 tokio-macros v0.2.5 rust 1.42.0-nightly
Platform
Linux 64-bit Debian, kernel 4.19.0-5-amd64
Description
I’m trying to write a file to disk, and then execute it. To do both, I’m using tokio::fs::File and tokio::process::Command and the default executor. Here’s my code:
use std::os::unix::fs::OpenOptionsExt;
use tokio::io::AsyncWriteExt;
#[tokio::main]
async fn main() {
let mut std_options = std::fs::OpenOptions::new();
std_options.create(true).truncate(true).write(true).mode(0o750);
let tokio_options = tokio::fs::OpenOptions::from(std_options);
let mut file = tokio_options.open("/tmp/foo.sh").await.unwrap();
file.write_all("#!/bin/sh\necho hello".as_bytes()).await.unwrap();
drop(file); // drop file to close it
let status = tokio::process::Command::new("/tmp/foo.sh").status().await.unwrap(); // line 15
println!("Status: {:?}", status);
}
The docs here say that dropping a File will close it (which is why you see me trying to do that in the above code). When I run this code, it sometimes works, but most of the time it fails with this error:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 26, kind: Other, message: "Text file busy" }', src/main.rs:15:18
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
I think this means that my program still /tmp/foo.sh open when tokio::process::Command tries to execute it. This is surprising to me, since the docs suggest that dropping a file is how you close it (and I did not see any explicit “close” methods).
Am I missing something? How can I safely close this file so that it can be used?
Thanks!
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 15 (8 by maintainers)
Commits related to this issue
- fs: properly document when files are closed Refs: #2307 — committed to Darksonn/tokio by Darksonn 4 years ago
- docs: make it easier to discover extension traits (#2434) Refs: #2307 — committed to tokio-rs/tokio by Darksonn 4 years ago
Hey,
So the short version is that you should
flushthe file to make sure that all pending operations are completed at a specific point in your code, so adding the following should make it work as expected:The slightly longer version is that all filesystem operations are performed on a separate threadpool (accessed through
spawn_blocking). This keeps the file handle alive while there are pending operations in the background even though the tokioFileinstance you created is dropped. A way that we wait for these pending operations to be applied for theFileinstance, is viaAsyncWriteExt::flush.This does sound like a good documentation item to fix.
I am working on a bigger doc change that addresses this (among other things).