Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 5 additions & 14 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ check: ## Build all code in suitable configurations
cd gitoxide-core && if cargo check --all-features 2>/dev/null; then false; else true; fi
cd gix-hash && cargo check --all-features \
&& cargo check
cd gix-tempfile && cargo check --features signals \
&& cargo check
cd gix-object && cargo check --all-features \
&& cargo check --features verbose-object-parsing-errors
cd gix-index && cargo check --features serde1
Expand Down Expand Up @@ -139,6 +141,8 @@ check: ## Build all code in suitable configurations

unit-tests: ## run all unit tests
cargo test --all
cd gix-tempfile && cargo test --features signals \
&& cargo test
cd gix-features && cargo test && cargo test --all-features
cd gix-ref/tests && cargo test --all-features
cd gix-odb && cargo test && cargo test --all-features
Expand Down
34 changes: 31 additions & 3 deletions gix-tempfile/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,44 @@ edition = "2021"
include = ["src/**/*", "LICENSE-*", "README.md", "CHANGELOG.md"]
rust-version = "1.64"

[[example]]
name = "delete-tempfiles-on-sigterm"
path = "examples/delete-tempfiles-on-sigterm.rs"
required-features = ["signals"]

[[example]]
name = "delete-tempfiles-on-sigterm-interactive"
path = "examples/delete-tempfiles-on-sigterm-interactive.rs"
required-features = ["signals"]

[[example]]
name = "try-deadlock-on-cleanup"
path = "examples/try-deadlock-on-cleanup.rs"
required-features = ["signals"]

[lib]
doctest = false
test = true

[dependencies]
dashmap = "5.1.0"
once_cell = { version = "1.8.0", default-features = false, features = ["race", "std"] }
signal-hook = { version = "0.3.9", default-features = false }
signal-hook-registry = "1.4.0"
tempfile = "3.2.0"
tempfile = "3.4.0"

signal-hook = { version = "0.3.9", default-features = false, optional = true }
signal-hook-registry = { version = "1.4.0", optional = true }

document-features = { version = "0.2.0", optional = true }

[features]
default = []
signals = ["dep:signal-hook", "dep:signal-hook-registry"]

[target.'cfg(not(windows))'.dependencies]
libc = { version = "0.2.98", default-features = false }

[package.metadata.docs.rs]
all-features = true
features = ["document-features"]
rustdoc-args = ["--cfg", "docsrs"]

13 changes: 9 additions & 4 deletions gix-tempfile/examples/delete-tempfiles-on-sigterm-interactive.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
use std::path::PathBuf;

use gix_tempfile::{AutoRemove, ContainingDirectory};
#[cfg(not(feature = "signals"))]
fn main() {
panic!("The `signals` feature needs to be set to compile this example");
}

