Skip to content

Commit 007ccdb

Browse files
Move prefetch dcf state file out of templateflow home since that leads templateflow to fall apart in an empty directory
1 parent 4ad8873 commit 007ccdb

7 files changed

Lines changed: 425 additions & 26 deletions

File tree

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,8 @@ local/
5454
docs
5555

5656
.DS_Store
57-
.vscode/
57+
.vscode/
58+
59+
# Python cache
60+
__pycache__/
61+
*.pyc

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package: BrainGnomes
22
Title: An R package for preprocessing and analyzing fMRI data
33
Version: 0.8-1
4-
Date: 2026-03-01
4+
Date: 2026-03-05
55
Authors@R:
66
person("Michael", "Hallquist", email="michael.hallquist@gmail.com", role = c("aut", "cre"))
77
Description: This R package handles preprocessing of fMRI data on HPC clusters.

NEWS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# BrainGnomes 0.8-1
22

3-
Released 2026-03-01
3+
Released 2026-03-05
44

55
* Improve CLI interface to support --help or BrainGnomes <command> help
66
* Add CLI status command to get project status from command line
@@ -9,6 +9,7 @@ Released 2026-03-01
99
* Refactor prefetch to fall back to no desc field if desc:brain fails
1010
* Harden prefetch caching and validation checks so that later failures invalidate skip logic
1111
* Make prefetch state query-specific so that an exact snapshot of templateflow files is retained
12+
* Move prefetch state files out of `templateflow_home` and into hashed project log paths; legacy state files in `templateflow_home` are now migrated and removed to avoid poisoning TemplateFlow standard-space discovery.
1213
* Harden check on flywheel location to accommodate missing fw command.
1314

1415
# BrainGnomes 0.8

R/run_project.R

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,28 @@ normalize_prefetch_spaces <- function(spaces) {
602602
sort(unique(spaces))
603603
}
604604

