Faking progress in long-running command-line scripts
Today I came across a problem that I’ve encountered a few times before.
I needed to spawn a process in a subshell and wait for it to finish; in
this case, I was using the mysql
command-line utility to import
a large database dump.
If we do this in the standard way:
…then the user of our program receives no feedback; just a blank prompt. It’s impossible to distinguish whether the program is in an importing state or in a hanged one — that is, to impossible to know whether to sit and wait for it to finish, or to interrupt it because something’s gone wrong.
Of course, we have no way of actually displaying accurate progress
updates to the user, since we don’t know what point the mysql
command
has reached. But if we want to be kinder to our user than just showing
them a blank prompt, we can still offer them some visual feedback to let
them know that the script is importing.
Let’s look at how we can do this.
The first thing we need to do is to recognise that system
is
a blocking call; whatever code we have following it will only execute
once the system call has finished. So our first step is to move our
system call into another process, so we can get on with the business of
displaying progress at the same time as the system call is running:
Then, we can start another process to display progress. In my case, I used Jeff Felchner’s excellent ruby-progressbar library:
Here we spawn another process and create a new progress bar. We tell
Ruby that we want to respond to the SIGINT
Unix signal, and that when
we’re sent that signal we want to complete the progress bar and exit.
Finally, we start an infinite loop that increments the progress bar
twice a second.
The final step is to wait for our import to finish:
Here we wait for the import
process to finish (this becomes the new
point where the script blocks; without this, it would exit immediately,
which isn’t what we want). But while we’re waiting, the progress bar
process will also be doing its magic and giving the user some feedback.
Once the wait
call has finished, we sent SIGINT
to the progress bar
process; this allows it to exit cleanly, and stops us from having an
runaway progress bar executing forever.
That’s it! Here’s the full code:
Ta-da! With some basic Unix process-wrangling, we’ve made our long-running script a little bit nicer for its users.
Taking it further
This pattern could trivially be extended into a method that takes
a block and passes it to the first fork
call, allowing you to display
this kind of indeterminate progress update for any code in just a single
line. Here’s what that might look like:
Now we can call our first example as:
Making it this easy to display progress — even if it isn’t really informing the user, it’s extending them a little courtesy — makes it easy to avoid those black screens of uncertainty that otherwise crop up regularly. It’s a pattern I think I’ll go back to.
Add a comment