#[cfg(feature = "signals")]
fn main() -> std::io::Result<()> {
gix_tempfile::setup(Default::default());
use gix_tempfile::{AutoRemove, ContainingDirectory};
use std::path::PathBuf;

gix_tempfile::signal::setup(Default::default());
let filepath = PathBuf::new().join("writable-tempfile.ext");
let markerpath = PathBuf::new().join("marker.ext");
let _tempfile = gix_tempfile::writable_at(&filepath, ContainingDirectory::Exists, AutoRemove::Tempfile)?;
Expand Down
19 changes: 12 additions & 7 deletions gix-tempfile/examples/delete-tempfiles-on-sigterm.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
use std::{
io::{stdout, Write},
path::PathBuf,
};

use gix_tempfile::{AutoRemove, ContainingDirectory};
#[cfg(not(feature = "signals"))]
fn main() {
panic!("The `signals` feature needs to be set to compile this example");
}

#[cfg(feature = "signals")]
fn main() -> std::io::Result<()> {
gix_tempfile::setup(Default::default());
use gix_tempfile::{AutoRemove, ContainingDirectory};
use std::{
io::{stdout, Write},
path::PathBuf,
};

gix_tempfile::signal::setup(Default::default());
let filepath = PathBuf::new().join("tempfile.ext");
let _tempfile = gix_tempfile::mark_at(&filepath, ContainingDirectory::Exists, AutoRemove::Tempfile)?;
assert!(filepath.is_file(), "a tempfile was created");
Expand Down
48 changes: 27 additions & 21 deletions gix-tempfile/examples/try-deadlock-on-cleanup.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
use std::{
path::Path,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
time::Duration,
};

use gix_tempfile::{handle::Writable, AutoRemove, ContainingDirectory, Handle};
#[cfg(not(feature = "signals"))]
fn main() {
panic!("The `signals` feature needs to be set to compile this example");
}

#[cfg(feature = "signals")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
use std::{
path::Path,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
time::Duration,
};

use gix_tempfile::{handle::Writable, AutoRemove, ContainingDirectory, Handle};

let secs_to_run: usize = std::env::args()
.nth(1)
.ok_or("the first argument is the amount of seconds to run")?
Expand All @@ -19,7 +25,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let tempfiles_created = Arc::new(AtomicUsize::default());
let tempfiles_registry_locked = Arc::new(AtomicUsize::default());
let signal_raised = Arc::new(AtomicUsize::default());
gix_tempfile::setup(gix_tempfile::SignalHandlerMode::DeleteTempfilesOnTermination);
gix_tempfile::signal::setup(gix_tempfile::signal::handler::Mode::DeleteTempfilesOnTermination);

for tid in 0..suspected_dashmap_block_size {
std::thread::spawn({
Expand Down Expand Up @@ -83,15 +89,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
signal_hook::low_level::raise(signal_hook::consts::SIGINT)?;
signal_raised.fetch_add(1, Ordering::SeqCst);
}
}

fn tempfile_for_thread_or_panic(tid: i32, tmp: &Path, count: &AtomicUsize) -> Handle<Writable> {
let res = gix_tempfile::writable_at(
tmp.join(format!("thread-{tid}")),
ContainingDirectory::Exists,
AutoRemove::Tempfile,
)
.unwrap();
count.fetch_add(1, Ordering::SeqCst);
res
fn tempfile_for_thread_or_panic(tid: i32, tmp: &Path, count: &AtomicUsize) -> Handle<Writable> {
let res = gix_tempfile::writable_at(
tmp.join(format!("thread-{tid}")),
ContainingDirectory::Exists,
AutoRemove::Tempfile,
)
.unwrap();
count.fetch_add(1, Ordering::SeqCst);
res
}
}
3 changes: 2 additions & 1 deletion gix-tempfile/src/forksafe.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{io::Write, path::Path};
use std::path::Path;

use tempfile::{NamedTempFile, TempPath};

Expand Down Expand Up @@ -88,6 +88,7 @@ impl ForksafeTempfile {
}

pub fn drop_without_deallocation(self) {
use std::io::Write;
let temppath = match self.inner {
TempfileOrTemppath::Tempfile(file) => {
let (mut file, temppath) = file.into_parts();
Expand Down
34 changes: 17 additions & 17 deletions gix-tempfile/src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{io, path::Path};

use tempfile::{NamedTempFile, TempPath};

use crate::{AutoRemove, ContainingDirectory, ForksafeTempfile, Handle, NEXT_MAP_INDEX, REGISTER};
use crate::{AutoRemove, ContainingDirectory, ForksafeTempfile, Handle, NEXT_MAP_INDEX, REGISTRY};

/// Marker to signal the Registration is an open file able to be written to.
#[derive(Debug)]
Expand Down Expand Up @@ -45,7 +45,7 @@ impl Handle<()> {
ForksafeTempfile::new(builder.rand_bytes(0).tempfile_in(parent_dir)?, cleanup, mode)
};
let id = NEXT_MAP_INDEX.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
expect_none(REGISTER.insert(id, Some(tempfile)));
expect_none(REGISTRY.insert(id, Some(tempfile)));
Ok(id)
}

Expand All @@ -57,7 +57,7 @@ impl Handle<()> {
) -> io::Result<usize> {
let containing_directory = directory.resolve(containing_directory.as_ref())?;
let id = NEXT_MAP_INDEX.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
expect_none(REGISTER.insert(
expect_none(REGISTRY.insert(
id,
Some(ForksafeTempfile::new(
NamedTempFile::new_in(containing_directory)?,
Expand Down Expand Up @@ -86,7 +86,7 @@ impl Handle<Closed> {
///
/// It's a theoretical possibility that the file isn't present anymore if signals interfere, hence the `Option`
pub fn take(self) -> Option<TempPath> {
let res = REGISTER.remove(&self.id);
let res = REGISTRY.remove(&self.id);
std::mem::forget(self);
res.and_then(|(_k, v)| v.map(|v| v.into_temppath()))
}
Expand Down Expand Up @@ -123,7 +123,7 @@ impl Handle<Writable> {
///
/// It's a theoretical possibility that the file isn't present anymore if signals interfere, hence the `Option`
pub fn take(self) -> Option<NamedTempFile> {
let res = REGISTER.remove(&self.id);
let res = REGISTRY.remove(&self.id);
std::mem::forget(self);
res.and_then(|(_k, v)| v.map(|v| v.into_tempfile().expect("correct runtime typing")))
}
Expand All @@ -134,17 +134,17 @@ impl Handle<Writable> {
/// it right after to perform more updates of this kind in other tempfiles. When all succeed, they can be renamed one after
/// another.
pub fn close(self) -> std::io::Result<Handle<Closed>> {
match REGISTER.remove(&self.id) {
match REGISTRY.remove(&self.id) {
Some((id, Some(t))) => {
std::mem::forget(self);
expect_none(REGISTER.insert(id, Some(t.close())));
expect_none(REGISTRY.insert(id, Some(t.close())));
Ok(Handle::<Closed> {
id,
_marker: Default::default(),
})
}
None | Some((_, None)) => Err(std::io::Error::new(
std::io::ErrorKind::Interrupted,
std::io::ErrorKind::NotFound,
format!("The tempfile with id {} wasn't available anymore", self.id),
)),
}
Expand All @@ -162,14 +162,14 @@ impl Handle<Writable> {
/// The caller must assure that the signal handler for cleanup will be followed by an abort call so that
/// this code won't run again on a removed instance. An error will occur otherwise.
pub fn with_mut<T>(&mut self, once: impl FnOnce(&mut NamedTempFile) -> T) -> std::io::Result<T> {
match REGISTER.remove(&self.id) {
match REGISTRY.remove(&self.id) {
Some((id, Some(mut t))) => {
let res = once(t.as_mut_tempfile().expect("correct runtime typing"));
expect_none(REGISTER.insert(id, Some(t)));
expect_none(REGISTRY.insert(id, Some(t)));
Ok(res)
}
None | Some((_, None)) => Err(std::io::Error::new(
std::io::ErrorKind::Interrupted,
std::io::ErrorKind::NotFound,
format!("The tempfile with id {} wasn't available anymore", self.id),
)),
}
Expand Down Expand Up @@ -210,7 +210,7 @@ pub mod persist {

use crate::{
handle::{expect_none, Closed, Writable},
Handle, REGISTER,
Handle, REGISTRY,
};

mod error {
Expand Down Expand Up @@ -247,7 +247,7 @@ pub mod persist {
/// Note that it might not exist anymore if an interrupt handler managed to steal it and allowed the program to return to
/// its normal flow.
pub fn persist(self, path: impl AsRef<Path>) -> Result<Option<std::fs::File>, Error<Writable>> {
let res = REGISTER.remove(&self.id);
let res = REGISTRY.remove(&self.id);

match res.and_then(|(_k, v)| v.map(|v| v.persist(path))) {
Some(Ok(Some(file))) => {
Expand All @@ -259,7 +259,7 @@ pub mod persist {
Ok(None)
}
Some(Err((err, tempfile))) => {
expect_none(REGISTER.insert(self.id, Some(tempfile)));
expect_none(REGISTRY.insert(self.id, Some(tempfile)));
Err(Error::<Writable> {
error: err,
handle: self,
Expand All @@ -274,15 +274,15 @@ pub mod persist {
/// Persist this tempfile to replace the file at the given `path` if necessary, in a way that recovers the original instance
/// on error.
pub fn persist(self, path: impl AsRef<Path>) -> Result<(), Error<Closed>> {
let res = REGISTER.remove(&self.id);
let res = REGISTRY.remove(&self.id);

match res.and_then(|(_k, v)| v.map(|v| v.persist(path))) {
None | Some(Ok(None)) => {
std::mem::forget(self);
Ok(())
}
Some(Err((err, tempfile))) => {
expect_none(REGISTER.insert(self.id, Some(tempfile)));
expect_none(REGISTRY.insert(self.id, Some(tempfile)));
Err(Error::<Closed> {
error: err,
handle: self,
Expand Down Expand Up @@ -312,7 +312,7 @@ fn expect_none<T>(v: Option<T>) {

impl<T: std::fmt::Debug> Drop for Handle<T> {
fn drop(&mut self) {
if let Some((_id, Some(tempfile))) = REGISTER.remove(&self.id) {
if let Some((_id, Some(tempfile))) = REGISTRY.remove(&self.id) {
tempfile.drop_impl();
}
}
Expand Down
Loading