MemoryMappedFile writes nothing when exclusive is true

So I have the following simple test code.

auto testFile = juce::File::getSpecialLocation(juce::File::SpecialLocationType::userDesktopDirectory).getChildFile ("mmaptest.txt");
testFile.deleteFile();
testFile.create();

{
    auto os = testFile.createOutputStream();
    jassert(os != nullptr);
    os->writeRepeatedByte(0, 7);
    os->writeByte(0xf);
}

{
    juce::MemoryMappedFile mmf (testFile, juce::MemoryMappedFile::AccessMode::readWrite, true);
    jassert (mmf.getSize() == 8);
    std::memcpy (mmf.getData(), "asdf", 4);
}

When I run that from within a plugin on macOS 14 on an ARM Mac, it will generate a file, which contains the following bytes after closing the application that wrote it:

0x00000000 0000000F

When I change that third exclusive argument in the MemoryMappedFile constructor to false, it contains

0x61736466 0000000F

So obviously nothing at all is written to the file in exclusive mode. The documentation on that argument says:

If exclusive is false then other apps can also open the same memory mapped file and use this mapping as an effective way of communicating. If exclusive is true then the mapped file will be opened exclusively - preventing other apps to access the file which may improve the performance of accessing the file.

From my understanding this means that I can’t expect to access partly written data to be accessible from another process while the memory mapped file is opened by a process, however I would have expected that the data will be written and readable from other processes after the memory mapped file has been closed. Furthermore, when re-opening the file I wrote to with the same application, it also sees an empty file.

Did I just understand that flag wrong and this is completely expected behavior and if so, what are the use-cases of this mode then? Or did I spot a bug here.

Note, this is still on JUCE 7, but I don’t think anything changed in that area with JUCE 8?

Don’t you need to close the file first, before opening it again using the MemoryMappedFile? You’re basically saying don’t allow this code to share the file with anyone, which would mean it can’t access the file because it’s already open. At least I think that’s the case here.

Doesn‘t juce::FileOutputStream close the file on destruction? That‘s why I put that in an extra scope to ensure it’s destructed and therefore closed.

1 Like

From juce_FileOutputStream.h:

    Destroying a FileOutputStream object does not force the operating system
    to write the buffered data to disk immediately. If this is required you
    should call flush() before triggering the destructor.

I assume if it has not flushed yet, then it cannot be closed yet.

Not sure about that comment, but this is the code from the class destructor:

FileOutputStream::~FileOutputStream()
{
    flushBuffer();
    closeHandle();
}

Where closeHandle is implemented like that

void FileOutputStream::closeHandle()
{
    if (fileHandle != nullptr)
    {
        close (getFD (fileHandle));
        fileHandle = nullptr;
    }
}

So I’m pretty sure the file should really have been closed then…

But that only calls flushBuffer(). It does not call flush(), which is where fsync is called on the actual file handle. Odd that it does call closeHandle(), though. Not sure how this works under the hood, but maybe you could try adding a call to flush() before the end of that block, just to see?