Newer
Older
bremer-ios-app / Pods / Realm / core / realm-monorepo.xcframework / macos-x86_64_arm64 / Headers / realm / util / interprocess_mutex.hpp
/*************************************************************************
 *
 * Copyright 2016 Realm Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 **************************************************************************/

#ifndef REALM_UTIL_INTERPROCESS_MUTEX
#define REALM_UTIL_INTERPROCESS_MUTEX

#include <realm/util/features.h>
#include <realm/util/thread.hpp>
#include <realm/util/file.hpp>
#include <realm/utilities.hpp>
#include <mutex>
#include <map>
#include <thread>

#if REALM_PLATFORM_APPLE
#include <dispatch/dispatch.h>
#endif

// Enable this only on platforms where it might be needed
#if REALM_PLATFORM_APPLE || REALM_ANDROID
#define REALM_ROBUST_MUTEX_EMULATION 1
#else
#define REALM_ROBUST_MUTEX_EMULATION 0
#endif

namespace realm::util {

// fwd decl to support friend decl below
class InterprocessCondVar;

// A wrapper for a semaphore to expose a mutex interface. Unlike a real mutex,
// this can be locked and unlocked from different threads. Currently only
// implemented on Apple platforms
class SemaphoreMutex {
public:
    SemaphoreMutex() noexcept;
    ~SemaphoreMutex() noexcept;

    SemaphoreMutex(const SemaphoreMutex&) = delete;
    SemaphoreMutex& operator=(const SemaphoreMutex&) = delete;

    void lock() noexcept;
    void unlock() noexcept;
    bool try_lock() noexcept;

private:
#if REALM_PLATFORM_APPLE
    dispatch_semaphore_t m_semaphore;
#endif
};


/// Emulation of a Robust Mutex.
/// A Robust Mutex is an interprocess mutex which will automatically
/// release any locks held by a process when it crashes. Contrary to
/// Posix robust mutexes, this robust mutex is not capable of informing
/// participants that they have been granted a lock after a crash of
/// the process holding it (though it could be added if needed).

class InterprocessMutex {
public:
    InterprocessMutex();
    ~InterprocessMutex() noexcept;

    // Disable copying. Copying a locked Mutex will create a scenario
    // where the same file descriptor will be locked once but unlocked twice.
    InterprocessMutex(const InterprocessMutex&) = delete;
    InterprocessMutex& operator=(const InterprocessMutex&) = delete;

#if REALM_ROBUST_MUTEX_EMULATION || defined(_WIN32)
    struct SharedPart {
    };
#else
    using SharedPart = RobustMutex;
#endif

    /// You need to bind the emulation to a SharedPart in shared/mmapped memory.
    /// The SharedPart is assumed to have been initialized (possibly by another process)
    /// elsewhere.
    void set_shared_part(SharedPart& shared_part, const std::string& path, const std::string& mutex_name);
    void set_shared_part(SharedPart& shared_part, File&& lock_file);

    /// Destroy shared object. Potentially release system resources. Caller must
    /// ensure that the shared_part is not in use at the point of call.
    void release_shared_part();

    /// Lock the mutex. If the mutex is already locked, wait for it to be unlocked.
    void lock();

    /// Non-blocking attempt to lock the mutex. Returns true if the lock is obtained.
    /// If the lock can not be obtained return false immediately.
    bool try_lock();

    /// Unlock the mutex
    void unlock();

