c++linuxboostboost-interprocessfilelock

boost::interprocess::file_lock erroneous behavior when used with std::ostream


I am trying to use file_lock for restricting multiple instances of the same program running at the same time (Implementing something mentioned in this answer). I am using 1.66 version of boost on Linux.

Before locking the file, I make sure that file exists (by opening it using std::ofstream with std::ios::app). I noticed one thing, if we close the stream, then file_lock is automatically unlocked and hence, allowing multiple instance of the same program to run at the same time.

The program below doesn't work as file_lock is automatically released.

int main(int argc, char *argv[])
{
    namespace bipc = boost::interprocess;
    if (argc < 2)
        return 0;

    std::string path = argv[1];
    std::string lock_path = "/var/lock/" + path + ".lock";

    std::ofstream stream(lock_path, std::ios::app);

    bipc::file_lock lock(lock_path.c_str());

    if (!lock.try_lock())
        throw std::runtime_error("Multiple instance");

    std::cout << "Running" << std::endl;
    stream.close();
    while (true)
        std::this_thread::sleep_for(std::chrono::seconds(1));
    return 0;
}

However, the two programs below works.

Looks like if we have opened a file while trying to acquire the file_lock, then we need to keep that file opened till we want to hold the lock. If we close the file, then lock is automatically released. I am not sure if this is a bug. Can someone help me with the reason of such behavior?

int main(int argc, char *argv[])
{
    namespace bipc = boost::interprocess;
    if (argc < 2)
        return 0;

    std::string path = argv[1];
    std::string lock_path = "/var/lock/" + path + ".lock";

    std::ofstream stream(lock_path, std::ios::app);
    bipc::file_lock lock(lock_path.c_str());

    if (!lock.try_lock())
        throw std::runtime_error("Multiple instance");

    std::cout << "Running" << std::endl;
    while (true)
        std::this_thread::sleep_for(std::chrono::seconds(1));
    return 0;
}

And

int main(int argc, char *argv[])
{
    namespace bipc = boost::interprocess;
    if (argc < 2)
        return 0;

    std::string path = argv[1];
    std::string lock_path = "/var/lock/" + path + ".lock";

    {
        std::ofstream stream(lock_path, std::ios::app);
    }
    bipc::file_lock lock(lock_path.c_str());

    if (!lock.try_lock())
        throw std::runtime_error("Multiple instance");

    std::cout << "Running" << std::endl;
    while (true)
        std::this_thread::sleep_for(std::chrono::seconds(1));
    return 0;
}

Solution

  • I have found the reason. It was due to boost::interprocess::file_lock were implemented using Classic POSIX File-locks.

    This link explains the issue with POSIX locks and hence, with boost file_locks.

    More troublingly, the standard states that all locks held by a process are dropped any time the process closes any file descriptor that corresponds to the locked file, even if those locks were made using a still-open file descriptor. It is this detail that catches most programmers by surprise as it requires that a program take extra care not to close a file descriptor until it is certain that locks held on that file are able to be dropped.

    Looks like I should be either using flock or some other platform library which uses flock.