Managing Command State
A component has access to the commandResponseManager
which is used to manage the state of commands during its execution. On receiving a command as a part of onSubmit
, and if the command is accepted by the component, the framework adds the command to an internal CommandResponseManager (CRM). The framework also uses the SubmitResponse
returned by the onSubmit
handler to update the CommandResponseManager. In many cases, this is adequate and no other information is required to handle completion information.
The CommandResponseManager can provide additional support in the following scenarios. These scenarios require the developer to update the CRM.
- The command received in
onSubmit
returnsStarted
indicating a long-running command that requires notification of completion at a later time. - To process an
onSubmit
that starts long-running actions, the component needs to send one or more commands to other components that may also take time to complete.
Updating a Long-running Command
In the first scenario, the developer has a long-running command and does not start any sub-commands or does not need to use the CRM to help manage subcommands. In this case, once the actions are completed, addOrUpdateCommand
is used to notify the CRM that the actions are complete. This will cause the original sender to be notified of completion using the SubmitResponse
passed to addOrUpdateCommand
.
addOrUpdateCommand
AddOrUpdateCommand
is used to add a new command or update the status of an existing command. The following example simulates a worker that takes some time to complete. The onSubmit
handler returns Started
and later the actions complete with Completed
, which completes the command.
- Scala
-
override def onSubmit(controlCommand: ControlCommand): SubmitResponse = { controlCommand.commandName match { case `longRunning` => ctx.scheduleOnce( 5.seconds, commandResponseManager.commandResponseManagerActor, AddOrUpdateCommand(Completed(controlCommand.runId)) ) Started(controlCommand.runId)
- Java
-
private CommandResponse.SubmitResponse crmAddOrUpdate(Setup setup) { // This simulates some worker task doing something that finishes after onSubmit returns Runnable task = new Runnable() { @Override public void run() { commandResponseManager.addOrUpdateCommand(new CommandResponse.Completed(setup.runId())); } }; // Wait a bit and then set CRM to Completed ExecutorService executor = Executors.newSingleThreadScheduledExecutor(); ((ScheduledExecutorService) executor).schedule(task, 1, TimeUnit.SECONDS); // Return Started from onSubmit return new CommandResponse.Started(setup.runId()); }
Using the CRM with Subcommands
If while processing a received command, the component needs to create and send commands to other components (e.g. an Assembly sending commands to one or more HCDs) it can use the CRM to help manage responses from the sub-commands.
A received command that requires one or more sub-commands must first associate the sub-commands with the received command using the addSubCommand
CRM method. When the sub-commands complete, updateSubCommand
is used to update the status of sub-commands.
The status of original command can then be derived from the status of the sub-commands and when all the sub-commands have completed either successfully or not, the original command will complete and a response returned to the original command sender.
addSubCommand
Use addSubCommand
to associate sub-commands with a received command.
- Scala
-
// When receiving the command, onSubmit adds three subCommands commandResponseManager.addOrUpdateCommand(Started(runId)) shortSetup = Setup(prefix, shortRunning, controlCommand.maybeObsId) commandResponseManager.addSubCommand(runId, shortSetup.runId) mediumSetup = Setup(prefix, mediumRunning, controlCommand.maybeObsId) commandResponseManager.addSubCommand(runId, mediumSetup.runId) longSetup = Setup(prefix, longRunning, controlCommand.maybeObsId) commandResponseManager.addSubCommand(runId, longSetup.runId)
- Java
-
Prefix prefix1 = new Prefix("wfos.red.detector"); Setup subCommand1 = new Setup(prefix1, new CommandName("sub-command-1"), sc.jMaybeObsId()); commandResponseManager.addSubCommand(sc.runId(), subCommand1.runId()); Prefix prefix2 = new Prefix("wfos.blue.detector"); Setup subCommand2 = new Setup(prefix2, new CommandName("sub-command-2"), sc.jMaybeObsId()); commandResponseManager.addSubCommand(sc.runId(), subCommand2.runId());
updateSubCommand
Use updateSubCommand
to update the CRM with the SubmitResponse
of the sub-commands. This can trigger the delivery of the status of the original/parent command when status of all the sub-commands have been updated. A SubmitResponse
indicating failure such as Cancelled
or Error
in any one of the sub-commands results in the error status of the parent command. Status of any other sub-commands will not be considered in this case.
- Scala
-
// An original command is split into sub-commands and sent to a component. // The current state publishing is not relevant to the updateSubCommand usage. case _: Completed => controlCommand.runId match { case id if id == shortSetup.runId => currentStatePublisher .publish(CurrentState(shortSetup.source, StateName("testStateName"), Set(choiceKey.set(shortCmdCompleted)))) // As the commands get completed, the results are updated in the commandResponseManager commandResponseManager.updateSubCommand(Completed(id)) case id if id == mediumSetup.runId => currentStatePublisher .publish(CurrentState(mediumSetup.source, StateName("testStateName"), Set(choiceKey.set(mediumCmdCompleted)))) commandResponseManager.updateSubCommand(Completed(id)) case id if id == longSetup.runId => currentStatePublisher .publish(CurrentState(longSetup.source, StateName("testStateName"), Set(choiceKey.set(longCmdCompleted)))) commandResponseManager.updateSubCommand(Completed(id)) }
- Java
-
// An original command is split into sub-commands and sent to a component. // The result from submitting the sub-commands is used to update the CRM ICommandService componentCommandService = runningHcds.get(componentInfo.getConnections().get(0)).orElseThrow(); componentCommandService.submitAndWait(subCommand2, Timeout.durationToTimeout(FiniteDuration.apply(5, TimeUnit.SECONDS))) .thenAccept(commandResponse -> { if (commandResponse instanceof CommandResponse.Completed) { // As the commands get completed, the results are updated in the commandResponseManager commandResponseManager.updateSubCommand(commandResponse); } else { // do something } });
It may be the case that the component wants to avoid automatic inference of a command based on the result of the sub-commands. It should refrain from updating the status of the sub-commands in this case and update the status of the parent command directly as required.