605-
get_prefetch_state_file <- function(templateflow_home) {
605+
prefetch_state_cache_hash <- function(templateflow_home) {
606+
normalized <- normalizePath(templateflow_home, winslash = "/", mustWork = FALSE)
607+
tmp <- tempfile("prefetch_state_hash_")
608+
on.exit(unlink(tmp), add = TRUE)
609+
writeLines(normalized, tmp, useBytes = TRUE)
610+
unname(tools::md5sum(tmp))
611+
}
612+
613+
get_prefetch_state_file <- function(log_directory, templateflow_home) {
614+
if (!checkmate::test_string(log_directory) || !nzchar(log_directory)) {
615+
stop("log_directory must be a non-empty string when resolving prefetch state file.", call. = FALSE)
616+
}
617+
if (!checkmate::test_string(templateflow_home) || !nzchar(templateflow_home)) {
618+
stop("templateflow_home must be a non-empty string when resolving prefetch state file.", call. = FALSE)
619+
}
620+
621+
log_directory <- normalizePath(log_directory, winslash = "/", mustWork = FALSE)
622+
cache_hash <- prefetch_state_cache_hash(templateflow_home)
623+
file.path(log_directory, sprintf(".braingnomes_prefetch_state_%s.dcf", cache_hash))
624+
}
625+
626+
get_legacy_prefetch_state_file <- function(templateflow_home) {
606627
file.path(templateflow_home, ".braingnomes_prefetch_state.dcf")
607628
}
608629

@@ -639,6 +660,87 @@ read_prefetch_state <- function(state_file) {
639660
state
640661
}
641662

663+
prefetch_templateflow_cache_initialized <- function(templateflow_home) {
664+
if (!checkmate::test_directory_exists(templateflow_home)) return(FALSE)
665+
entries <- list.files(templateflow_home, all.files = FALSE, no.. = TRUE, full.names = FALSE)
666+
any(grepl("^tpl-", entries))
667+
}
668+
669+
copy_prefetch_state_file <- function(from, to) {
670+
if (!checkmate::test_file_exists(from)) {
671+
stop(glue::glue("Cannot copy prefetch state; source file does not exist: {from}"), call. = FALSE)
672+
}
673+
674+
target_dir <- dirname(to)
675+
if (!dir.exists(target_dir)) dir.create(target_dir, recursive = TRUE, showWarnings = FALSE)
676+
tmp <- paste0(to, ".tmp.", Sys.getpid())
677+
678+
copied <- isTRUE(file.copy(from, tmp, overwrite = TRUE, copy.mode = TRUE, copy.date = TRUE))
679+
if (!copied || !file.exists(tmp)) {
680+
stop(glue::glue("Failed to stage copied prefetch state file: {tmp}"), call. = FALSE)
681+
}
682+
683+
renamed <- isTRUE(file.rename(tmp, to))
684+
if (!renamed) {
685+
copied_final <- isTRUE(file.copy(tmp, to, overwrite = TRUE, copy.mode = TRUE, copy.date = TRUE))
686+
suppressWarnings(unlink(tmp))
687+
if (!copied_final || !file.exists(to)) {
688+
stop(glue::glue("Failed to finalize copied prefetch state file: {to}"), call. = FALSE)
689+
}
690+
}
691+
692+
invisible(to)
693+
}
694+
695+
migrate_prefetch_state_file <- function(state_file, legacy_state_file, templateflow_home) {
696+
if (!checkmate::test_file_exists(legacy_state_file)) return(invisible(NULL))
697+
698+
if (!checkmate::test_file_exists(state_file)) {
699+
copy_prefetch_state_file(legacy_state_file, state_file)
700+
message(glue::glue(
701+
"Migrated legacy TemplateFlow prefetch state from {legacy_state_file} to {state_file}."
702+
))
703+
} else {
704+
message(glue::glue(
705+
"Found legacy TemplateFlow prefetch state at {legacy_state_file}; using logs-based state file at {state_file}."
706+
))
707+
}
708+
709+
removed <- suppressWarnings(unlink(legacy_state_file))
710+
if (!checkmate::test_file_exists(legacy_state_file) || identical(removed, 0L)) {
711+
message(glue::glue(
712+
"Removed legacy TemplateFlow prefetch state file from templateflow_home: {legacy_state_file}"
713+
))
714+
return(invisible(NULL))
715+
}
716+
717+
cache_initialized <- prefetch_templateflow_cache_initialized(templateflow_home)
718+
base_msg <- glue::glue(
719+
"Unable to remove legacy TemplateFlow prefetch state file at {legacy_state_file}. "
720+
)
721+
guidance <- "Remove it manually to avoid TemplateFlow standard-space resolution failures."
722+
723+
if (!cache_initialized) {
724+
stop(
725+
paste0(
726+
base_msg,
727+
"TemplateFlow cache appears uninitialized (no tpl-* directories). ",
728+
guidance
729+
),
730+
call. = FALSE
731+
)
732+
}
733+
734+
warning(
735+
paste0(
736+
base_msg,
737+
"Continuing because TemplateFlow cache appears initialized (tpl-* directories detected). ",
738+
guidance
739+
),
740+
call. = FALSE
741+
)
742+
}
743+
642744
find_container_runtime <- function() {
643745
runtimes <- Sys.which(c("singularity", "apptainer"))
644746
available <- unname(runtimes[nzchar(runtimes)])
@@ -808,6 +910,8 @@ submit_prefetch_templates <- function(scfg, steps, sequence_id = NULL) {
808910

809911
tf_home <- normalizePath(tf_home, mustWork = FALSE)
810912
if (!dir.exists(tf_home)) dir.create(tf_home, showWarnings = FALSE, recursive = TRUE)
913+
prefetch_state_file <- get_prefetch_state_file(scfg$metadata$log_directory, tf_home)
914+
legacy_prefetch_state_file <- get_legacy_prefetch_state_file(tf_home)
811915

812916
spaces <- scfg$fmriprep$output_spaces
813917
if (isTRUE(steps["aroma"]) && (is.null(spaces) || !grepl("MNI152NLin6Asym:res-2", spaces, fixed = TRUE))) {
@@ -832,14 +936,19 @@ submit_prefetch_templates <- function(scfg, steps, sequence_id = NULL) {
832936
# preflight permission checks for project-level paths
833937
pf_issues <- c(
834938
check_write_target(scfg$metadata$log_directory, "log directory"),
835-
check_write_target(tf_home, "templateflow_home directory")
939+
check_write_target(tf_home, "templateflow_home directory"),
940+
check_write_target(prefetch_state_file, "prefetch state file")
836941
)
837942
if (length(pf_issues) > 0L) {
838943
stop("Preflight permission check failed for prefetch_templates:\n",
839944
paste(paste0(" - ", pf_issues), collapse = "\n"), call. = FALSE)
840945
}
841946

842-
prefetch_state_file <- get_prefetch_state_file(tf_home)
947+
migrate_prefetch_state_file(
948+
state_file = prefetch_state_file,
949+
legacy_state_file = legacy_prefetch_state_file,
950+
templateflow_home = tf_home
951+
)
843952
prefetch_state <- read_prefetch_state(prefetch_state_file)
844953
prefetch_plan <- resolve_prefetch_query_plan(
845954
container_path = container_path,

inst/hpc_scripts/prefetch_templates.pbs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,31 @@ prefetch_script=$(abspath "$prefetch_script")
5959
templateflow_home=$(abspath "$templateflow_home")
6060
prefetch_script_dir=$(dirname "$prefetch_script")
6161

62+
# Create temp files for summary/manifest OUTSIDE templateflow_home to avoid
63+
# poisoning the TemplateFlow cache. When templateflow_home is empty, any file
64+
# (including hidden dot-files) causes templateflow's _init_cache() to skip
65+
# skeleton extraction, which makes api.get() return empty results.
66+
prefetch_tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/braingnomes_prefetch_${PBS_JOBID}_XXXX")
67+
prefetch_summary_file="$prefetch_tmpdir/.braingnomes_prefetch_summary_${PBS_JOBID}.json"
68+
prefetch_manifest_file="$prefetch_tmpdir/.braingnomes_prefetch_manifest_${PBS_JOBID}.json"
69+
touch "$prefetch_summary_file" "$prefetch_manifest_file"
70+
71+
# Resolve bind paths: templateflow_home, script dir, prefetch temp dir,
72+
# and (if set) the prefetch state file's parent directory so the Python
73+
# script running inside --containall can write the state file.
74+
prefetch_state_dir=""
75+
if [ -n "$prefetch_state_file" ]; then
76+
prefetch_state_dir=$(dirname "$prefetch_state_file")
77+
[ ! -d "$prefetch_state_dir" ] && mkdir -p "$prefetch_state_dir"
78+
fi
79+
6280
bind_args=()
63-
bind_paths=("$templateflow_home" "$prefetch_script_dir")
81+
bind_paths=("$templateflow_home" "$prefetch_script_dir" "$prefetch_tmpdir" "$prefetch_state_dir")
6482
for path in "${bind_paths[@]}"; do
6583
[ -z "$path" ] && continue
6684
bind_args+=("-B" "$path:$path")
6785
done
6886

69-
prefetch_summary_file=$(mktemp "$templateflow_home/.braingnomes_prefetch_summary_${PBS_JOBID}_XXXX.json")
70-
prefetch_manifest_file=$(mktemp "$templateflow_home/.braingnomes_prefetch_manifest_${PBS_JOBID}_XXXX.json")
71-
7287
write_prefetch_failed_state_fallback() {
7388
[ -z "$prefetch_state_file" ] && return 0
7489

@@ -151,5 +166,6 @@ else
151166
fi
152167

153168
rm -f "$prefetch_summary_file" "$prefetch_manifest_file"
169+
[ -n "$prefetch_tmpdir" ] && rm -rf "$prefetch_tmpdir"
154170

155171
exit $cmd_status

inst/hpc_scripts/prefetch_templates.sbatch

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,31 @@ prefetch_script=$(abspath "$prefetch_script")
5959
templateflow_home=$(abspath "$templateflow_home")
6060
prefetch_script_dir=$(dirname "$prefetch_script")
6161

62+
# Create temp files for summary/manifest OUTSIDE templateflow_home to avoid
63+
# poisoning the TemplateFlow cache. When templateflow_home is empty, any file
64+
# (including hidden dot-files) causes templateflow's _init_cache() to skip
65+
# skeleton extraction, which makes api.get() return empty results.
66+
prefetch_tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/braingnomes_prefetch_${SLURM_JOB_ID}_XXXX")
67+
prefetch_summary_file="$prefetch_tmpdir/.braingnomes_prefetch_summary_${SLURM_JOB_ID}.json"
68+
prefetch_manifest_file="$prefetch_tmpdir/.braingnomes_prefetch_manifest_${SLURM_JOB_ID}.json"
69+
touch "$prefetch_summary_file" "$prefetch_manifest_file"
70+
71+
# Resolve bind paths: templateflow_home, script dir, prefetch temp dir,
72+
# and (if set) the prefetch state file's parent directory so the Python
73+
# script running inside --containall can write the state file.
74+
prefetch_state_dir=""
75+
if [ -n "$prefetch_state_file" ]; then
76+
prefetch_state_dir=$(dirname "$prefetch_state_file")
77+
[ ! -d "$prefetch_state_dir" ] && mkdir -p "$prefetch_state_dir"
78+
fi
79+
6280
bind_args=()
63-
bind_paths=("$templateflow_home" "$prefetch_script_dir")
81+
bind_paths=("$templateflow_home" "$prefetch_script_dir" "$prefetch_tmpdir" "$prefetch_state_dir")
6482
for path in "${bind_paths[@]}"; do
6583
[ -z "$path" ] && continue
6684
bind_args+=("-B" "$path:$path")
6785
done
6886

69-
prefetch_summary_file=$(mktemp "$templateflow_home/.braingnomes_prefetch_summary_${SLURM_JOB_ID}_XXXX.json")
70-
prefetch_manifest_file=$(mktemp "$templateflow_home/.braingnomes_prefetch_manifest_${SLURM_JOB_ID}_XXXX.json")
71-
7287
write_prefetch_failed_state_fallback() {
7388
[ -z "$prefetch_state_file" ] && return 0
7489

@@ -151,5 +166,6 @@ else
151166
fi
152167

153168
rm -f "$prefetch_summary_file" "$prefetch_manifest_file"
169+
[ -n "$prefetch_tmpdir" ] && rm -rf "$prefetch_tmpdir"
154170

155171
exit $cmd_status

0 commit comments

Comments
 (0)