diff --git a/Framework/Core/src/DeviceSpecHelpers.cxx b/Framework/Core/src/DeviceSpecHelpers.cxx index f1365407c088a..0cc4ea56c7dea 100644 --- a/Framework/Core/src/DeviceSpecHelpers.cxx +++ b/Framework/Core/src/DeviceSpecHelpers.cxx @@ -708,7 +708,6 @@ void DeviceSpecHelpers::dataProcessorSpecs2DeviceSpecs(const WorkflowSpec& workf // them before assigning to a device. std::vector outputs; - WorkflowHelpers::verifyWorkflow(workflow); WorkflowHelpers::constructGraph(workflow, logicalEdges, outputs, availableForwardsInfo); // We need to instanciate one device per (me, timeIndex) in the diff --git a/Framework/Core/src/WorkflowHelpers.cxx b/Framework/Core/src/WorkflowHelpers.cxx index 8e517cd2bccd4..9ace1c786f61f 100644 --- a/Framework/Core/src/WorkflowHelpers.cxx +++ b/Framework/Core/src/WorkflowHelpers.cxx @@ -755,10 +755,10 @@ void WorkflowHelpers::sortEdges(std::vector& inEdgeIndex, std::sort(outEdgeIndex.begin(), outEdgeIndex.end(), outSorter); } -void WorkflowHelpers::verifyWorkflow(const o2::framework::WorkflowSpec& workflow) +WorkflowParsingState WorkflowHelpers::verifyWorkflow(const o2::framework::WorkflowSpec& workflow) { if (workflow.empty()) { - throw std::runtime_error("Empty workflow!"); + return WorkflowParsingState::Empty; } std::set validNames; std::vector availableOutputs; @@ -799,6 +799,7 @@ void WorkflowHelpers::verifyWorkflow(const o2::framework::WorkflowSpec& workflow } } } + return WorkflowParsingState::Valid; } using UnifiedDataSpecType = std::variant; diff --git a/Framework/Core/src/WorkflowHelpers.h b/Framework/Core/src/WorkflowHelpers.h index 9cb5c46829744..c673b1e9940b0 100644 --- a/Framework/Core/src/WorkflowHelpers.h +++ b/Framework/Core/src/WorkflowHelpers.h @@ -143,6 +143,11 @@ struct OutputObj { bool isdangling; }; +enum struct WorkflowParsingState : int { + Valid, + Empty, +}; + /// A set of internal helper classes to manipulate a Workflow struct WorkflowHelpers { /// Topological sort for a graph of @a nodeCount nodes. @@ -163,7 +168,7 @@ struct WorkflowHelpers { // Helper method to verify that a given workflow is actually valid e.g. that // it contains no empty labels. - static void verifyWorkflow(const WorkflowSpec& workflow); + [[nodiscard]] static WorkflowParsingState verifyWorkflow(const WorkflowSpec& workflow); // Depending on the workflow and the dangling inputs inside it, inject "fake" // devices to mark the fact we might need some extra action to make sure diff --git a/Framework/Core/src/runDataProcessing.cxx b/Framework/Core/src/runDataProcessing.cxx index 9ad45a114f2b7..bdeed2f1a5e43 100644 --- a/Framework/Core/src/runDataProcessing.cxx +++ b/Framework/Core/src/runDataProcessing.cxx @@ -928,6 +928,8 @@ int runStateMachine(DataProcessorSpecs const& workflow, std::vector preScheduleCallbacks; std::vector postScheduleCallbacks; + bool guiDeployedOnce = false; + while (true) { // If control forced some transition on us, we push it to the queue. if (driverControl.forcedTransitions.empty() == false) { @@ -1025,6 +1027,10 @@ int runStateMachine(DataProcessorSpecs const& workflow, break; case DriverState::MATERIALISE_WORKFLOW: try { + auto workflowState = WorkflowHelpers::verifyWorkflow(workflow); + if (driverInfo.batch == true && workflowState == WorkflowParsingState::Empty) { + throw runtime_error("Empty workflow provided while running in batch mode."); + } DeviceSpecHelpers::dataProcessorSpecs2DeviceSpecs(workflow, driverInfo.channelPolicies, driverInfo.completionPolicies, @@ -1063,6 +1069,13 @@ int runStateMachine(DataProcessorSpecs const& workflow, } catch (std::runtime_error& e) { std::cerr << "Invalid workflow: " << e.what() << std::endl; return 1; + } catch (o2::framework::RuntimeErrorRef ref) { + auto& e = o2::framework::error_from_ref(ref); +#ifdef DPL_ENABLE_BACKTRACE + backtrace_symbols_fd(err.backtrace, err.maxBacktrace, STDERR_FILENO); +#endif + std::cerr << "Invalid workflow: " << e.what << std::endl; + return 1; } catch (...) { std::cerr << "Unknown error while materialising workflow"; return 1; @@ -1107,6 +1120,7 @@ int runStateMachine(DataProcessorSpecs const& workflow, guiContext.window = window; gui_timer.data = &guiContext; uv_timer_start(&gui_timer, gui_callback, 0, 20); + guiDeployedOnce = true; } break; case DriverState::SCHEDULE: { @@ -1185,10 +1199,14 @@ int runStateMachine(DataProcessorSpecs const& workflow, driverInfo.states.push_back(DriverState::RUNNING); driverInfo.states.push_back(DriverState::REDEPLOY_GUI); driverInfo.states.push_back(DriverState::SCHEDULE); - } else if (deviceSpecs.size() == 0) { + } else if (deviceSpecs.empty() && driverInfo.batch == true) { LOG(INFO) << "No device resulting from the workflow. Quitting."; // If there are no deviceSpecs, we exit. driverInfo.states.push_back(DriverState::EXIT); + } else if (deviceSpecs.empty() && driverInfo.batch == false && !guiDeployedOnce) { + // In case of an empty workflow, we need to deploy the GUI at least once. + driverInfo.states.push_back(DriverState::RUNNING); + driverInfo.states.push_back(DriverState::REDEPLOY_GUI); } else { driverInfo.states.push_back(DriverState::RUNNING); }