toil 6.1.0a1__py3-none-any.whl → 8.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- toil/__init__.py +122 -315
- toil/batchSystems/__init__.py +1 -0
- toil/batchSystems/abstractBatchSystem.py +173 -89
- toil/batchSystems/abstractGridEngineBatchSystem.py +272 -148
- toil/batchSystems/awsBatch.py +244 -135
- toil/batchSystems/cleanup_support.py +26 -16
- toil/batchSystems/contained_executor.py +31 -28
- toil/batchSystems/gridengine.py +86 -50
- toil/batchSystems/htcondor.py +166 -89
- toil/batchSystems/kubernetes.py +632 -382
- toil/batchSystems/local_support.py +20 -15
- toil/batchSystems/lsf.py +134 -81
- toil/batchSystems/lsfHelper.py +13 -11
- toil/batchSystems/mesos/__init__.py +41 -29
- toil/batchSystems/mesos/batchSystem.py +290 -151
- toil/batchSystems/mesos/executor.py +79 -50
- toil/batchSystems/mesos/test/__init__.py +31 -23
- toil/batchSystems/options.py +46 -28
- toil/batchSystems/registry.py +53 -19
- toil/batchSystems/singleMachine.py +296 -125
- toil/batchSystems/slurm.py +603 -138
- toil/batchSystems/torque.py +47 -33
- toil/bus.py +186 -76
- toil/common.py +664 -368
- toil/cwl/__init__.py +1 -1
- toil/cwl/cwltoil.py +1136 -483
- toil/cwl/utils.py +17 -22
- toil/deferred.py +63 -42
- toil/exceptions.py +5 -3
- toil/fileStores/__init__.py +5 -5
- toil/fileStores/abstractFileStore.py +140 -60
- toil/fileStores/cachingFileStore.py +717 -269
- toil/fileStores/nonCachingFileStore.py +116 -87
- toil/job.py +1225 -368
- toil/jobStores/abstractJobStore.py +416 -266
- toil/jobStores/aws/jobStore.py +863 -477
- toil/jobStores/aws/utils.py +201 -120
- toil/jobStores/conftest.py +3 -2
- toil/jobStores/fileJobStore.py +292 -154
- toil/jobStores/googleJobStore.py +140 -74
- toil/jobStores/utils.py +36 -15
- toil/leader.py +668 -272
- toil/lib/accelerators.py +115 -18
- toil/lib/aws/__init__.py +74 -31
- toil/lib/aws/ami.py +122 -87
- toil/lib/aws/iam.py +284 -108
- toil/lib/aws/s3.py +31 -0
- toil/lib/aws/session.py +214 -39
- toil/lib/aws/utils.py +287 -231
- toil/lib/bioio.py +13 -5
- toil/lib/compatibility.py +11 -6
- toil/lib/conversions.py +104 -47
- toil/lib/docker.py +131 -103
- toil/lib/ec2.py +361 -199
- toil/lib/ec2nodes.py +174 -106
- toil/lib/encryption/_dummy.py +5 -3
- toil/lib/encryption/_nacl.py +10 -6
- toil/lib/encryption/conftest.py +1 -0
- toil/lib/exceptions.py +26 -7
- toil/lib/expando.py +5 -3
- toil/lib/ftp_utils.py +217 -0
- toil/lib/generatedEC2Lists.py +127 -19
- toil/lib/humanize.py +6 -2
- toil/lib/integration.py +341 -0
- toil/lib/io.py +141 -15
- toil/lib/iterables.py +4 -2
- toil/lib/memoize.py +12 -8
- toil/lib/misc.py +66 -21
- toil/lib/objects.py +2 -2
- toil/lib/resources.py +68 -15
- toil/lib/retry.py +126 -81
- toil/lib/threading.py +299 -82
- toil/lib/throttle.py +16 -15
- toil/options/common.py +843 -409
- toil/options/cwl.py +175 -90
- toil/options/runner.py +50 -0
- toil/options/wdl.py +73 -17
- toil/provisioners/__init__.py +117 -46
- toil/provisioners/abstractProvisioner.py +332 -157
- toil/provisioners/aws/__init__.py +70 -33
- toil/provisioners/aws/awsProvisioner.py +1145 -715
- toil/provisioners/clusterScaler.py +541 -279
- toil/provisioners/gceProvisioner.py +282 -179
- toil/provisioners/node.py +155 -79
- toil/realtimeLogger.py +34 -22
- toil/resource.py +137 -75
- toil/server/app.py +128 -62
- toil/server/celery_app.py +3 -1
- toil/server/cli/wes_cwl_runner.py +82 -53
- toil/server/utils.py +54 -28
- toil/server/wes/abstract_backend.py +64 -26
- toil/server/wes/amazon_wes_utils.py +21 -15
- toil/server/wes/tasks.py +121 -63
- toil/server/wes/toil_backend.py +142 -107
- toil/server/wsgi_app.py +4 -3
- toil/serviceManager.py +58 -22
- toil/statsAndLogging.py +224 -70
- toil/test/__init__.py +282 -183
- toil/test/batchSystems/batchSystemTest.py +460 -210
- toil/test/batchSystems/batch_system_plugin_test.py +90 -0
- toil/test/batchSystems/test_gridengine.py +173 -0
- toil/test/batchSystems/test_lsf_helper.py +67 -58
- toil/test/batchSystems/test_slurm.py +110 -49
- toil/test/cactus/__init__.py +0 -0
- toil/test/cactus/test_cactus_integration.py +56 -0
- toil/test/cwl/cwlTest.py +496 -287
- toil/test/cwl/measure_default_memory.cwl +12 -0
- toil/test/cwl/not_run_required_input.cwl +29 -0
- toil/test/cwl/scatter_duplicate_outputs.cwl +40 -0
- toil/test/cwl/seqtk_seq.cwl +1 -1
- toil/test/docs/scriptsTest.py +69 -46
- toil/test/jobStores/jobStoreTest.py +427 -264
- toil/test/lib/aws/test_iam.py +118 -50
- toil/test/lib/aws/test_s3.py +16 -9
- toil/test/lib/aws/test_utils.py +5 -6
- toil/test/lib/dockerTest.py +118 -141
- toil/test/lib/test_conversions.py +113 -115
- toil/test/lib/test_ec2.py +58 -50
- toil/test/lib/test_integration.py +104 -0
- toil/test/lib/test_misc.py +12 -5
- toil/test/mesos/MesosDataStructuresTest.py +23 -10
- toil/test/mesos/helloWorld.py +7 -6
- toil/test/mesos/stress.py +25 -20
- toil/test/options/__init__.py +13 -0
- toil/test/options/options.py +42 -0
- toil/test/provisioners/aws/awsProvisionerTest.py +320 -150
- toil/test/provisioners/clusterScalerTest.py +440 -250
- toil/test/provisioners/clusterTest.py +166 -44
- toil/test/provisioners/gceProvisionerTest.py +174 -100
- toil/test/provisioners/provisionerTest.py +25 -13
- toil/test/provisioners/restartScript.py +5 -4
- toil/test/server/serverTest.py +188 -141
- toil/test/sort/restart_sort.py +137 -68
- toil/test/sort/sort.py +134 -66
- toil/test/sort/sortTest.py +91 -49
- toil/test/src/autoDeploymentTest.py +141 -101
- toil/test/src/busTest.py +20 -18
- toil/test/src/checkpointTest.py +8 -2
- toil/test/src/deferredFunctionTest.py +49 -35
- toil/test/src/dockerCheckTest.py +32 -24
- toil/test/src/environmentTest.py +135 -0
- toil/test/src/fileStoreTest.py +539 -272
- toil/test/src/helloWorldTest.py +7 -4
- toil/test/src/importExportFileTest.py +61 -31
- toil/test/src/jobDescriptionTest.py +46 -21
- toil/test/src/jobEncapsulationTest.py +2 -0
- toil/test/src/jobFileStoreTest.py +74 -50
- toil/test/src/jobServiceTest.py +187 -73
- toil/test/src/jobTest.py +121 -71
- toil/test/src/miscTests.py +19 -18
- toil/test/src/promisedRequirementTest.py +82 -36
- toil/test/src/promisesTest.py +7 -6
- toil/test/src/realtimeLoggerTest.py +10 -6
- toil/test/src/regularLogTest.py +71 -37
- toil/test/src/resourceTest.py +80 -49
- toil/test/src/restartDAGTest.py +36 -22
- toil/test/src/resumabilityTest.py +9 -2
- toil/test/src/retainTempDirTest.py +45 -14
- toil/test/src/systemTest.py +12 -8
- toil/test/src/threadingTest.py +44 -25
- toil/test/src/toilContextManagerTest.py +10 -7
- toil/test/src/userDefinedJobArgTypeTest.py +8 -5
- toil/test/src/workerTest.py +73 -23
- toil/test/utils/toilDebugTest.py +103 -33
- toil/test/utils/toilKillTest.py +4 -5
- toil/test/utils/utilsTest.py +245 -106
- toil/test/wdl/wdltoil_test.py +818 -149
- toil/test/wdl/wdltoil_test_kubernetes.py +91 -0
- toil/toilState.py +120 -35
- toil/utils/toilConfig.py +13 -4
- toil/utils/toilDebugFile.py +44 -27
- toil/utils/toilDebugJob.py +214 -27
- toil/utils/toilDestroyCluster.py +11 -6
- toil/utils/toilKill.py +8 -3
- toil/utils/toilLaunchCluster.py +256 -140
- toil/utils/toilMain.py +37 -16
- toil/utils/toilRsyncCluster.py +32 -14
- toil/utils/toilSshCluster.py +49 -22
- toil/utils/toilStats.py +356 -273
- toil/utils/toilStatus.py +292 -139
- toil/utils/toilUpdateEC2Instances.py +3 -1
- toil/version.py +12 -12
- toil/wdl/utils.py +5 -5
- toil/wdl/wdltoil.py +3913 -1033
- toil/worker.py +367 -184
- {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/LICENSE +25 -0
- toil-8.0.0.dist-info/METADATA +173 -0
- toil-8.0.0.dist-info/RECORD +253 -0
- {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/WHEEL +1 -1
- toil-6.1.0a1.dist-info/METADATA +0 -125
- toil-6.1.0a1.dist-info/RECORD +0 -237
- {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/entry_points.txt +0 -0
- {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/top_level.txt +0 -0
toil/cwl/utils.py
CHANGED
|
@@ -16,21 +16,11 @@
|
|
|
16
16
|
|
|
17
17
|
import logging
|
|
18
18
|
import os
|
|
19
|
-
from pathlib import PurePosixPath
|
|
20
19
|
import posixpath
|
|
21
20
|
import stat
|
|
22
|
-
from
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Dict,
|
|
26
|
-
Iterable,
|
|
27
|
-
List,
|
|
28
|
-
MutableMapping,
|
|
29
|
-
MutableSequence,
|
|
30
|
-
Type,
|
|
31
|
-
TypeVar,
|
|
32
|
-
Union,
|
|
33
|
-
)
|
|
21
|
+
from collections.abc import Iterable, MutableMapping, MutableSequence
|
|
22
|
+
from pathlib import PurePosixPath
|
|
23
|
+
from typing import Any, Callable, TypeVar, Union
|
|
34
24
|
|
|
35
25
|
from toil.fileStores import FileID
|
|
36
26
|
from toil.fileStores.abstractFileStore import AbstractFileStore
|
|
@@ -55,7 +45,7 @@ try:
|
|
|
55
45
|
import cwltool.errors
|
|
56
46
|
|
|
57
47
|
CWL_UNSUPPORTED_REQUIREMENT_EXCEPTION: Union[
|
|
58
|
-
|
|
48
|
+
type[cwltool.errors.UnsupportedRequirement], type[CWLUnsupportedException]
|
|
59
49
|
] = cwltool.errors.UnsupportedRequirement
|
|
60
50
|
except ImportError:
|
|
61
51
|
CWL_UNSUPPORTED_REQUIREMENT_EXCEPTION = CWLUnsupportedException
|
|
@@ -92,8 +82,8 @@ def visit_cwl_class_and_reduce(
|
|
|
92
82
|
rec: Any,
|
|
93
83
|
classes: Iterable[str],
|
|
94
84
|
op_down: Callable[[Any], DownReturnType],
|
|
95
|
-
op_up: Callable[[Any, DownReturnType,
|
|
96
|
-
) ->
|
|
85
|
+
op_up: Callable[[Any, DownReturnType, list[UpReturnType]], UpReturnType],
|
|
86
|
+
) -> list[UpReturnType]:
|
|
97
87
|
"""
|
|
98
88
|
Apply the given operations to all CWL objects with the given named CWL class.
|
|
99
89
|
|
|
@@ -130,9 +120,12 @@ def visit_cwl_class_and_reduce(
|
|
|
130
120
|
return results
|
|
131
121
|
|
|
132
122
|
|
|
133
|
-
DirectoryStructure =
|
|
123
|
+
DirectoryStructure = dict[str, Union[str, "DirectoryStructure"]]
|
|
134
124
|
|
|
135
|
-
|
|
125
|
+
|
|
126
|
+
def get_from_structure(
|
|
127
|
+
dir_dict: DirectoryStructure, path: str
|
|
128
|
+
) -> Union[str, DirectoryStructure, None]:
|
|
136
129
|
"""
|
|
137
130
|
Given a relative path, follow it in the given directory structure.
|
|
138
131
|
|
|
@@ -144,7 +137,7 @@ def get_from_structure(dir_dict: DirectoryStructure, path: str) -> Union[str, Di
|
|
|
144
137
|
parts = PurePosixPath(posixpath.normpath(path)).parts
|
|
145
138
|
if len(parts) == 0:
|
|
146
139
|
return dir_dict
|
|
147
|
-
if parts[0] in (
|
|
140
|
+
if parts[0] in ("..", "/"):
|
|
148
141
|
raise RuntimeError(f"Path {path} not resolvable in virtual directory")
|
|
149
142
|
found: Union[str, DirectoryStructure] = dir_dict
|
|
150
143
|
for part in parts:
|
|
@@ -161,8 +154,8 @@ def get_from_structure(dir_dict: DirectoryStructure, path: str) -> Union[str, Di
|
|
|
161
154
|
|
|
162
155
|
def download_structure(
|
|
163
156
|
file_store: AbstractFileStore,
|
|
164
|
-
index:
|
|
165
|
-
existing:
|
|
157
|
+
index: dict[str, str],
|
|
158
|
+
existing: dict[str, str],
|
|
166
159
|
dir_dict: DirectoryStructure,
|
|
167
160
|
into_dir: str,
|
|
168
161
|
) -> None:
|
|
@@ -215,7 +208,9 @@ def download_structure(
|
|
|
215
208
|
)
|
|
216
209
|
else:
|
|
217
210
|
# We need to download from some other kind of URL.
|
|
218
|
-
size, executable = AbstractJobStore.read_from_url(
|
|
211
|
+
size, executable = AbstractJobStore.read_from_url(
|
|
212
|
+
value, open(dest_path, "wb")
|
|
213
|
+
)
|
|
219
214
|
if executable:
|
|
220
215
|
# Make the written file executable
|
|
221
216
|
os.chmod(dest_path, os.stat(dest_path).st_mode | stat.S_IXUSR)
|
toil/deferred.py
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
import
|
|
14
|
+
import errno
|
|
15
15
|
import logging
|
|
16
16
|
import os
|
|
17
17
|
import tempfile
|
|
@@ -21,13 +21,16 @@ from contextlib import contextmanager
|
|
|
21
21
|
import dill
|
|
22
22
|
|
|
23
23
|
from toil.lib.io import robust_rmtree
|
|
24
|
+
from toil.lib.threading import safe_lock, safe_unlock_and_close
|
|
24
25
|
from toil.realtimeLogger import RealtimeLogger
|
|
25
26
|
from toil.resource import ModuleDescriptor
|
|
26
27
|
|
|
27
28
|
logger = logging.getLogger(__name__)
|
|
28
29
|
|
|
29
30
|
|
|
30
|
-
class DeferredFunction(
|
|
31
|
+
class DeferredFunction(
|
|
32
|
+
namedtuple("DeferredFunction", "function args kwargs name module")
|
|
33
|
+
):
|
|
31
34
|
"""
|
|
32
35
|
>>> from collections import defaultdict
|
|
33
36
|
>>> df = DeferredFunction.create(defaultdict, None, {'x':1}, y=2)
|
|
@@ -36,6 +39,7 @@ class DeferredFunction(namedtuple('DeferredFunction', 'function args kwargs name
|
|
|
36
39
|
>>> df.invoke() == defaultdict(None, x=1, y=2)
|
|
37
40
|
True
|
|
38
41
|
"""
|
|
42
|
+
|
|
39
43
|
@classmethod
|
|
40
44
|
def create(cls, function, *args, **kwargs):
|
|
41
45
|
"""
|
|
@@ -50,21 +54,25 @@ class DeferredFunction(namedtuple('DeferredFunction', 'function args kwargs name
|
|
|
50
54
|
# concurrently running jobs when the cache state is loaded from disk. By implication we
|
|
51
55
|
# should serialize as early as possible. We need to serialize the function as well as its
|
|
52
56
|
# arguments.
|
|
53
|
-
return cls(
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
return cls(
|
|
58
|
+
*list(map(dill.dumps, (function, args, kwargs))),
|
|
59
|
+
name=function.__name__,
|
|
60
|
+
module=ModuleDescriptor.forModule(function.__module__).globalize(),
|
|
61
|
+
)
|
|
56
62
|
|
|
57
63
|
def invoke(self):
|
|
58
64
|
"""
|
|
59
65
|
Invoke the captured function with the captured arguments.
|
|
60
66
|
"""
|
|
61
|
-
logger.debug(
|
|
67
|
+
logger.debug("Running deferred function %s.", self)
|
|
62
68
|
self.module.makeLoadable()
|
|
63
|
-
function, args, kwargs = list(
|
|
69
|
+
function, args, kwargs = list(
|
|
70
|
+
map(dill.loads, (self.function, self.args, self.kwargs))
|
|
71
|
+
)
|
|
64
72
|
return function(*args, **kwargs)
|
|
65
73
|
|
|
66
74
|
def __str__(self):
|
|
67
|
-
return f
|
|
75
|
+
return f"{self.__class__.__name__}({self.name}, ...)"
|
|
68
76
|
|
|
69
77
|
__repr__ = __str__
|
|
70
78
|
|
|
@@ -93,13 +101,13 @@ class DeferredFunctionManager:
|
|
|
93
101
|
"""
|
|
94
102
|
|
|
95
103
|
# Define what directory the state directory should actaully be, under the base
|
|
96
|
-
STATE_DIR_STEM =
|
|
104
|
+
STATE_DIR_STEM = "deferred"
|
|
97
105
|
# Have a prefix to distinguish our deferred functions from e.g. NFS
|
|
98
106
|
# "silly rename" files, or other garbage that people put in our
|
|
99
107
|
# directory
|
|
100
|
-
PREFIX =
|
|
108
|
+
PREFIX = "func"
|
|
101
109
|
# And a suffix to distinguish in-progress from completed files
|
|
102
|
-
WIP_SUFFIX =
|
|
110
|
+
WIP_SUFFIX = ".tmp"
|
|
103
111
|
|
|
104
112
|
def __init__(self, stateDirBase: str) -> None:
|
|
105
113
|
"""
|
|
@@ -122,25 +130,31 @@ class DeferredFunctionManager:
|
|
|
122
130
|
|
|
123
131
|
# We need to get a state file, locked by us and not somebody scanning for abandoned state files.
|
|
124
132
|
# So we suffix not-yet-ready ones with our suffix
|
|
125
|
-
self.stateFD, self.stateFileName = tempfile.mkstemp(
|
|
126
|
-
|
|
127
|
-
|
|
133
|
+
self.stateFD, self.stateFileName = tempfile.mkstemp(
|
|
134
|
+
dir=self.stateDir, prefix=self.PREFIX, suffix=self.WIP_SUFFIX
|
|
135
|
+
)
|
|
128
136
|
|
|
129
137
|
# Lock the state file. The lock will automatically go away if our process does.
|
|
130
138
|
try:
|
|
131
|
-
|
|
139
|
+
safe_lock(self.stateFD, block=False)
|
|
132
140
|
except OSError as e:
|
|
133
|
-
|
|
134
|
-
|
|
141
|
+
if e.errno in (errno.EACCES, errno.EAGAIN):
|
|
142
|
+
# Someone else locked it even though they should not have.
|
|
143
|
+
raise RuntimeError(
|
|
144
|
+
f"Could not lock deferred function state file {self.stateFileName}"
|
|
145
|
+
) from e
|
|
146
|
+
else:
|
|
147
|
+
# Something else went wrong
|
|
148
|
+
raise
|
|
135
149
|
|
|
136
150
|
# Rename it to remove the suffix
|
|
137
|
-
os.rename(self.stateFileName, self.stateFileName[
|
|
138
|
-
self.stateFileName = self.stateFileName[
|
|
151
|
+
os.rename(self.stateFileName, self.stateFileName[: -len(self.WIP_SUFFIX)])
|
|
152
|
+
self.stateFileName = self.stateFileName[: -len(self.WIP_SUFFIX)]
|
|
139
153
|
|
|
140
|
-
#
|
|
154
|
+
# Get a Python file object for the file, which we will use to actually use it.
|
|
141
155
|
# Problem: we can't be readable and writable at the same time. So we need two file objects.
|
|
142
|
-
self.stateFileOut =
|
|
143
|
-
self.stateFileIn = open(self.stateFileName,
|
|
156
|
+
self.stateFileOut = open(self.stateFileName, "wb")
|
|
157
|
+
self.stateFileIn = open(self.stateFileName, "rb")
|
|
144
158
|
|
|
145
159
|
logger.debug("Opened with own state file %s" % self.stateFileName)
|
|
146
160
|
|
|
@@ -157,10 +171,7 @@ class DeferredFunctionManager:
|
|
|
157
171
|
os.unlink(self.stateFileName)
|
|
158
172
|
|
|
159
173
|
# Unlock it
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
# Don't bother with close, destroying will close and it seems to maybe
|
|
163
|
-
# have been GC'd already anyway.
|
|
174
|
+
safe_unlock_and_close(self.stateFD)
|
|
164
175
|
|
|
165
176
|
@contextmanager
|
|
166
177
|
def open(self):
|
|
@@ -177,8 +188,9 @@ class DeferredFunctionManager:
|
|
|
177
188
|
self._runOrphanedDeferredFunctions()
|
|
178
189
|
|
|
179
190
|
try:
|
|
191
|
+
|
|
180
192
|
def defer(deferredFunction):
|
|
181
|
-
# Just serialize
|
|
193
|
+
# Just serialize deferred functions one after the other.
|
|
182
194
|
# If serializing later ones fails, eariler ones will still be intact.
|
|
183
195
|
# We trust dill to protect sufficiently against partial reads later.
|
|
184
196
|
logger.debug("Deferring function %s" % repr(deferredFunction))
|
|
@@ -216,7 +228,6 @@ class DeferredFunctionManager:
|
|
|
216
228
|
logger.exception(err)
|
|
217
229
|
# we tried, lets move on
|
|
218
230
|
|
|
219
|
-
|
|
220
231
|
def _runDeferredFunction(self, deferredFunction):
|
|
221
232
|
"""
|
|
222
233
|
Run a deferred function (either our own or someone else's).
|
|
@@ -228,9 +239,15 @@ class DeferredFunctionManager:
|
|
|
228
239
|
deferredFunction.invoke()
|
|
229
240
|
except Exception as err:
|
|
230
241
|
# Report this in real time, if enabled. Otherwise the only place it ends up is the worker log.
|
|
231
|
-
RealtimeLogger.error(
|
|
242
|
+
RealtimeLogger.error(
|
|
243
|
+
"Failed to run deferred function %s: %s",
|
|
244
|
+
repr(deferredFunction),
|
|
245
|
+
str(err),
|
|
246
|
+
)
|
|
232
247
|
except:
|
|
233
|
-
RealtimeLogger.error(
|
|
248
|
+
RealtimeLogger.error(
|
|
249
|
+
"Failed to run deferred function %s", repr(deferredFunction)
|
|
250
|
+
)
|
|
234
251
|
|
|
235
252
|
def _runAllDeferredFunctions(self, fileObj):
|
|
236
253
|
"""
|
|
@@ -318,11 +335,16 @@ class DeferredFunctionManager:
|
|
|
318
335
|
continue
|
|
319
336
|
|
|
320
337
|
try:
|
|
321
|
-
|
|
322
|
-
except OSError:
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
338
|
+
safe_lock(fd, block=False)
|
|
339
|
+
except OSError as e:
|
|
340
|
+
os.close(fd)
|
|
341
|
+
if e.errno in (errno.EACCES, errno.EAGAIN):
|
|
342
|
+
# File is still locked by someone else.
|
|
343
|
+
# Look at the next file instead
|
|
344
|
+
continue
|
|
345
|
+
else:
|
|
346
|
+
# Something else went wrong
|
|
347
|
+
raise
|
|
326
348
|
|
|
327
349
|
logger.debug("Locked file %s" % fullFilename)
|
|
328
350
|
|
|
@@ -330,7 +352,7 @@ class DeferredFunctionManager:
|
|
|
330
352
|
foundFiles = True
|
|
331
353
|
|
|
332
354
|
# Actually run all the stored deferred functions
|
|
333
|
-
fileObj =
|
|
355
|
+
fileObj = open(fullFilename, "rb")
|
|
334
356
|
self._runAllDeferredFunctions(fileObj)
|
|
335
357
|
states_handled += 1
|
|
336
358
|
|
|
@@ -342,10 +364,9 @@ class DeferredFunctionManager:
|
|
|
342
364
|
pass
|
|
343
365
|
|
|
344
366
|
# Unlock it
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
# Now close it. This closes the backing file descriptor. See
|
|
348
|
-
# <https://stackoverflow.com/a/24984929>
|
|
349
|
-
fileObj.close()
|
|
367
|
+
safe_unlock_and_close(fd)
|
|
350
368
|
|
|
351
|
-
logger.debug(
|
|
369
|
+
logger.debug(
|
|
370
|
+
"Ran orphaned deferred functions from %d abandoned state files",
|
|
371
|
+
states_handled,
|
|
372
|
+
)
|
toil/exceptions.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Neutral place for exceptions, to break import cycles."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
from toil.statsAndLogging import StatsAndLogging
|
|
7
7
|
|
|
@@ -16,7 +16,7 @@ class FailedJobsException(Exception):
|
|
|
16
16
|
def __init__(
|
|
17
17
|
self,
|
|
18
18
|
job_store: "AbstractJobStore",
|
|
19
|
-
failed_jobs:
|
|
19
|
+
failed_jobs: list["JobDescription"],
|
|
20
20
|
exit_code: int = 1,
|
|
21
21
|
):
|
|
22
22
|
"""
|
|
@@ -36,7 +36,9 @@ class FailedJobsException(Exception):
|
|
|
36
36
|
for job_desc in failed_jobs:
|
|
37
37
|
if job_desc.logJobStoreFileID:
|
|
38
38
|
with job_desc.getLogFileHandle(job_store) as f:
|
|
39
|
-
self.msg += "\n" + StatsAndLogging.formatLogStream(
|
|
39
|
+
self.msg += "\n" + StatsAndLogging.formatLogStream(
|
|
40
|
+
f, f'Log from job "{job_desc}"'
|
|
41
|
+
)
|
|
40
42
|
# catch failures to prepare more complex details and only return the basics
|
|
41
43
|
except Exception:
|
|
42
44
|
logger.exception("Exception when compiling information about failed jobs")
|
toil/fileStores/__init__.py
CHANGED
|
@@ -28,7 +28,7 @@ class FileID(str):
|
|
|
28
28
|
the job store if unavailable in the ID.
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
|
-
def __new__(cls, fileStoreID: str, *args: Any) ->
|
|
31
|
+
def __new__(cls, fileStoreID: str, *args: Any) -> "FileID":
|
|
32
32
|
return super().__new__(cls, fileStoreID)
|
|
33
33
|
|
|
34
34
|
def __init__(self, fileStoreID: str, size: int, executable: bool = False) -> None:
|
|
@@ -43,18 +43,18 @@ class FileID(str):
|
|
|
43
43
|
return f'{self.size}:{"1" if self.executable else "0"}:{self}'
|
|
44
44
|
|
|
45
45
|
@classmethod
|
|
46
|
-
def forPath(cls, fileStoreID: str, filePath: str) ->
|
|
46
|
+
def forPath(cls, fileStoreID: str, filePath: str) -> "FileID":
|
|
47
47
|
executable = os.stat(filePath).st_mode & stat.S_IXUSR != 0
|
|
48
48
|
return cls(fileStoreID, os.stat(filePath).st_size, executable)
|
|
49
49
|
|
|
50
50
|
@classmethod
|
|
51
|
-
def unpack(cls, packedFileStoreID: str) ->
|
|
51
|
+
def unpack(cls, packedFileStoreID: str) -> "FileID":
|
|
52
52
|
"""Unpack the result of pack() into a FileID object."""
|
|
53
53
|
# Only separate twice in case the FileID itself has colons in it
|
|
54
|
-
vals = packedFileStoreID.split(
|
|
54
|
+
vals = packedFileStoreID.split(":", 2)
|
|
55
55
|
# Break up the packed value
|
|
56
56
|
size = int(vals[0])
|
|
57
|
-
executable =
|
|
57
|
+
executable = vals[1] == "1"
|
|
58
58
|
value = vals[2]
|
|
59
59
|
# Create the FileID
|
|
60
60
|
return cls(value, size, executable)
|