Research and Development

Killing User Processes With Xexit on LTSP Servers

A user logs in, opens some applications, plays a game, launches a browser and visits a webpage. Processes process. The user logs out. What happens to application processes when a user logs out? Do the processes die? And if they do, do they do so willingly or unwillingly? Or perhaps they just lay dormant until next user session?

I don’t want to get overly poetic or philosophical, but the answer does have some relevance and is not always obvious. Usually we expect processes to just die (or otherwise ”become nonexistant”) when we log out from a computer system, and this is what typically happens (if you are interested: most often processes die willingly, if we allow some antropomorphism here). But some software may instead daemonize itself, meaning that they detach themselves from terminal sessions and keep running after a user has logged out. A user may intentionally, perhaps even maliciously, leave processes running in the background when he logs out to perform some tasks. Some programs may have bugs and will not always exit as a user logs out or a terminal connection is broken. Even worse, a program may end up using resources pointlessly, for example stay in an endless loop using most available CPU time, or keep trying to write to nonwritable filesystem mounts or doing something else nonsensical.

These may be problems and while they may appear elsewhere, they are probably most prevalent in server environments such as in LTSP systems, because badly behaving software spends resources that could be used by other software. With servers problems also persist long times, because servers are rebooted relatively infrequently.

In some environments, of course, it may be desirable that users can leave processes open to do some processing after a user has logged out. For example, this might be the case when servers are used for scientific calculations. But even in these cases it may be useful to have some limits to what particular programs should be left running after the actual desktop session is closed.

One possible solution is xexit, which may be used to trigger scripts as users log out or their session is abruptly broken by a network disconnection or some other such an event. See the xexit project website at https://code.launchpad.net/~sbalneav/ltsp/xexit. The xexit program is written by Scott Balneaves, an LTSP developer, and it is created primarily for LTSP environments even though it can be used in other settings.

For those who use Ubuntu (or perhaps Debian or some other of its derivatives), the easiest way to install xexit is from the launchpad ppa by Scott:

sudo add-apt-repository ppa:sbalneav/ppa
sudo apt-get update
sudo apt-get install xexit

Those who use some other Unix can compile the source and install the xexit binary in the following way. First install autoconf and bzr, and then do (this should work for version 0.2):

bzr branch lp:~sbalneav/ltsp/xexit
cd xexit
touch ChangeLog
sh autogen.sh
./configure
make
sudo install -o root -g root -m 755 src/xexit /usr/local/bin/xexit

Using xexit from the Ubuntu package is easy. The package installs a startup snippet into /etc/X11/Xsession.d/90xexit so that xexit is started with parameter /usr/bin/EndXSession when user logs in. The xexit binary periodically pings the X display and if a connection to X server is broken or does not answer to ping checks in some time, xexit runs the session exit script it was given as a parameter. With thin clients, if a thin client is turned off uncleanly from a power button or if a network cable is unplugged, xexit normally waits about 20-30 seconds before the exit script is run on the server.

The default exit session script (/usr/bin/EndXSession) runs all the scripts in directory /etc/Xexit.d. With the default script the scripts under /etc/Xexit.d must be bourne shell fragments. The default configuration kills evolution-data-server (that daemonizes itself when started), pulseaudio, requests gconfd to shut down and then kills all user processes forcibly with KILL-signal. This should work quite well for many configurations.

One can change the script that is run at session exit by editing /etc/X11/Xsession.d/90xexit. This also allows to use some other language than bourne shell. For example, we might want to trigger /usr/local/bin/kill_desktop_session instead of executing the scripts in /etc/Xexit.d.

Edit /etc/X11/Xsession.d/90xexit:

xexit /usr/local/bin/kill_desktop_session || true

One possible problem with default configuration is that if a user has two desktop sessions to the same LTSP server, exiting one session will end up forcing an exit to the other one as well. To kill only the processes of the session where X connection is lost, one can only kill processes that have the same LTSP_CLIENT_HOSTNAME environment variable set that xexit has. This can be done by having the following script at /usr/local/bin/kill_desktop_session (do remember to set the executable bits for this script):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/bin/sh

TERMINAL="$LTSP_CLIENT_HOSTNAME"

lookup_processes() {
  ps eww -u "$USER" \
    | grep -w "LTSP_CLIENT_HOSTNAME=$LTSP_CLIENT_HOSTNAME" \
    | awk '{ print $1 }' \
    | fgrep -vw $$ \
    | xargs
}

logger "kill_desktop_session: $$ killing all session processes of user $USER on $TERMINAL"

for signal in CONT TERM KILL; do
  if [ "$signal" = "KILL" ]; then
    sleep 3
  fi
  PROCESSES="$(lookup_processes)"
  if [ -n "$PROCESSES" ]; then
    logger "kill_desktop_session: $$ sending $signal to $USER processes $PROCESSES"
    kill -$signal $PROCESSES
  fi
done

logger "kill_desktop_session: $$ finished killing"

This works, because for each desktop session an xexit process is started, and it has the same environment variable LTSP_CLIENT_HOSTNAME as other processes in that session. In function lookup_processes we lookup the pids of those processes that match this environment variable. Then we send them CONT (continue) signal (in case some of them have been stopped), request them to terminate (by sending TERM), and if they did not comply after three seconds, we forcibly kill them. (In process termination, it is nice to ask politely before making demands, in case some programs want to do some operations (such as writing their internal state to files) before they are killed).

In a non-LTSP case we could replace the TERMINAL and lookup_processes() definitions with this:

1
2
3
4
5
TERMINAL="$(hostname)"

lookup_processes() {
  pgrep -u "$USER" | fgrep -vw $$ | xargs
}

This should work for laptops, workstations and other such cases, in case xexit is wanted or needed for those. Xexit also works properly with the ”change user” feature in GNOME session (and probably others), that is, it will not kill processes when user session is still open, even if it is not active.

Note that xexit is run with the privileges of the user that logs in. This means that if a user wants to bypass this mechanism and not have her processes killed when she logs out, she can do so by killing the xexit process when her desktop session is still open. While this may be an issue, on the other hand the choice to run xexit with user privileges makes xexit simple to implement, and it safeguards against some bad session exit scripts, because xexit only has the same limited privileges that the user has.

What is nice about xexit is that it is simple, effective and mostly desktop environment agnostic. The default configuration targets a typical Ubuntu installation, but it may be adapted with ease to other systems and configurations. Try it out, and keep your servers clean!

Comments