Skip to content

Commit 9907fbb

Browse files
committed
Read the individuals archive without extracting it
frequency.py and mutation_overlap.py each extracted chr{N}n.tar.gz into ./chr{N}n before reading it. Every task for a chromosome shares that path -- one per population in each of the two stages, ten of them at five populations -- and tarfile.extractall truncates each target before rewriting it, so a task can read a file another task is midway through replacing. A reader that catches a file mid-rewrite sees a part-written line. frequency.py takes the second field of each line and raises IndexError; mutation_overlap.py splits the whole file and takes the result, so a short read passes silently into its output. Three Q3 runs died this way, at two different tasks, and the fourth passed -- and a directory with five concurrent extractors reproduces it at 4 IndexErrors in 29 reader passes, against none with a single extractor. Both stages now read members straight out of the archive, which removes the shared path and with it the race. It also drops ten redundant 16MB extractions per chromosome, since every one of those tasks was already extracting the whole archive only to overwrite what the others wrote. Verified against the previous implementation on chr7: the text each stage builds per individual, in both its own semantics, is identical for all 1,153 individuals across AFR and EUR, as is the sifted-mutation set that flows downstream. Worker image 1.6.
1 parent 70e92b7 commit 9907fbb

8 files changed

Lines changed: 28 additions & 22 deletions

File tree

.github/workflows/equivalence.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ jobs:
7676
make -C worker-image image
7777
docker build --platform linux/amd64 \
7878
-f engines/nextflow/worker-nf.Dockerfile \
79-
-t 1000genome-worker-nf:1.5 engines/nextflow/
79+
-t 1000genome-worker-nf:1.6 engines/nextflow/
8080
8181
- name: Install Nextflow
8282
run: |

engines/hyperflow/harness/docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ services:
3232
- REDIS_URL=redis://redis:6379
3333

3434
# HyperFlow configuration
35-
- HF_VAR_WORKER_CONTAINER=${HF_VAR_WORKER_CONTAINER:-hyperflowwms/1000genome-worker:1.5-je1.4.2}
35+
- HF_VAR_WORKER_CONTAINER=${HF_VAR_WORKER_CONTAINER:-hyperflowwms/1000genome-worker:1.6-je1.4.2}
3636
- HF_VAR_WORK_DIR=${WORKFLOW_DIR}
3737
- HF_VAR_HFLOW_IN_CONTAINER=true
3838
- HF_VAR_function=redisCommand

engines/nextflow/worker-nf.Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# Nextflow does not use. Both engines therefore run the same scripts from the
77
# same layer, so "the science is identical" is a property of the image graph
88
# rather than a claim about two copies staying in sync.
9-
FROM hyperflowwms/1000genome-worker-base:1.5
9+
FROM hyperflowwms/1000genome-worker-base:1.6
1010

1111
# Nextflow requires bash in the container; the base is Alpine, which ships ash.
1212
RUN apk add --no-cache bash

worker-base-image/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
REPO_NAME = 1000genome-worker-base
22
PREFIX = hyperflowwms
3-
VERSION = 1.5
3+
VERSION = 1.6
44
TAG = $(VERSION)
55

66
all: push

worker-base-image/scripts/frequency.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,18 @@
5555
font = {'family': 'serif', 'size': 14}
5656
plt.rc('font', **font)
5757

58-
# untar input data
58+
# Input data, read straight out of the archive. Extracting it would put every
59+
# task for this chromosome into one ./chr{N}n directory -- one task per
60+
# population here and another in mutation_overlap, ten of them at five
61+
# populations -- where each extraction truncates and rewrites files the others
62+
# are reading. A reader that catches a file mid-rewrite sees a part-written
63+
# line, which fails here on the second field and passes silently into
64+
# mutation_overlap's results. See archive.py.
5965
import tarfile
6066

61-
tar = tarfile.open(chrom + 'n.tar.gz')
62-
tar.extractall(path='./' + chrom + 'n')
63-
tar.close()
67+
import archive
68+
69+
individuals = dict(archive.read_archive(chrom + 'n.tar.gz'))
6470

6571

6672
class ReadData:
@@ -108,10 +114,8 @@ def read_individuals(self, ids, rs_numbers):
108114
tic = time.perf_counter()
109115
mutation_index_array = []
110116
for name in ids:
111-
filename = data_dir + chrom + 'n/' + chrom + '.' + name
112-
f = open(filename, 'r')
113117
text = []
114-
for item in f:
118+
for item in individuals[chrom + '.' + name]:
115119
item = item.split()
116120
text.append(item[1])
117121
sifted_mutations = list(set(rs_numbers).intersection(text))

worker-base-image/scripts/mutation_overlap.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,17 @@
5656
plt.rc('font', **font)
5757

5858

59-
# untar input data
59+
# Input data, read straight out of the archive. Extracting it would put every
60+
# task for this chromosome into one ./chr{N}n directory -- one task per
61+
# population here and another in frequency, ten of them at five populations --
62+
# where each extraction truncates and rewrites files the others are reading. A
63+
# reader that catches a file mid-rewrite sees a part-written line, which passes
64+
# silently into the results here and fails outright in frequency. See archive.py.
6065
import tarfile
61-
tar = tarfile.open(chrom + 'n.tar.gz')
62-
tar.extractall(path='./' + chrom + 'n')
63-
tar.close()
66+
67+
import archive
68+
69+
individuals = dict(archive.read_archive(chrom + 'n.tar.gz'))
6470

6571
tic = time.perf_counter()
6672

@@ -115,11 +121,7 @@ def read_individuals(self, ids, rs_numbers) :
115121
total_mutations={}
116122
total_mutations_list =[]
117123
for name in ids :
118-
filename = data_dir + chrom + 'n/' + chrom + '.' + name
119-
f = open(filename, 'r')
120-
text = f.read()
121-
f.close()
122-
text = text.split()
124+
text = ''.join(individuals[chrom + '.' + name]).split()
123125
sifted_mutations = list(set(rs_numbers).intersection(text))
124126
mutation_index_array.append(sifted_mutations)
125127
total_mutations[name]= len(sifted_mutations)

worker-image/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM hyperflowwms/1000genome-worker-base:1.5
1+
FROM hyperflowwms/1000genome-worker-base:1.6
22

33
# Version of the job executor should be passed via docker build, e.g.:
44
# docker build --build-arg hf_job_executor_version="1.3.4"

worker-image/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
REPO_NAME = 1000genome-worker
22
PREFIX = hyperflowwms
3-
VERSION = 1.5
3+
VERSION = 1.6
44
# ?= so CI and Renovate can override the version from the environment
55
HF_JOB_EXECUTOR_VERSION ?= 1.4.2
66
TAG = $(VERSION)-je$(HF_JOB_EXECUTOR_VERSION)

0 commit comments

Comments
 (0)