Part 3 of 6 in Building and Operating an HTCondor Pool for Research Computing.
Our lab guide describes building a standalone application with MATLAB Compiler on Linux and running it on an HTCondor worker that reads MATLAB Runtime from our NAS. Its build command uses R2021b, and its Runtime installer is R2021b Update 7. Its example times a product of two random matrices.
Here we’ll use a smaller matrix whose answer we can check exactly. Once the remote shell job from Part 1 succeeds, you’ll build a CPU application, submit it to one worker, and verify the MAT-file it returns. The walkthrough uses Ubuntu 24.04 LTS and R2025b as a supported example. The outputs below are expectations for the exercise. R2025b Linux system requirements
Put the build, Runtime, and results in the right places
| Location | What you need there |
|---|---|
| Linux x86-64 build machine | MATLAB, MATLAB Compiler, and the source file. This machine needs the build licenses and can also serve as the access point. |
| HTCondor access point (submit host) | The compiled executable, its generated Linux launcher, the job wrapper, and the submit file. The logs and returned MAT-file go here. |
| Shared storage | An installed Linux x86-64 MATLAB Runtime, mounted at the same path on each eligible worker. |
| Worker | A supported Linux environment, permission to load the shared libraries, and writable job scratch space. The worker can run the executable with MATLAB Runtime alone. |
Match the MATLAB release used to build the application. Within that release, use a Runtime at the same or a later update. For example, an R2025b Update 2 build needs R2025b Update 2 or later in the R2025b line. Record the build update and the Runtime installer version separately. MathWorks Runtime compatibility
If you need to install the Runtime, unpack the matching Linux Runtime installer on a host with write access to the share. Run ./install and choose the shared destination. On the worker, locate the directory that contains runtime/glnxa64. For example, if the libraries are under /shared/MATLAB_Runtime/R2025b/runtime/glnxa64, the launcher takes /shared/MATLAB_Runtime/R2025b. The R2021b lab guide calls this directory v911. Runtime downloads · Installation instructions · Network installations
Build a calculation you can check by hand
Row 3 of a lower triangular matrix of ones begins with three ones. Row 7 begins with seven. Their dot product is 3. In general, if W = tril(ones(n)), entry (i,j) of W * W.' equals min(i,j). That gives us a way to verify the worker’s entire result without repeating the multiplication.
Save this function as window_overlap.m in a new directory on the build machine. The standalone application receives its command-line argument as text, so the function converts it before allocating the matrix. Standalone application arguments
function window_overlap(n_text) n = str2double(n_text); validateattributes(n, {'double'}, ... {'real','finite','scalar','integer','>=',7,'<=',1024}); windows = tril(ones(n)); overlap = windows * windows.'; assert(overlap(3,7) == 3 && isequal(overlap(end,:), 1:n)); save('result.mat', 'n', 'overlap'); fprintf('n=%d size=%dx%d overlap(3,7)=%g overlap(n,n)=%g\n', ... n, size(overlap,1), size(overlap,2), overlap(3,7), overlap(n,n)); end From that directory, run the function and build it with the intended MATLAB release. If several releases are installed, invoke that release’s bin/matlab explicitly. The command prints the release and update before the source test. The source test writes result.mat in the current directory. Leave that file there. Only the executable and launcher in build/ are needed for the job.
mkdir -p build matlab -batch "disp(matlabRelease); window_overlap('64'); mcc -m -R -singleCompThread -d build window_overlap.m" && ls -l build/window_overlap build/run_window_overlap.sh The default build embeds the deployable archive in the executable. The generated launcher takes the Runtime root as its first argument and sets the library paths. The -R -singleCompThread build option limits MATLAB’s computational threads to one, matching the CPU request below. MATLAB Compiler options · Linux launcher and library paths
Prepare the job on the access point
In a fresh job directory on the access point, place window_overlap and run_window_overlap.sh from the same build. Save the following wrapper as run_job.sh. Replace runtime_root with the path as the worker sees it.
#!/usr/bin/env bash set -euo pipefail runtime_root=/shared/MATLAB_Runtime/R2025b printf 'Worker: %s\nRuntime: %s\n' "$(hostname)" "$runtime_root" if [[ ! -d "$runtime_root/runtime/glnxa64" || ! -x "$runtime_root/runtime/glnxa64" ]]; then printf 'Cannot access Linux Runtime under %s\n' "$runtime_root" >&2 exit 1 fi export MCR_CACHE_ROOT="${_CONDOR_SCRATCH_DIR:?Missing HTCondor job scratch directory}/mcr-cache" mkdir -p "$MCR_CACHE_ROOT" chmod u+x ./window_overlap ./run_window_overlap.sh exec ./run_window_overlap.sh "$runtime_root" "$@" The lab guide sets the library path in its wrapper and uses a cache under /var/tmp/mcr for each execution account. Here the generated launcher sets the library path, and each job extracts its archive into a fresh cache. With file transfer enabled, HTCondor starts the job in _CONDOR_SCRATCH_DIR. The application writes result.mat there, alongside the cache directory. HTCondor job environment · MATLAB Runtime component cache
Submit one CPU job
Run condor_status -af Machine OpSys Arch on the access point. Choose a worker with the matching Runtime mounted and copy its advertised Machine name. Save this file as matlab.sub in the job directory and replace worker.example.org. The OS and architecture attributes alone cannot tell you whether a worker can load the shared installation.
universe = vanilla executable = run_job.sh arguments = 64 should_transfer_files = YES transfer_input_files = window_overlap, run_window_overlap.sh when_to_transfer_output = ON_SUCCESS success_exit_code = 0 transfer_output_files = result.mat transfer_output_remaps = "result.mat=results/result.$(ClusterId).$(ProcId).mat" requirements = (TARGET.OpSys == "LINUX") && (TARGET.Arch == "X86_64") && (TARGET.Machine == "worker.example.org") request_cpus = 1 request_memory = 8 GB request_disk = 2 GB output = log/matlab.$(ClusterId).$(ProcId).out error = log/matlab.$(ClusterId).$(ProcId).err stream_output = True stream_error = True log = log/matlab.$(ClusterId).log queue 1 HTCondor transfers the wrapper and its two input files. On exit code zero, it returns result.mat under the name in transfer_output_remaps. If the application exits nonzero, HTCondor skips the MAT-file transfer. If it exits zero and the named file is missing, the job goes on hold. Streaming stdout and stderr keeps the short diagnostics available in either case. The RAM and scratch disk requests are starting values for this exercise. HTCondor output transfer and streaming
mkdir -p log results chmod u+x run_job.sh condor_submit matlab.sub Verify the returned matrix
From the job directory, replace 123 with the cluster number printed by condor_submit. The single process in this example has process ID 0.
cluster=123 job="$cluster.0" event_log="log/matlab.$cluster.log" if condor_wait -wait 180 "$event_log" "$job"; then condor_history "$job" -af JobStatus ExitBySignal ExitCode else condor_q "$job" -af JobStatus HoldReason tail -n 20 "$event_log" fi A zero return from condor_wait can also mean the job was removed. Check the history row for this job. A successful run should show 4 false 0 (completed, normal exit, code zero). Run cat "log/matlab.$job.out" and look for the wrapper’s hostname and this expected line. HTCondor wait status
n=64 size=64x64 overlap(3,7)=3 overlap(n,n)=64 If the access point has MATLAB, run the check below in the same shell. Otherwise, copy that job’s returned file to the build machine and set RESULT_MAT to its path there. The check compares all 4,096 entries with the formula derived above.
export RESULT_MAT="$PWD/results/result.$job.mat" # Adjust if you copied the file. matlab -batch " r = load(getenv('RESULT_MAT'), 'n', 'overlap'); k = 1:64; expected = min(k.', k); assert(isequal(r.n, 64) && isequal(r.overlap, expected)); disp('Verified all 4096 entries')" Before using this job in a DAG, test the failure path once. Submit the same build with an argument below the minimum of 7.
condor_submit -append 'arguments = 6' matlab.sub Repeat the wait and history check with the new cluster number. For this input, expect a MATLAB validation error in the streamed logs, a normal exit with a nonzero code, and no returned MAT-file for that job ID. If MATLAB reports an error and the job is held for a missing output, inspect the event log and the generated launcher. The wrapper’s exec preserves the launcher’s status, so the launcher must propagate the application’s failure.
| Symptom | First check |
|---|---|
| Still Idle after the wait | Run condor_q -better-analyze "$job". Check the exact worker name and the available CPU, memory, and disk. |
| Still Running after the wait | Repeat the wait with the same ID. Initial archive extraction can take time. |
| Held | Run condor_q "$job" -af HoldReason. For an output transfer error, check the worker-side filename and the destination under results/ on the access point. |
| Runtime or cache error | Inspect log/matlab.$job.err. If the wrapper cannot reach the Runtime, check the mount and permissions on the named worker. For a loader error, check the named library, OS dependencies, and Runtime version. For a cache error, check scratch space and write permissions. |
To qualify another worker, replace the Machine name and repeat the successful file check before expanding the pool of eligible workers.
Use the result in Part 4
The cluster and process IDs identify a submitted job. An evicted job can start again with the same ID. A fresh submission gets a new cluster ID. For the DAG in Part 4, each parent instead returns its result under a participant label in the workflow’s own run directory.
For example, assign the parent a label with VARS RUN1 subject="sub-001" in the DAG file. In the parent submit file, replace only the remap with the line below. Keep arguments = 64 for this matrix test.
transfer_output_remaps = "result.mat=results/$(subject).mat" HTCondor returns RUN1’s file to the access point as results/sub-001.mat. The merge job lists that path in transfer_input_files and, with the default transfer layout, opens sub-001.mat in its own working directory. The DAG dependency tells the merge job when it may run. In this exercise the participant labels identify files from identical calculations. A participant analysis would also need distinct input data for each parent. DAGMan dependencies and VARS · HTCondor file transfer
AI assistance was used in drafting and editing this article.
