Blocking the launch of the second instance of the program in Perl

    image

    From time to time there is a need to make sure that the program is guaranteed to work in one instance. For example, it can be a script that generates a certain file - if you run two copies of the script at the same time, the file will be corrupted.

    It is necessary to check whether the process being launched is the only currently running instance of the program, or is there already another instance running?

    There are several methods of such verification, differing in reliability.

    Main methods


    1) Check for the existence of a pid file

    The script is launched and checks for the presence of the pid file. If the pid-file already exists, it means that another instance of the script is already running and should not be run a second time. If the pid file does not exist, then the script creates the pid file and starts working.

    The problem is that the first instance may crash without deleting the pid file. And now it will be impossible to run the script at all, since the launched script will always detect the pid-file, consider itself the second instance and refuse to run until the pid-file is manually deleted. In addition, there is a problem with race conditions, since checking the existence of a file and then creating this file are two separate operations, not one atomic.

    2) Check for a pid in the process list

    The script is launched, reads the pid file and then checks if there is a process with a read pid in the process table. If such a process exists, then another instance of the script is already running and should not be run a second time. If such a process does not exist, then the script writes its pid to the pid file and starts working.

    The problem is that the first instance may fall, and the pid with which it worked may be issued to another process. After that, as in the first method, there will be a problem with running the script. Of course, the likelihood of such a situation is slightly lower than in the first case, because the pid will not be issued again immediately. Yes, and the likelihood that an extraneous process will receive exactly the same pid as our process is not very large, however it is, since there are not an infinite number of pids and they are issued in a circle. Well, plus a race of conditions, since there are even more operations than in the first method.

    3) PID file lock

    The script runs and tries to lock the pid file. If it was not possible to block, it means that another instance of the script is already running and should not be run a second time. If it was possible to lock the pid-file, then the script continues to work.

    This method has no problems arising in the previous two methods:

    1. The fall of the first instance of the script automatically unlocks the pid file, so the next instance can be started without problems
    2. There is no race of conditions, since blocking is an atomic action

    Thus, this method is guaranteed to block the launch of the second instance of the program.

    Pid File Lock Method


    Let us consider in detail the implementation of this method.

    #!/usr/bin/perl
    use Carp;
    use Fcntl qw(:DEFAULT :flock);
    check_proc('/tmp/testscript.pid') and die "Скрипт уже запущен, запуск дубля отклонен!\n";
    # Тут находится код,
    # который должен исполняться
    # в единственном экземпляре
    sleep 15;
    # Проверка существования запущенного экземпляра
    sub check_proc {
        my ($file) = @_;
        my $result;
        sysopen LOCK, $file, O_RDWR|O_CREAT or croak "Невозможно открыть файл $file: $!";
        if ( flock LOCK, LOCK_EX|LOCK_NB  ) {
            truncate LOCK, 0 or croak "Невозможно усечь файл $file: $!";
            my $old_fh = select LOCK;
            $| = 1;
            select $old_fh;
            print LOCK $$;
        }
        else {
            $result = ;
            if (defined $result) {
                chomp $result;
            }
            else {
                carp "Отсутствует PID в пид-файле $file";
                $result = '0 but true';
            }
        }
        return $result;
    }
    


    First of all, the script calls the check_proc function, which checks for the presence of another running instance, and if the verification succeeds, the script stops with the corresponding message.

    Note that in this line the functions check_proc and die are combined through the conditional operator and. Usually, such connectives are made through the or operator, but in our case, the logic of the connective is different - we kind of tell the script: “Realize the meaninglessness of your existence and die!”.

    The check_proc function returns the pid of an already running instance, if it is actually running, or undef. Accordingly, the true result of the execution of this function means that one instance of the program is already running and that it is not necessary to start the second time.

    Function check_proc


    Now we will analyze the check_proc function line by line.

    1) The sysopen function opens a file for reading and writing

    It is important that the file must be opened in non-destructive mode, otherwise the contents of the file will be destroyed. Because of this, you cannot use the simple open function, since it cannot open files in non-destructive mode.

    The sysopen function with the O_RDWR | O_CREAT flags opens the file in non-destructive mode. The O_RDWR flag means opening at the same time for reading and writing, the O_CREAT flag creates a file if it does not exist at the time of opening. Flags are imported from the Fcntl module (you can do without Fcntl if you use the numerical values ​​of the flags).

    2) The flock function locks the file

    Since we need to make only one process lock, we need to request an exclusive lock. Exclusive locking is set with the LOCK_EX flag. As soon as the process receives an exclusive lock - that's it, no one else can get such a lock in parallel. This, in fact, is the basis for the blocking mechanism for starting the second instance of the program, this is a key function.

    If the flock function detects that someone else has already locked the file, then it will wait until the lock is released. This behavior is not suitable for our verification. We do not need to wait for the file to be released, we need the check_proc function to immediately return a positive result when a lock is detected. To do this, use the flag LOCK_NB.

    Further behavior depends on whether it was possible to obtain a lock (3) or failed (4).

    3a) The truncate function clears the file

    Since we opened the file in non-destructive mode, the old contents of the file remained untouched. We do not need this content, and may even interfere, so the file needs to be cleaned.

    3b) The combination of select and $ | disables buffering

    We need to write the pid of the current process to the pid file. But the output to the file is buffered block by block, so the pid will be written (it would seem), but in fact it will be (so far) empty in the file. Because of this, any other process that tries to read the pid of our running process from the pid file will find emptiness there. Our check is based on blocking the pid file, and not on checking the pid, so for our processes the lack of a pid will not be a disaster. But for processes that care about the PID itself, this will create a problem.

    To disable output buffering, you need the variable $ | set to true value. The first select sets the current descriptor to the handle of our pid file, then the variable is set to true, then the second select returns STDOUT back to the current descriptor. After that, writing to the file will occur immediately, without buffering.

    4a) Read the pid from the pid file

    Reading from the file itself is trivial, but you need to keep in mind that it is possible that the pid in the file is not detected. This will mean that the instance of the program is already running (after all, it was not possible to obtain a lock), but for some reason the pid of this running instance was not written. This should not be a problem for our check, because it is not based on checking the pid. But the check_proc function should return the true value in case of detection of a running instance, so instead of the missing pid, you need to return something else, which is, nevertheless, true.

    A suitable value in this case would be true zero. This is a magical value (of which there are many in the pearl), which in the numerical context is zero, and in the Boolean it is true. There are several options for recording true zero, I use the option “0 but true”.

    Conclusion


    The pid file locking method is the most reliable way to ensure that the program works in a single copy.

    The check_proc function and the connection of the Fcntl module can be moved to a separate module (for example, with the name MacLeod.pm), in this case, ensuring the operation of the program in one instance will be done in only two lines:

    use MacLeod;
    check_proc('/tmp/testscript.pid') and die "Скрипт уже запущен, запуск дубля отклонен!\n";
    

    Or, the check can be made a little more detailed:

    use MacLeod;
    my $pid = check_proc('/tmp/testscript.pid');
    if ($pid) {
        die "Скрипт с пидом $pid уже запущен, запуск дубля отклонен!\n";
    }
    else {
        print "Поехали!\n";
    }
    

    In this case, the pid of the running process returned by the check_proc function is written to the $ pid variable and can be displayed in the message.

    Also popular now: