Motivation (my sob stories)
Too many times I've run a notebook for hours and then Mathematica crashes (FE or Kernel, mostly FE) and things are lost. I've made various attempts at custom solutions from time to time by serializing things intermittently though persistent constructs like LocalObject
or PersistentValue
, etc. but these stores can get corrupted during crashes! I've never found a good general solution to this, so I'm asking for one.
Here's a concrete example: say you're doing a curl for every file in some directory and collecting 100s of megabytes (or a few gigabytes) of results:
fns = FileNames["*.jpg", "~/work/"];
results = Table[remoteFunction @ f, {f, fns}]; (* crashes 3 hours in *)
Instead, I want for a drop-in replacement Table
, that looks like this:
results = SafeTable[remoteFunction @ f, {f, fns}, "Monitor" -> True,
"ParallelKernels" -> 4, "Checkpoint" -> Quantity[10, "Seconds"]
] (* after the crash I re-run and it resumes, e.g. like "wget -c ..." *)
Now, how could this be implemented? Well, perhaps it works like a cronjob or god task. Maybe it creates an association of asynchronous tasks and dispatches them. Or perhaps instead it uses a collection of PersistentObjects
and ParallelTable
under-the-hood... At different scales (assume we're handling multiple gigabytes) I'm not sure what advantages there are from using LocalCache
or LocalValue
vs just MX dumping every-so-often.
Problem Statement
I'm looking for help implementing a drop-in replacement for Table
that is resumable after a crash. This new function SafeTable
acts exactly like a normal Table
, but with efficient and safe storing of temporary results to disk, so that it can be easily resumed after the assured crash and kernel restart occurs.
Here's some code to get started with (although it's an analog for Map, not Table), ...and I'm not even sure if LocalSymbol
is a good thing to use!
ClearAll @ SafeMap;
SafeMap[id_String, f_, list_] := Module[
{ idx, status, len = Length @ list, b = Internal`Bag[],
jobDone = StringJoin[id, "_done"], jobIter = StringJoin[id, "_iter"],
jobResult = StringJoin[id, "_result"] },
status = LocalSymbol[jobDone];
Switch[
status, _LocalSymbol,
Print @ "Start anew";
LocalSymbol[jobDone] = False;
idx = LocalSymbol[jobIter] = 0;
LocalSymbol[jobResult] = b
, True, Print @ "Already finished";
b = LocalSymbol[jobResult];
Return[Internal`BagPart[b, All], Module];
, False,
idx = LocalSymbol[jobIter];
Print["Resuming at index ", idx];
b = LocalSymbol[jobResult];
];
With[{s = LocalSymbol[jobIter]+1},
Table[
With[{x=f[list[[j]]]},
Internal`StuffBag[b, x]
];
LocalSymbol[jobIter] = j;
LocalSymbol[jobResult] = b;
If[j==Length @ list,
LocalSymbol[jobDone] = True;
Return[Internal`BagPart[LocalSymbol[jobResult], All], Module]
];
Nothing
, {j, s, len}
]
]
]
You give it a seed and if it crashes, re-evaluate the definition and re-execute and it will resume:
SafeMap["task_abcd", (Pause[1]; RandomChoice[{Quit, 1, 2}][]; #) &,
Range@5]
So this sort of works for small tasks, but is ugly, sequential, slow (doesn't save incremental chunks), and messy (with creating local symbols which don't clean themselves up). Moreover, using string seeds seems weird to me but I'm not sure what would be a better design. Anyhow, a cleaner implementation that is faster, parallelizable, and monitored would greatly help restore some of my sanity!
Updates
- To give some idea of the headaches I'm having - I experience frontend crashes at least 5 times per day. When I'm doing a complicated workload, e.g. running code for more than an hour, about 50% of the time MMA crashes out and I lose kernel data and notebook changes. This is killing my soul.
- @Roman gives a nice technique in his answer that uses memoization, but what I'm after is something that has similar semantics to
Table
- a construct that can be easily re-applied everywhere. Also, for longer lists with elements of larger sizes, saving every single element in separate MX files might be a very inefficient granularity, there's overhead manually changing names and directories, things quickly get messy trying to re-use this technique many times. Typically you don't want to pollute yourNotebookDirectory
, so you would useCreateDirectory
but when the FE crashes, this path is lost. Also having millions of downvalues might not be optimal.
Export
to MX. More primitive but more reliable. I do this when I’m working with like 10GB in memory. You might need more but if you chunk it up it’s lol be fine. $\endgroup$ParallelMap
? $\endgroup$