web: backend: Implement job queue

This commit is contained in:
2025-11-06 15:22:19 -06:00
parent 5c7b8038a1
commit 35dd5ab1a9
6 changed files with 270 additions and 113 deletions

99
web/backend/src/jobs.jl Normal file
View File

@@ -0,0 +1,99 @@
# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2025, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
using UnitCommitment
import Base: put!
Base.@kwdef mutable struct JobProcessor
pending::Channel{String} = Channel{String}(Inf)
processing::Channel{String} = Channel{String}(Inf)
shutdown::Channel{Bool} = Channel{Bool}(1)
worker_task::Union{Task,Nothing} = nothing
optimizer = nothing
end
function Base.put!(processor::JobProcessor, job_id::String)
@info "New job received: $job_id"
return put!(processor.pending, job_id)
end
function isbusy(processor::JobProcessor)
return isready(processor.pending) || isready(processor.processing)
end
function run!(processor::JobProcessor)
while true
# Check for shutdown signal
if isready(processor.shutdown)
break
end
# Wait for a job with timeout
if !isready(processor.pending)
sleep(0.1)
continue
end
# Move job from pending to processing queue
job_id = take!(processor.pending)
@info "Processing job: $job_id"
job_dir = joinpath(basedir, "jobs", job_id)
log_path = joinpath(job_dir, "output.log")
put!(processor.processing, job_id)
# Run optimization
try
open(log_path, "w") do io
redirect_stdout(io) do
redirect_stderr(io) do
json_path = joinpath(job_dir, "input.json.gz")
instance = UnitCommitment.read(json_path)
model = UnitCommitment.build_model(;
instance,
optimizer = processor.optimizer,
)
UnitCommitment.optimize!(model)
solution = UnitCommitment.solution(model)
return UnitCommitment.write(
joinpath(job_dir, "output.json"),
solution,
)
end
end
end
# Remove job from processing queue
take!(processor.processing)
catch e
open(log_path, "a") do io
println(io, "\nError: ", e)
println(io, "\nStacktrace:")
return Base.show_backtrace(io, catch_backtrace())
end
end
end
end
function start(processor::JobProcessor)
processor.worker_task = @async run!(processor)
return
end
function stop(processor::JobProcessor)
# Signal worker to stop
put!(processor.shutdown, true)
# Wait for worker to finish
if processor.worker_task !== nothing
try
wait(processor.worker_task)
catch
# Worker may have already exited
end
end
return
end
export JobProcessor, start, stop, put!, isbusy