Unify batch job processing with a shared runBatch core
This commit is contained in:
@@ -27,164 +27,53 @@ nRunIds = numel(run_ids);
|
||||
nJobsPerRunId = submit_options.wh.getLastLinIndice();
|
||||
totalJobs = nRunIds * nJobsPerRunId;
|
||||
|
||||
% Preallocate
|
||||
results = cell(nJobsPerRunId, nRunIds);
|
||||
futures = parallel.FevalFuture.empty(totalJobs,0);
|
||||
jobIndices = zeros(totalJobs,2);
|
||||
|
||||
% Optional waitbar
|
||||
if submit_options.waitbar
|
||||
h = waitbar(0, 'Processing Jobs...');
|
||||
cleanupObj = onCleanup(@() delete(h));
|
||||
end
|
||||
|
||||
switch submit_mode
|
||||
case processingMode.parallel
|
||||
%—– SET UP POOL & QUEUE —–
|
||||
p = setupParallelPool(11, 300); % 10 workers, 300s idle timeout
|
||||
|
||||
% === submit all futures ===
|
||||
jobCounter = 0;
|
||||
for r = 1:nRunIds
|
||||
for k = 1:nJobsPerRunId
|
||||
jobCounter = jobCounter + 1;
|
||||
jobIndices(jobCounter,:) = [r,k];
|
||||
|
||||
opt = buildOptionalVars(k, submit_options.wh);
|
||||
futures(jobCounter) = parfeval( ...
|
||||
p, @dsp_runid, 1, ...
|
||||
run_ids(r), ...
|
||||
"database_type", dsp_options.database_type, ...
|
||||
"dataBase", dsp_options.dataBase, ...
|
||||
"append_to_db", dsp_options.append_to_db, ...
|
||||
"load_file_path", dsp_options.load_file_path, ...
|
||||
"max_occurences", dsp_options.max_occurences, ...
|
||||
"storage_path", dsp_options.storage_path, ...
|
||||
"mode", dsp_options.mode, ...
|
||||
"parameters", opt ...
|
||||
);
|
||||
|
||||
fprintf('[RunID %d, Job %d] Submitted to pool.\n', run_ids(r), k);
|
||||
end
|
||||
end
|
||||
|
||||
%—– W A I T B A R U P D A T E —–
|
||||
if submit_options.waitbar
|
||||
futureArray = futures;
|
||||
updateWB = @() waitbar( ...
|
||||
sum(arrayfun(@(f) strcmp(f.State,'finished'), futureArray))/totalJobs, ...
|
||||
h, sprintf('Completed %d/%d jobs', ...
|
||||
sum(arrayfun(@(f) strcmp(f.State,'finished'), futureArray)), totalJobs) ...
|
||||
);
|
||||
afterEach(futureArray, updateWB, 0);
|
||||
end
|
||||
|
||||
% —– before the loop —–
|
||||
% Keep track of which futures we've already handled:
|
||||
consumedIdx = false(totalJobs,1);
|
||||
|
||||
% —– fetch in completion order, handling successes and errors —–
|
||||
for n = 1:totalJobs
|
||||
try
|
||||
% This returns the value AND the linear index in 'futures'
|
||||
[idx, val] = fetchNext(futures);
|
||||
|
||||
duration = futures(idx).RunningDuration;
|
||||
startDT = futures(idx).StartDateTime;
|
||||
finishDT = futures(idx).FinishDateTime;
|
||||
duration = finishDT - startDT;
|
||||
|
||||
% Mark it consumed
|
||||
consumedIdx(idx) = true;
|
||||
|
||||
% Map back to (r,k) and store
|
||||
r = jobIndices(idx,1);
|
||||
k = jobIndices(idx,2);
|
||||
|
||||
fprintf('[%s] JobID %d/%d (%.1f%%) — %s — RunID %d — Subjob %d — fetched.\n', ...
|
||||
datestr(now,'yyyy-mm-dd HH:MM:SS'), ...
|
||||
r, totalJobs, 100*n/totalJobs,char(duration), ...
|
||||
run_ids(r), k);
|
||||
|
||||
% Update waitbar
|
||||
if submit_options.waitbar
|
||||
waitbar(n/totalJobs, h, ...
|
||||
sprintf('Fetched %d/%d (%.1f% Percent)', n, totalJobs, 100*n/totalJobs));
|
||||
drawnow; % force the GUI to refresh
|
||||
end
|
||||
|
||||
storeResult(val, k, submit_options.wh);
|
||||
results{k,r} = val;
|
||||
|
||||
catch fetchErr
|
||||
% fetchNext has already set Read=true on the errored future.
|
||||
% Find the one Read==true that we have _not_ yet consumed.
|
||||
readMask = arrayfun(@(f) f.Read, futures);
|
||||
idxErr = find(readMask & ~consumedIdx', 1);
|
||||
consumedIdx(idxErr) = true;
|
||||
|
||||
% Pull the _real_ exception out of the future object
|
||||
errInfo = futures(idxErr).Error;
|
||||
if iscell(errInfo)
|
||||
origME = errInfo{1};
|
||||
else
|
||||
origME = errInfo;
|
||||
end
|
||||
|
||||
% Map back to (r,k) and log
|
||||
r = jobIndices(idxErr,1);
|
||||
k = jobIndices(idxErr,2);
|
||||
handleError(origME, k, run_ids(r));
|
||||
results{k,r} = origME;
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
case processingMode.serial
|
||||
%—– SERIAL EXECUTION —–
|
||||
jobCounter = 0;
|
||||
for r = 1:nRunIds
|
||||
for k = 1:nJobsPerRunId
|
||||
jobCounter = jobCounter + 1;
|
||||
optionalVars = buildOptionalVars(k, submit_options.wh);
|
||||
try
|
||||
fprintf('[RunID %d, Job %d] Running in linear mode...\n', run_ids(r), k);
|
||||
val = dsp_runid( run_ids(r), ...
|
||||
"database_type", dsp_options.database_type, ...
|
||||
"dataBase", dsp_options.dataBase, ...
|
||||
"append_to_db", dsp_options.append_to_db, ...
|
||||
"load_file_path", dsp_options.load_file_path, ...
|
||||
"max_occurences", dsp_options.max_occurences, ...
|
||||
"storage_path", dsp_options.storage_path, ...
|
||||
"mode", dsp_options.mode, ...
|
||||
"parameters", optionalVars );
|
||||
|
||||
fprintf('[RunID %d, Job %d] Completed successfully.\n', run_ids(r), k);
|
||||
storeResult(val, k, submit_options.wh);
|
||||
results{k,r} = val;
|
||||
|
||||
catch ME
|
||||
handleError(ME, k, run_ids(r));
|
||||
results{k,r} = ME;
|
||||
end
|
||||
|
||||
if submit_options.waitbar
|
||||
waitbar(jobCounter/totalJobs, h, ...
|
||||
sprintf('Completed %d/%d jobs', jobCounter, totalJobs));
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
otherwise
|
||||
error('Unknown submit_mode "%s".', string(submit_mode))
|
||||
end
|
||||
|
||||
wh = submit_options.wh;
|
||||
results = cell(nJobsPerRunId, nRunIds);
|
||||
jobs = repmat(struct('args', {{}}, 'label', "", 'meta', struct()), 1, totalJobs);
|
||||
|
||||
jobCounter = 0;
|
||||
for r = 1:nRunIds
|
||||
for k = 1:nJobsPerRunId
|
||||
jobCounter = jobCounter + 1;
|
||||
optionalVars = buildOptionalVars(k, submit_options.wh);
|
||||
|
||||
jobs(jobCounter).args = { ...
|
||||
run_ids(r), ...
|
||||
"database_type", dsp_options.database_type, ...
|
||||
"dataBase", dsp_options.dataBase, ...
|
||||
"append_to_db", dsp_options.append_to_db, ...
|
||||
"load_file_path", dsp_options.load_file_path, ...
|
||||
"max_occurences", dsp_options.max_occurences, ...
|
||||
"storage_path", dsp_options.storage_path, ...
|
||||
"mode", dsp_options.mode, ...
|
||||
"parameters", optionalVars};
|
||||
jobs(jobCounter).label = sprintf('RunID %d, Job %d', run_ids(r), k);
|
||||
jobs(jobCounter).meta.runIndex = r;
|
||||
jobs(jobCounter).meta.jobIndex = k;
|
||||
jobs(jobCounter).meta.run_id = run_ids(r);
|
||||
end
|
||||
end
|
||||
|
||||
linearResults = runBatch(@dsp_runid, jobs, ...
|
||||
"mode", submit_mode, ...
|
||||
"waitbar", submit_options.waitbar, ...
|
||||
"waitbarMessage", "Processing Jobs...", ...
|
||||
"numWorkers", 11, ...
|
||||
"idleTimeout", 300, ...
|
||||
"cancelExistingQueue", true, ...
|
||||
"resultHandler", @storeResult, ...
|
||||
"errorHandler", @handleError);
|
||||
|
||||
for idx = 1:totalJobs
|
||||
r = jobs(idx).meta.runIndex;
|
||||
k = jobs(idx).meta.jobIndex;
|
||||
results{k, r} = linearResults{idx};
|
||||
end
|
||||
|
||||
%% Local helpers %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
function handleError(ME, jobIndex, run_id)
|
||||
fprintf('[RunID %d, Job %d] ERROR [%s]: %s\n', run_id, jobIndex, ...
|
||||
function handleError(ME, job, ~)
|
||||
fprintf('[RunID %d, Job %d] ERROR [%s]: %s\n', job.meta.run_id, job.meta.jobIndex, ...
|
||||
ME.identifier, ME.message);
|
||||
for st = ME.stack'
|
||||
fprintf(' %s:%d (%s)\n', st.file, st.line, st.name);
|
||||
@@ -202,8 +91,9 @@ wh = submit_options.wh;
|
||||
end
|
||||
end
|
||||
|
||||
function storeResult(val, jobIndex, wh)
|
||||
function storeResult(val, job, ~)
|
||||
if ~isempty(wh)
|
||||
jobIndex = job.meta.jobIndex;
|
||||
wh.addValueToStorageByLinIdx(val.ffe_package, 'ffe_package', jobIndex);
|
||||
wh.addValueToStorageByLinIdx(val.mlse_package, 'mlse_package', jobIndex);
|
||||
wh.addValueToStorageByLinIdx(val.vnle_package, 'vnle_package', jobIndex);
|
||||
@@ -212,24 +102,5 @@ wh = submit_options.wh;
|
||||
wh.addValueToStorageByLinIdx(val.mlmlse_package,'mlmlse_package',jobIndex);
|
||||
end
|
||||
end
|
||||
function p = setupParallelPool(numWorkers, idleTimeout)
|
||||
% Ensure a pool exists at the right size & timeout
|
||||
p = gcp('nocreate');
|
||||
if isempty(p) || p.NumWorkers~=numWorkers
|
||||
if ~isempty(p)
|
||||
delete(p);
|
||||
end
|
||||
p = parpool('local', numWorkers, 'IdleTimeout', idleTimeout);
|
||||
end
|
||||
|
||||
% Cancel anything left in the pool's default queue
|
||||
q = p.FevalQueue;
|
||||
if ~isempty(q.QueuedFutures) || ~isempty(q.RunningFutures)
|
||||
cancelAll(q);
|
||||
fprintf('Canceled %d unfetched jobs from old queue.\n', ...
|
||||
numel(q.QueuedFutures)+numel(q.RunningFutures));
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user