This is an archive of the discontinued LLVM Phabricator instance.

Use kevent queue for detecting file unlocking in LockFileManager
Needs ReviewPublic

Authored by PaulTaykalo on Nov 21 2019, 1:20 PM.

Details

Summary

LockFileManager is used for acquiring and watching .lock files
Current implementation that checks for lock release, is using exponentially growing timeouts.
In some cases, dependent module compilation time is relatively long (tens of seconds). In this case, timeouts could lead to the situation, when a process, watching for a lock release will just sleep for another ten of seconds after lock release.

Provided implementation is using kqueue for listening for file system changes process termination. This allows preventing unneded timeouts in the most of the cases.

Diff Detail

Event Timeline

PaulTaykalo created this revision.Nov 21 2019, 1:20 PM

This is the somewhat an example of program that is using kevent queue. additional timeouts are added to test cases such as deletion of the file right after the queue was created but notlistened yet.
Kevent testing program (Pass event for first parameter)

#include <iostream>
#include <sys/event.h>
#include <unistd.h>
#include <fcntl.h>

// Class that employs RAII to save a file descriptor
class FileDescriptorKeeper {
    int FileDescriptor;

public:
    FileDescriptorKeeper(int Descriptor) { FileDescriptor = Descriptor; }

    ~FileDescriptorKeeper() {
        close(FileDescriptor);
        std::cout << "File successfully closed" << std::endl;
    }
};

bool ListenForAFileDeletion(char *path, int PID) {
    int LockFileDescriptor;
    int EventQueue;

    // Opening file for lock descriptor

    std::cout << "Opening file: " << path << std::endl;

    if ((LockFileDescriptor = open(path, O_RDONLY)) == -1) {
        std::cout << "[ERROR] File open error. The file was probably removed "
                  << path << std::endl;
        return false;
    }

    std::cout
            << "Will wait for 5 seconds to check if kqueu fails if file was deleted before event was added"
            << std::endl;

    sleep(5);
    FileDescriptorKeeper fileRAII = FileDescriptorKeeper(LockFileDescriptor);

    std::cout << "Creating queue: " << std::endl;

    if ((EventQueue = kqueue()) == -1) {
        std::cout << "[ERROR] Queue cannot be created " << errno << std::endl;
        return false;
    }

    FileDescriptorKeeper queueRAII = FileDescriptorKeeper(EventQueue);

    int ListeningEventsCount = 2;
    struct kevent ListeningEvents[2];
    struct kevent *FileRemovingEvent = &ListeningEvents[0];
    struct kevent *ProcessTerminationEvent = &ListeningEvents[1];

    EV_SET(FileRemovingEvent, LockFileDescriptor, EVFILT_VNODE,
           EV_ADD | EV_ENABLE | EV_ONESHOT | EV_CLEAR | EV_VANISHED |
           EV_DISPATCH2, NOTE_DELETE, 0, 0);

    EV_SET(ProcessTerminationEvent, PID, EVFILT_PROC,
           EV_ADD | EV_ENABLE | EV_ONESHOT, NOTE_EXIT, 0, 0);


    struct timespec timeout = {10, 0};
    struct kevent ReceivedEvent;

    int RegisteingResult =
            kevent(EventQueue, ListeningEvents, ListeningEventsCount, NULL, 0,
                   NULL);

    std::cout << "Registering events and start listening waiting for 5 sec"
              << RegisteingResult << std::endl;
    sleep(5);

    std::cout << "Listening event" << std::endl;

    int ListeningResult =
            kevent(EventQueue, NULL, 0, &ReceivedEvent, 1, &timeout);

    // File was successfully deleted
    if (ListeningResult > 0 && (ReceivedEvent.fflags & NOTE_DELETE &&
                            ReceivedEvent.filter == EVFILT_VNODE)) {
        std::cout << "[OK] File was deleted: " << path << std::endl;
        return true;
    }

    // Process owning the lock died
    if (ListeningResult > 0 && (ReceivedEvent.fflags & NOTE_EXIT &&
                                ReceivedEvent.filter == EVFILT_PROC)) {
        std::cout << "[OK] PID exited: " << path << std::endl;
        return true;
    }

    if (ListeningResult < 0) {
        std::cout << "[ERROR] Queue ended up with error" << errno << std::endl;
        return false;
    }

    if (ListeningResult == 0) {
        std::cout << "[OK] Queue has timed out" << std::endl;
        return false;
    }

    std::cout << "[??] Unexpected event received" << ReceivedEvent.fflags << ":"
              << ReceivedEvent.filter << std::endl;
    return false;
}

int main(int argc, char **argv) {
    if (argc < 2) {
        std::cout << "Please specify file to be deleted" << std::endl;
    }

    if (argc < 3) {
        std::cout << "Please specify pid to be listened to" << std::endl;
    }

    char *FileName = argv[1];
    int PID = atoi(argv[2]);

    ListenForAFileDeletion(FileName, PID);

    return 0;
}
bruno resigned from this revision.May 19 2021, 6:13 PM