Search code examples
bashbackground-processkillbash-completion

Bash kill process misses up completion


I have a bash theme that displays various parts of the prompt in a lazy way. So it displays a set of information in the beginning and then lazy load information that takes time to load and rewrite the prompt from a background process.

Now since the rendering of the lazy loading is async if you move away from the current working directory to another one while the async part was not rendered we will get misplaced information in the wrong place. Example:

enter image description here

The async rendering function is called by:

  # Check the async side of the prompt if available
  set +m
  _render_async &

Now to prevent this from happening, I added a check to see before rendering the prompt if i have any background process that is waiting to render that async side that is not in the current directory and kill it.

# Check and kill any irreelvant background jobs
# Outdated background jobs are any gaudi::async_render executed on folders
# other than the current working directory $PWD  

export PROMPT_COMMAND="gaudi::kill_outdated_asyncRender; $PROMPT_COMMAND"

# Kill all background gaudi::render_async that are running in the wrong context
# Wrong context is any directory (CWD) that is not the current directory
# USAGE:
#   gaudi::kill_outdated_asyncRender

gaudi::kill_outdated_asyncRender() {
  joblist="$(jobs | grep '_render_async.*wd:' | cut -d "[" -f2 | cut -d "]" -f1 | tr '\n' ' ')"
  IFS=' '
  for job in $joblist; do kill "%$job"; done
}

This works perfectly now and i cannot see the wrong rendering, but what I have noticed is that now bash completion on functions is messed up and items do not show as they are intended as follows:

enter image description here

As you can see her the completion elements are not in the same line and its just broken. Removing the kill_outdated_asyncRender fixes this behaviour but I am still not sure to why this happens.


Solution

  • gaudi::kill_outdated_asyncRender sets IFS, but does not reset it. That has global implications, as it's used throughout bash to control word-splitting behavior:

    The IFS variable is used in shells (Bourne, POSIX, ksh, bash) as the input field separator (or internal field separator). Essentially, it is a string of special characters which are to be treated as delimiters between words/fields when splitting a line of input.

    Modifying IFS is a common pattern, but you'll want to reset it:

    gaudi::kill_outdated_asyncRender() {
      joblist="$(jobs | grep '_render_async.*wd:' | cut -d "[" -f2 | cut -d "]" -f1 | tr '\n' ' ')"
      local oIFS=$IFS
      IFS=' '
      for job in $joblist; do kill "%$job"; done
      IFS=$oIFS
    }
    

    As a general guideline, keep your $PROMPT_COMMAND dead simple. Obviously there's the latency concern, but also it's pretty hard to debug in itself. Along these lines I'd make a couple of targeted recommendations to reduce the chance of side-effects:

    1. When you spawn off the background process, record its PID into a file. Then, use the contents of this file in your kill function instead of the jobs | grep pipeline.

    2. Rather than calculating these values upon directory change, pre-compute them (in the background) during login and periodically (via cron). You also have a "refresh" command that updates the values synchronously so you can always get the latest value if you question what you're seeing.