    /// Attempt to check if the mutex is valid (only relevant if not emulating)
    bool is_valid() noexcept;

#if REALM_ROBUST_MUTEX_EMULATION
    constexpr static bool is_robust_on_this_platform = true; // we're faking it!
#else
    constexpr static bool is_robust_on_this_platform = RobustMutex::is_robust_on_this_platform;
#endif

#if REALM_PLATFORM_APPLE
    // On Apple platforms we support locking and unlocking InterprocessMutex on
    // different threads, while on other platforms the locking thread owns the
    // mutex. The non-thread-confined version should be implementable on more
    // platforms if desired.
    constexpr static bool is_thread_confined = false;
#else
    constexpr static bool is_thread_confined = true;
#endif

private:
#if REALM_ROBUST_MUTEX_EMULATION
    struct LockInfo {
        File m_file;
#if REALM_PLATFORM_APPLE
        SemaphoreMutex m_local_mutex;
#else
        Mutex m_local_mutex;
#endif
        LockInfo() {}
        ~LockInfo() noexcept;
        // Disable copying.
        LockInfo(const LockInfo&) = delete;
        LockInfo& operator=(const LockInfo&) = delete;
    };
    /// InterprocessMutex created on the same file (same inode on POSIX) share the same LockInfo.
    /// LockInfo will be saved in a static map as a weak ptr and use the UniqueID as the key.
    /// Operations on the map need to be protected by s_mutex
    static std::map<File::UniqueID, std::weak_ptr<LockInfo>>* s_info_map;
    static Mutex* s_mutex;
    /// We manually initialize these static variables when first needed,
    /// creating them on the heap so that they last for the entire lifetime
    /// of the process. The destructor of these is never called; the
    /// process will clean up their memory when exiting. It is not enough
    /// to count instances of InterprocessMutex and clean up these statics when
    /// the count reaches zero because the program can create more
    /// InterprocessMutex instances before the process ends, so we really need
    /// these variables for the entire lifetime of the process.
    static std::once_flag s_init_flag;
    static void initialize_statics();

    /// Only used for release_shared_part
    std::string m_filename;
    File::UniqueID m_fileuid;
    std::shared_ptr<LockInfo> m_lock_info;

