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.

  1. The command received in onSubmit returns Started indicating a long-running command that requires notification of completion at a later time.
  2. 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
            }
        });
Note

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.