Asynchronous Commands

The name is taken from Bash and reflects a different kind of job to the usual pipeline. Normally we expect jobs to be vaguely task-oriented and our script is about orchestrating those tasks.

An asynchronous command, however, is sort of hiding away in the background, a stub process waiting for us to read from or write to it. It’s a sort of adjunct service job.

For good or for ill, Bash is a bit easy on asynchronous commands and whether they succeed to fail. I’ve been bitten by this where a Process Substitution, <( ... ), was failing and it took me a while to figure that out.

Perhaps you don’t care. I’m thinking that you should. I went over some of the issues in set -e.

Job Control

In the first instance we can extend the job structure to include an asynchronous command flag when we know it is an asynchronous command.

Otherwise, the job will inherit any extant job control error handling as per the parent Idio. No sense in doing anything different.

However, we do want to behave differently if an asynchronous command fails. Here, because we set the asynchronous command flag, we can trivially raise a different condition is the job failed. In particular, raise a ^rt-async-command-status-error rather than the normal ^rt-command-status-error.

To, hopefully, no-one’s huge surprise, the new async condition is derived from the normal condition though that may catch you out if you write your own ^rt-command-status-error as it will pick up ^rt-async-command-status-error as well.

We can now have a distinct default condition handler for the async condition where we can choose an appropriate behaviour.

And that behaviour is? Hmm. Tricky. The implicit suppression of any errors by Bash has filled me with no confidence about the various asynchronous processes I run.

I suppose, in the first instance I would like to be told that something went wrong. So, the default action for ^rt-async-command-status-error is to report the job status and… that’s it.

Just report the job status. Let’s get a feel for what we’re up against before getting all picky about stuff.

There’s two more options in play, here:

  1. you can set the dynamic variable suppress-async-command-report! to not-#f which suppresses the warning

  2. you can supercede the default handler with one of your own which can do whatever

Exiting

As noted in job control considerations when Idio comes to exit asynchronous commands will be handled in the same way as stopped jobs, that is they’ll be sent a SIGTERM.

Examples

Let’s contrive some examples. We’ll use utils/bin/auto-exit to control the behaviour of the asynchronous command.

SIGTERM

auto-exit can be told to read two lines of input

x.idio
oph := pipe-into auto-exit -r 2
hprintf oph "hello\n"
close-handle oph

Hmm, we only wrote one line and then closed our pipe to the asynchronous process. It’s still there, though because if the script continued:

ps -Ht (collect-output tty)
    PID    PPID    PGID COMMAND
 568144  568143  568144 bash
 570027  568144  570027   idio x
 570031  570027  570031     idio x
 570032  570031  570031       bash .../auto-exit -r 2
 570033  570027  570027     /usr/bin/ps -Ht /dev/pts/6

In fact, it won’t go anywhere until we exit, whereon:

[1]$ ps -Ht $(tty)
    PID    PPID    PGID COMMAND
 568144  568143  568144 bash
 570041  568144  570041   ps -Ht /dev/pts/6

Gone!

As Job Control is shutting down it’ll walk round the list of outstanding jobs and if any are marked as stopped or asynchronous then it sends them a SIGTERM.

Early Bath

We can have auto-exit time-out waiting for input – seeing as we’re not giving it any – and exit in disgust at our poor manners. We’ll throw in a sleep to pad things out:

x.idio
oph := pipe-into auto-exit -r 2 -t 1
hprintf oph "hello\n"
close-handle oph
ps -Ht (collect-output tty)
sleep 2
ps -Ht (collect-output tty)

Giving the following output:

    PID    PPID    PGID COMMAND
 568144  568143  568144 bash
 570056  568144  570056   idio x
 570059  570056  570059     idio x
 570060  570059  570059       bash .../auto-exit -r 2 -t 1
 570061  570056  570056     /usr/bin/ps -Ht /dev/pts/6
default-racse-handler: this async job result has been ignored:
job 570059: (auto-exit -r 2 -t 1): a?=#t
           PID fl  status       cmd
  proc: 570059  C  (exit 142)   (auto-exit -r 2 -t 1)
  flags: C - completed; !C - not completed; S - stopped
Real 2.024
User 0.007
Syst 0.031

    PID    PPID    PGID COMMAND
 568144  568143  568144 bash
 570056  568144  570056   idio x
 570065  570056  570056     /usr/bin/ps -Ht /dev/pts/6

So, two things, firstly the asynchronous command exited and we were told about it.

Secondly, the command, bash, called exit 142 which we can interpret as signal 14 which is SIGALRM on this system. I would read that as bash caught SIGALRM and then called exit (128 + SIGALRM) rather than call kill (getpid (), SIGALRM).

Thirdly [sic], we didn’t exit because the asynchronous command failed.

We can write an ^rt-async-command-status-error handler that does exit if we want. Perhaps, in due course, we can have an enable-async-command-exit-on-error! variable.

Quietly Does It

As noted, we can suppress the warning to get tradition shell behaviour by setting the dynamic variable suppress-async-command-report! to non-#f:

x.idio
suppress-async-command-report! = #t
oph := pipe-into auto-exit -r 2 -t 1
hprintf oph "hello\n"
close-handle oph
ps -Ht (collect-output tty)
sleep 2
ps -Ht (collect-output tty)

Giving the following output:

   PID    PPID    PGID COMMAND
568144  568143  568144 bash
570056  568144  570056   idio x
570066  570056  570066     idio x
570067  570066  570066       bash .../auto-exit -r 2 -t 1
570068  570056  570056     /usr/bin/ps -Ht /dev/pts/6
   PID    PPID    PGID COMMAND
568144  568143  568144 bash
570056  568144  570056   idio x
570072  570056  570056     /usr/bin/ps -Ht /dev/pts/6

As it is a dynamic variable you can create a new value transiently in a block:

{
  suppress-async-command-report! :~ #t
  ...
}

Normal Behaviour

Of course, if the asynchronous command exits cleanly, we shouldn’t see a thing:

x.idio
oph := pipe-into auto-exit -r 1 -t 1
hprintf oph "hello\n"
close-handle oph
ps -Ht (collect-output tty)
sleep 1
ps -Ht (collect-output tty)

Giving the following output:

   PID    PPID    PGID COMMAND
568144  568143  568144 bash
570096  568144  570096   idio x
570113  570096  570113     [idio]
570114  570096  570096     /usr/bin/ps -Ht /dev/pts/6
   PID    PPID    PGID COMMAND
568144  568143  568144 bash
570096  568144  570096   idio x
570120  570096  570096     /usr/bin/ps -Ht /dev/pts/6

It’s possible you might see the about-to-exit auto-exit in the first ps output as timing is everything. It shouldn’t appear in the second!

I guess it’s even possible that things could be grinding so slowly on your machine that we fail to run hprintf before the 1 second timeout on bash’s read builtin expires. Meh! At least you’ll now get a warning!

Last built at 2024-12-21T07:11:03Z+0000 from 463152b (dev)