    /// Free the lock info hold by this instance.
    /// If it is the last reference, underly resources will be freed as well.
    void free_lock_info();
#else
    SharedPart* m_shared_part = nullptr;

#ifdef _WIN32
    HANDLE m_handle = 0;
#endif

#endif
    friend class InterprocessCondVar;
};

inline InterprocessMutex::InterprocessMutex()
{
#if REALM_ROBUST_MUTEX_EMULATION
    std::call_once(s_init_flag, initialize_statics);
#endif
}

inline InterprocessMutex::~InterprocessMutex() noexcept
{
#ifdef _WIN32
    if (m_handle) {
        bool b = CloseHandle(m_handle);
        REALM_ASSERT_RELEASE(b);
    }
#endif

#if REALM_ROBUST_MUTEX_EMULATION
    free_lock_info();
#endif
}

#if REALM_ROBUST_MUTEX_EMULATION
inline InterprocessMutex::LockInfo::~LockInfo() noexcept
{
    if (m_file.is_attached()) {
        m_file.close();
    }
}

inline void InterprocessMutex::free_lock_info()
{
    // It has not been initialized yet.
    if (!m_lock_info)
        return;

    std::lock_guard<Mutex> guard(*s_mutex);

    m_lock_info.reset();
    if ((*s_info_map)[m_fileuid].expired()) {
        s_info_map->erase(m_fileuid);
    }
    m_filename.clear();
}

inline void InterprocessMutex::initialize_statics()
{
    s_mutex = new Mutex();
    s_info_map = new std::map<File::UniqueID, std::weak_ptr<LockInfo>>();
}
#endif

inline void InterprocessMutex::set_shared_part(SharedPart& shared_part, const std::string& path,
                                               const std::string& mutex_name)
{
#if REALM_ROBUST_MUTEX_EMULATION
    static_cast<void>(shared_part);

    free_lock_info();
    if (path.size() == 0) {
        m_filename = make_temp_file(mutex_name.c_str());
    }
    else {
        m_filename = path + "." + mutex_name + ".mx";
    }

    std::lock_guard<Mutex> guard(*s_mutex);

    // Try to get the file uid if the file exists
    if (auto uid = File::get_unique_id(m_filename)) {
        m_fileuid = std::move(*uid);
        auto result = s_info_map->find(m_fileuid);
        if (result != s_info_map->end()) {
            // File exists and the lock info has been created in the map.
            m_lock_info = result->second.lock();
            return;
        }
    }

    // LockInfo has not been created yet.
    m_lock_info = std::make_shared<LockInfo>();
    // Always use mod_Write to open file and retreive the uid in case other process
    // deletes the file.
    m_lock_info->m_file.open(m_filename, File::mode_Write);
    // exFAT does not allocate a unique id for the file until it's non-empty
    m_lock_info->m_file.resize(1);
    m_fileuid = m_lock_info->m_file.get_unique_id();

    (*s_info_map)[m_fileuid] = m_lock_info;
#elif defined(_WIN32)
    if (m_handle) {
        bool b = CloseHandle(m_handle);
        REALM_ASSERT_RELEASE(b);
    }
    // replace backslashes because they're significant in object namespace names
    std::string path_escaped = path;
    std::replace(path_escaped.begin(), path_escaped.end(), '\\', '/');
    std::string name = "Local\\realm_named_intermutex_" + path_escaped + mutex_name;

    std::wstring wname(name.begin(), name.end());
    m_handle = CreateMutexW(0, false, wname.c_str());
    if (!m_handle) {
        throw std::system_error(GetLastError(), std::system_category(), "Error opening mutex");
    }
#else
    m_shared_part = &shared_part;
    static_cast<void>(path);
    static_cast<void>(mutex_name);
#endif
}

inline void InterprocessMutex::set_shared_part(SharedPart& shared_part, File&& lock_file)
{
#if REALM_ROBUST_MUTEX_EMULATION
    static_cast<void>(shared_part);

    free_lock_info();

    std::lock_guard<Mutex> guard(*s_mutex);

    m_fileuid = lock_file.get_unique_id();
    auto result = s_info_map->find(m_fileuid);
    if (result == s_info_map->end()) {
        m_lock_info = std::make_shared<LockInfo>();
        m_lock_info->m_file = std::move(lock_file);
        (*s_info_map)[m_fileuid] = m_lock_info;
    }
    else {
        // File exists and the lock info has been created in the map.
        m_lock_info = result->second.lock();
        lock_file.close();
    }
#else
    m_shared_part = &shared_part;
    static_cast<void>(lock_file);
#endif
}

inline void InterprocessMutex::release_shared_part()
{
#if REALM_ROBUST_MUTEX_EMULATION
    if (!m_filename.empty())
        File::try_remove(m_filename);

    free_lock_info();
#else
    m_shared_part = nullptr;
#endif
}

inline void InterprocessMutex::lock()
{
#if REALM_ROBUST_MUTEX_EMULATION
    std::unique_lock mutex_lock(m_lock_info->m_local_mutex);
    m_lock_info->m_file.lock();
    mutex_lock.release();
#else

#ifdef _WIN32
    DWORD d = WaitForSingleObject(m_handle, INFINITE);
    REALM_ASSERT_RELEASE(d != WAIT_FAILED);
#else
    REALM_ASSERT(m_shared_part);
    m_shared_part->lock([]() {});
#endif
#endif
}

inline bool InterprocessMutex::try_lock()
{
#if REALM_ROBUST_MUTEX_EMULATION
    std::unique_lock mutex_lock(m_lock_info->m_local_mutex, std::try_to_lock_t());
    if (!mutex_lock.owns_lock()) {
        return false;
    }
    bool success = m_lock_info->m_file.try_lock();
    if (success) {
        mutex_lock.release();
        return true;
    }
    else {
        return false;
    }
#else

#ifdef _WIN32
    DWORD ret = WaitForSingleObject(m_handle, 0);
    REALM_ASSERT_RELEASE(ret != WAIT_FAILED);

    if (ret == WAIT_OBJECT_0) {
        return true;
    }
    else {
        return false;
    }
#else
    REALM_ASSERT(m_shared_part);
    return m_shared_part->try_lock([]() {});
#endif
#endif
}


inline void InterprocessMutex::unlock()
{
#if REALM_ROBUST_MUTEX_EMULATION
    m_lock_info->m_file.unlock();
    m_lock_info->m_local_mutex.unlock();
#else
#ifdef _WIN32
    bool b = ReleaseMutex(m_handle);
    REALM_ASSERT_RELEASE(b);
#else
    REALM_ASSERT(m_shared_part);
    m_shared_part->unlock();
#endif
#endif
}


inline bool InterprocessMutex::is_valid() noexcept
{
#if REALM_ROBUST_MUTEX_EMULATION
    return true;
#elif defined(_WIN32)
    // There is no safe way of testing if the m_handle mutex handle is valid on Windows, without having bad side
    // effects for the cases where it is indeed invalid. If m_handle contains an arbitrary value, it might by
    // coincidence be equal to a real live handle of another kind. This excludes a try_lock implementation and many
    // other ideas.
    return true;
#else
    REALM_ASSERT(m_shared_part);
    return m_shared_part->is_valid();
#endif
}


} // namespace realm::util

#endif // #ifndef REALM_UTIL_INTERPROCESS_MUTEX