1 /*************************************************************************
3 * Copyright 2016 Realm Inc.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 **************************************************************************/
19 #ifndef REALM_UTIL_INTERPROCESS_MUTEX
20 #define REALM_UTIL_INTERPROCESS_MUTEX
22 #include <realm/util/features.h>
23 #include <realm/util/thread.hpp>
24 #include <realm/util/file.hpp>
25 #include <realm/utilities.hpp>
30 // Enable this only on platforms where it might be needed
31 #if REALM_PLATFORM_APPLE || REALM_ANDROID
32 #define REALM_ROBUST_MUTEX_EMULATION
38 // fwd decl to support friend decl below
39 class InterprocessCondVar;
42 /// Emulation of a Robust Mutex.
43 /// A Robust Mutex is an interprocess mutex which will automatically
44 /// release any locks held by a process when it crashes. Contrary to
45 /// Posix robust mutexes, this robust mutex is not capable of informing
46 /// participants that they have been granted a lock after a crash of
47 /// the process holding it (though it could be added if needed).
49 class InterprocessMutex {
52 ~InterprocessMutex() noexcept;
54 // Disable copying. Copying a locked Mutex will create a scenario
55 // where the same file descriptor will be locked once but unlocked twice.
56 InterprocessMutex(const InterprocessMutex&) = delete;
57 InterprocessMutex& operator=(const InterprocessMutex&) = delete;
59 #if defined(REALM_ROBUST_MUTEX_EMULATION) || defined(_WIN32)
63 using SharedPart = RobustMutex;
66 /// You need to bind the emulation to a SharedPart in shared/mmapped memory.
67 /// The SharedPart is assumed to have been initialized (possibly by another process)
69 void set_shared_part(SharedPart& shared_part, const std::string& path, const std::string& mutex_name);
70 void set_shared_part(SharedPart& shared_part, File&& lock_file);
72 /// Destroy shared object. Potentially release system resources. Caller must
73 /// ensure that the shared_part is not in use at the point of call.
74 void release_shared_part();
76 /// Lock the mutex. If the mutex is already locked, wait for it to be unlocked.
79 /// Non-blocking attempt to lock the mutex. Returns true if the lock is obtained.
80 /// If the lock can not be obtained return false immediately.
86 /// Attempt to check if the mutex is valid (only relevant if not emulating)
87 bool is_valid() noexcept;
89 static bool is_robust_on_this_platform()
91 #ifdef REALM_ROBUST_MUTEX_EMULATION
92 return true; // we're faking it!
94 return RobustMutex::is_robust_on_this_platform();
99 #ifdef REALM_ROBUST_MUTEX_EMULATION
104 ~LockInfo() noexcept;
106 LockInfo(const LockInfo&) = delete;
107 LockInfo& operator=(const LockInfo&) = delete;
109 /// InterprocessMutex created on the same file (same inode on POSIX) share the same LockInfo.
110 /// LockInfo will be saved in a static map as a weak ptr and use the UniqueID as the key.
111 /// Operations on the map need to be protected by s_mutex
112 static std::map<File::UniqueID, std::weak_ptr<LockInfo>>* s_info_map;
113 static Mutex* s_mutex;
114 /// We manually initialize these static variables when first needed,
115 /// creating them on the heap so that they last for the entire lifetime
116 /// of the process. The destructor of these is never called; the
117 /// process will clean up their memory when exiting. It is not enough
118 /// to count instances of InterprocessMutex and clean up these statics when
119 /// the count reaches zero because the program can create more
120 /// InterprocessMutex instances before the process ends, so we really need
121 /// these variables for the entire lifetime of the process.
122 static std::once_flag s_init_flag;
123 static void initialize_statics();
125 /// Only used for release_shared_part
126 std::string m_filename;
127 File::UniqueID m_fileuid;
128 std::shared_ptr<LockInfo> m_lock_info;
130 /// Free the lock info hold by this instance.
131 /// If it is the last reference, underly resources will be freed as well.
132 void free_lock_info();
134 SharedPart* m_shared_part = nullptr;
141 friend class InterprocessCondVar;
144 inline InterprocessMutex::InterprocessMutex()
146 #ifdef REALM_ROBUST_MUTEX_EMULATION
147 std::call_once(s_init_flag, initialize_statics);
151 inline InterprocessMutex::~InterprocessMutex() noexcept
155 bool b = CloseHandle(m_handle);
156 REALM_ASSERT_RELEASE(b);
160 #ifdef REALM_ROBUST_MUTEX_EMULATION
165 #ifdef REALM_ROBUST_MUTEX_EMULATION
166 inline InterprocessMutex::LockInfo::~LockInfo() noexcept
168 if (m_file.is_attached()) {
173 inline void InterprocessMutex::free_lock_info()
175 // It has not been initialized yet.
179 std::lock_guard<Mutex> guard(*s_mutex);
182 if ((*s_info_map)[m_fileuid].expired()) {
183 s_info_map->erase(m_fileuid);
188 inline void InterprocessMutex::initialize_statics()
190 s_mutex = new Mutex();
191 s_info_map = new std::map<File::UniqueID, std::weak_ptr<LockInfo>>();
195 inline void InterprocessMutex::set_shared_part(SharedPart& shared_part, const std::string& path,
196 const std::string& mutex_name)
198 #ifdef REALM_ROBUST_MUTEX_EMULATION
199 static_cast<void>(shared_part);
203 m_filename = path + "." + mutex_name + ".mx";
205 std::lock_guard<Mutex> guard(*s_mutex);
207 // Try to get the file uid if the file exists
208 if (File::get_unique_id(m_filename, m_fileuid)) {
209 auto result = s_info_map->find(m_fileuid);
210 if (result != s_info_map->end()) {
211 // File exists and the lock info has been created in the map.
212 m_lock_info = result->second.lock();
217 // LockInfo has not been created yet.
218 m_lock_info = std::make_shared<LockInfo>();
219 // Always use mod_Write to open file and retreive the uid in case other process
221 m_lock_info->m_file.open(m_filename, File::mode_Write);
222 m_fileuid = m_lock_info->m_file.get_unique_id();
224 (*s_info_map)[m_fileuid] = m_lock_info;
225 #elif defined(_WIN32)
227 bool b = CloseHandle(m_handle);
228 REALM_ASSERT_RELEASE(b);
230 // replace backslashes because they're significant in object namespace names
231 std::string path_escaped = path;
232 std::replace(path_escaped.begin(), path_escaped.end(), '\\', '/');
233 std::string name = "Local\\realm_named_intermutex_" + path_escaped + mutex_name;
235 std::wstring wname(name.begin(), name.end());
236 m_handle = CreateMutexW(0, false, wname.c_str());
238 throw std::system_error(std::error_code(::GetLastError(), std::system_category()), "Error opening mutex");
241 m_shared_part = &shared_part;
242 static_cast<void>(path);
243 static_cast<void>(mutex_name);
247 inline void InterprocessMutex::set_shared_part(SharedPart& shared_part, File&& lock_file)
249 #ifdef REALM_ROBUST_MUTEX_EMULATION
250 static_cast<void>(shared_part);
254 std::lock_guard<Mutex> guard(*s_mutex);
256 m_fileuid = lock_file.get_unique_id();
257 auto result = s_info_map->find(m_fileuid);
258 if (result == s_info_map->end()) {
259 m_lock_info = std::make_shared<LockInfo>();
260 m_lock_info->m_file = std::move(lock_file);
261 (*s_info_map)[m_fileuid] = m_lock_info;
264 // File exists and the lock info has been created in the map.
265 m_lock_info = result->second.lock();
269 m_shared_part = &shared_part;
270 static_cast<void>(lock_file);
274 inline void InterprocessMutex::release_shared_part()
276 #ifdef REALM_ROBUST_MUTEX_EMULATION
277 if (!m_filename.empty())
278 File::try_remove(m_filename);
282 m_shared_part = nullptr;
286 inline void InterprocessMutex::lock()
288 #ifdef REALM_ROBUST_MUTEX_EMULATION
289 std::unique_lock<Mutex> mutex_lock(m_lock_info->m_local_mutex);
290 m_lock_info->m_file.lock_exclusive();
291 mutex_lock.release();
295 DWORD d = WaitForSingleObject(m_handle, INFINITE);
296 REALM_ASSERT_RELEASE(d != WAIT_FAILED);
298 REALM_ASSERT(m_shared_part);
299 m_shared_part->lock([]() {});
304 inline bool InterprocessMutex::try_lock()
306 #ifdef REALM_ROBUST_MUTEX_EMULATION
307 std::unique_lock<Mutex> mutex_lock(m_lock_info->m_local_mutex, std::try_to_lock_t());
308 if (!mutex_lock.owns_lock()) {
311 bool success = m_lock_info->m_file.try_lock_exclusive();
313 mutex_lock.release();
322 DWORD ret = WaitForSingleObject(m_handle, 0);
323 REALM_ASSERT_RELEASE(ret != WAIT_FAILED);
325 if (ret == WAIT_OBJECT_0) {
332 REALM_ASSERT(m_shared_part);
333 return m_shared_part->try_lock([]() {});
339 inline void InterprocessMutex::unlock()
341 #ifdef REALM_ROBUST_MUTEX_EMULATION
342 m_lock_info->m_file.unlock();
343 m_lock_info->m_local_mutex.unlock();
346 bool b = ReleaseMutex(m_handle);
347 REALM_ASSERT_RELEASE(b);
349 REALM_ASSERT(m_shared_part);
350 m_shared_part->unlock();
356 inline bool InterprocessMutex::is_valid() noexcept
358 #ifdef REALM_ROBUST_MUTEX_EMULATION
360 #elif defined(_WIN32)
361 // There is no safe way of testing if the m_handle mutex handle is valid on Windows, without having bad side effects
362 // for the cases where it is indeed invalid. If m_handle contains an arbitrary value, it might by coincidence be equal
363 // to a real live handle of another kind. This excludes a try_lock implementation and many other ideas.
366 REALM_ASSERT(m_shared_part);
367 return m_shared_part->is_valid();
375 #endif // #ifndef REALM_UTIL_INTERPROCESS_MUTEX