1

I'm developing a system of executables which run together (and communicate via ROS). I have a .json configuration file which is common for all the executables and a libCommon.so which is a library of functions to read certain values from the .json. The library is linked statically to all the executables in CMakeLists.txt:

set(NAME exec1)
target_link_libraries(${NAME} ... Common)

As the system is launching, all the execs need to start one after another - something like that in bash script:

./exec1 & ./exec2

etc.

The problem

The .json parser which I use is giving me assertion errors which I found out to be symptoms of the executables running their constructors and accessing the same config file at once;

So, I have tried some stuff with a global mutex (std::mutex busy), which is declared in the header and defined in cpp of the libCommon.so. Then, it is locked on entry of every function and unlocked before return statement:

Common.h

namespace jsonFunctions
{
extern std::mutex busy;
namespace ROS
{
extern double readRosRate( configFiles::fileID configID );
}
...
}
class ConfigFile
{
public:
    ConfigFile( configFiles::fileID configID )
    {
        configFileFstream.open( configFiles::filePaths.at( configID ) );
        if( configFileFstream.is_open() )
        {
            parsedFile.parse( configFileFstream );
        }
    }
    ~ConfigFile()
    {
        configFileFstream.close();
    }

public:
    jsonxx::Object parsedFile;
    
private:
    std::fstream configFileFstream;
};

Common.cpp

namespace jsonFunctions
{
std::mutex busy;
namespace ROS
{
double readRosRate( configFiles::fileID configID )
{

busy.lock();
ConfigFile* desiredConfigFile = new ConfigFile( configID );

auto rosConfig = desiredConfigFile->parsedFile.get< jsonxx::Object >( "ROS" );

delete desiredConfigFile;
busy.unlock();
return rosConfig.get< jsonxx::Number >( "rate" );
}

But this doesn't work. How am I supposed to block the executables from accessing the config file at the same time?

  • Does deleting desiredConfigFile also closes the file? If that's not the case the constructors respect the mux, but the resource you are trying to protect is left open when the second constructor tries to use it. – Fra93 Oct 25 '21 at 14:57
  • 1
    Yes, as in the snippet~ConfigFile() { configFileFstream.close(); } – Tomasz Małachowski Oct 25 '21 at 15:03
  • Right, my eyes skipped over that. I am wondering if a mutex works across executables which are not linked together. What if you print the mutex ID (address), is it the same for all the executables? – Fra93 Oct 25 '21 at 15:08
  • @FrAxl93 hmm, they differ 0x7f6083425260 0x7f72bca3d260 – Tomasz Małachowski Oct 25 '21 at 15:20
  • 1
    My best guess at the moment is that the mutex isn't doing anything and the executables are accessing the file at the same time. A similar problem has been addressed here https://stackoverflow.com/questions/49381583/c-multiple-processes-writing-to-the-same-file-interprocess-mutex although the general advice is to not do this (even though you want to read the shared file not write it, so it must be less problematic). – Fra93 Oct 25 '21 at 15:41
  • 2
    AFAIK, the only thing that is shared in a Linux shared library is the read-only code. If the library uses global variables, each process gets its own copy of those. That means, each of your executables is using its own `std::mutex` variable, and they will have no effect on each other. One option open to you would be to use a [_lock file_](https://linux.die.net/man/3/open_excl) in the local file system. Another option would be to use an [_IPC Semaphore_](https://linux.die.net/man/7/sem_overview). – Solomon Slow Oct 25 '21 at 16:49
  • 1
    See also: https://www.baeldung.com/linux/file-locking – Solomon Slow Oct 25 '21 at 16:50
  • @FrAxl93 and Solomon Slow Thank you very much, you both were right :) I tried with interprocess mutex from boost but with very little success, tried also flock() and it works as I wished :) – Tomasz Małachowski Oct 26 '21 at 04:10

1 Answers1

1

Executables live in their own memory space. They do not share memory with other executables, even if they are both using the same shared library1.

There are no interprocess mutexes in standard C++. You have to find an interprocess mutex from outside the standard.

boost has them, and most OS file system APIs can be used to create them.

Some OS APIs allow you to open files in an exclusive mode. Others rely on you created explicit locks. In some cases, this functionality may depend on the filesystem you are accessing, and even how you are accessing it.

Apparently one way to do it is to open open("unique_name.lock", O_CREAT|O_EXCL, 0777) in a specific spot you both share. Only one process can have unique_name.lock open in this way at a time.

Another is to use flock.


1 Well, read only pages are sometimes shared by some OS's between processes; like executable code. And even read-write pages can be copy-on-write shared. This kind of sharing, however, happens just "as if" it didn't happen. Also, address space layout randomization makes this optimization less useful.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524