How should the interrupt channel be initialized and stored?
When automation software is used as:
- CLI interactive / non-interactive.
- Web service + browser access.
- CLI + web service + browser access.
In all cases, we need to:
- Initialize the
CmdCtx
with the interrupt_rx
- Spawn the listener that will send
InterruptSignal
in interrupt_tx
.
Both interactive and non-interactive can listen for SIGINT
:
- Interactive:
SIGINT
will be sent by the user pressing Ctrl + C
.
- Non-interactive:
SIGINT
could be sent by a CI thread.
let (interrupt_tx, interrupt_rx) = oneshot::channel::<InterruptSignal>();
tokio::task::spawn(
async move {
.expect(
"Failed to initialize signal handler for SIGINT");
let (
Ok(()) |
Err(InterruptSignal)) = interrupt_tx.send(InterruptSignal);
let mut cmd_ctx = CmdCtx::single_profile_single_flow(output, workspace, interrupt_rx)
let cmd_outcome = EnsureCmd::exec(&
mut cmd_ctx).
await?;
The interrupt_tx
must be accessible from a separate web request.
async fn cmd_exec_start_handler(params: Params) -> CmdExecutionId {
let (interrupt_tx, interrupt_rx) = oneshot::channel::<InterruptSignal>();
let mut cmd_ctx = CmdCtx::single_profile_single_flow(output, workspace, interrupt_rx)
let cmd_execution_id = EnsureCmd::exec_bg(cmd_ctx);
let cmd_execution_by_id = cmd_execution_by_id
cmd_execution_by_id.insert(cmd_execution_id, interrupt_tx);
async fn cmd_exec_progress_handler(cmd_execution_id: CmdExecutionId) ->
Result<CmdProgress, E> {
self.cmd_progress_storage.get(cmd_execution_id).
await
async fn cmd_exec_interrupt_handler(cmd_execution_id: CmdExecutionId) ->
Result<(), E> {
let cmd_execution_by_id = cmd_execution_by_id
if let Some(interrupt_tx) = cmd_execution_by_id.get(cmd_execution_id) {
let (
Ok(()) |
Err(InterruptSignal)) = interrupt_tx.send(InterruptSignal);
Err(E::from(Error::CmdExecutionIdNotFound { cmd_execution_id }))
There are two variants of CLI and web service:
- CLI command running on the user's machine, web service that is a UI for that one command execution.
- CLI client to a web service, so the CLI is just a REST client.
For the first variant, the CmdExecution
invocation is similar to Web Service, with the following differences:
- Output progress is pushed to both CLI and
CmdProgress
storage.
- Interruptions are received from both process
SIGINT
and client requests.
async fn cmd_exec_start(params: Params) {
let (interrupt_tx, interrupt_rx) = oneshot::channel::<InterruptSignal>();
let mut cmd_ctx = CmdCtx::single_profile_single_flow(output, workspace, interrupt_rx)
let cmd_execution_id = EnsureCmd::exec_bg(cmd_ctx);
let cmd_execution_by_id = cmd_execution_by_id
cmd_execution_by_id.insert(cmd_execution_id, interrupt_tx.clone());
tokio::task::spawn(
async move {
.expect(
"Failed to initialize signal handler for SIGINT");
let (
Ok(()) |
Err(InterruptSignal)) = interrupt_tx.send(InterruptSignal);
async fn cmd_exec_progress_handler(cmd_execution_id: CmdExecutionId) ->
Result<CmdProgress, E> {
self.cmd_progress_storage.get(cmd_execution_id).
await
async fn cmd_exec_interrupt_handler(cmd_execution_id: CmdExecutionId) ->
Result<(), E> {
let cmd_execution_by_id = cmd_execution_by_id
if let Some(interrupt_tx) = cmd_execution_by_id.get(cmd_execution_id) {
let (
Ok(()) |
Err(InterruptSignal)) = interrupt_tx.send(InterruptSignal);
Err(E::from(Error::CmdExecutionIdNotFound { cmd_execution_id }))
This is essentially the Web Service implementation, but rendering the progress on the machine with the CLI.