added iOS source code
[wl-app.git] / iOS / Pods / Realm / include / core / realm / util / interprocess_mutex.hpp
1 /*************************************************************************
2  *
3  * Copyright 2016 Realm Inc.
4  *
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
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  *
17  **************************************************************************/
18
19 #ifndef REALM_UTIL_INTERPROCESS_MUTEX
20 #define REALM_UTIL_INTERPROCESS_MUTEX
21
22 #include <realm/util/features.h>
23 #include <realm/util/thread.hpp>
24 #include <realm/util/file.hpp>
25 #include <realm/utilities.hpp>
26 #include <mutex>
27 #include <map>
28 #include <iostream>
29
30 // Enable this only on platforms where it might be needed
31 #if REALM_PLATFORM_APPLE || REALM_ANDROID
32 #define REALM_ROBUST_MUTEX_EMULATION
33 #endif
34
35 namespace realm {
36 namespace util {
37
38 // fwd decl to support friend decl below
39 class InterprocessCondVar;
40
41
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).
48
49 class InterprocessMutex {
50 public:
51     InterprocessMutex();
52     ~InterprocessMutex() noexcept;
53
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;
58
59 #if defined(REALM_ROBUST_MUTEX_EMULATION) || defined(_WIN32)
60     struct SharedPart {
61     };
62 #else
63     using SharedPart = RobustMutex;
64 #endif
65
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)
68     /// elsewhere.
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);
71
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();
75
76     /// Lock the mutex. If the mutex is already locked, wait for it to be unlocked.
77     void lock();
78
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.
81     bool try_lock();
82
83     /// Unlock the mutex
84     void unlock();
85
86     /// Attempt to check if the mutex is valid (only relevant if not emulating)
87     bool is_valid() noexcept;
88
89     static bool is_robust_on_this_platform()
90     {
91 #ifdef REALM_ROBUST_MUTEX_EMULATION
92         return true; // we're faking it!
93 #else
94         return RobustMutex::is_robust_on_this_platform();
95 #endif
96     }
97
98 private:
99 #ifdef REALM_ROBUST_MUTEX_EMULATION
100     struct LockInfo {
101         File m_file;
102         Mutex m_local_mutex;
103         LockInfo() {}
104         ~LockInfo() noexcept;
105         // Disable copying.
106         LockInfo(const LockInfo&) = delete;
107         LockInfo& operator=(const LockInfo&) = delete;
108     };
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();
124
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;
129
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();
133 #else
134     SharedPart* m_shared_part = nullptr;
135
136 #ifdef _WIN32
137     HANDLE m_handle = 0;
138 #endif
139
140 #endif
141     friend class InterprocessCondVar;
142 };
143
144 inline InterprocessMutex::InterprocessMutex()
145 {
146 #ifdef REALM_ROBUST_MUTEX_EMULATION
147     std::call_once(s_init_flag, initialize_statics);
148 #endif
149 }
150
151 inline InterprocessMutex::~InterprocessMutex() noexcept
152 {
153 #ifdef _WIN32
154     if (m_handle) {
155         bool b = CloseHandle(m_handle);
156         REALM_ASSERT_RELEASE(b);
157     }
158 #endif
159
160 #ifdef REALM_ROBUST_MUTEX_EMULATION
161     free_lock_info();
162 #endif
163 }
164
165 #ifdef REALM_ROBUST_MUTEX_EMULATION
166 inline InterprocessMutex::LockInfo::~LockInfo() noexcept
167 {
168     if (m_file.is_attached()) {
169         m_file.close();
170     }
171 }
172
173 inline void InterprocessMutex::free_lock_info()
174 {
175     // It has not been initialized yet.
176     if (!m_lock_info)
177         return;
178
179     std::lock_guard<Mutex> guard(*s_mutex);
180
181     m_lock_info.reset();
182     if ((*s_info_map)[m_fileuid].expired()) {
183         s_info_map->erase(m_fileuid);
184     }
185     m_filename.clear();
186 }
187
188 inline void InterprocessMutex::initialize_statics()
189 {
190     s_mutex = new Mutex();
191     s_info_map = new std::map<File::UniqueID, std::weak_ptr<LockInfo>>();
192 }
193 #endif
194
195 inline void InterprocessMutex::set_shared_part(SharedPart& shared_part, const std::string& path,
196                                                const std::string& mutex_name)
197 {
198 #ifdef REALM_ROBUST_MUTEX_EMULATION
199     static_cast<void>(shared_part);
200
201     free_lock_info();
202
203     m_filename = path + "." + mutex_name + ".mx";
204
205     std::lock_guard<Mutex> guard(*s_mutex);
206
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();
213             return;
214         }
215     }
216
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
220     // deletes the file.
221     m_lock_info->m_file.open(m_filename, File::mode_Write);
222     m_fileuid = m_lock_info->m_file.get_unique_id();
223
224     (*s_info_map)[m_fileuid] = m_lock_info;
225 #elif defined(_WIN32)
226     if (m_handle) {
227         bool b = CloseHandle(m_handle);
228         REALM_ASSERT_RELEASE(b);
229     }
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;
234
235     std::wstring wname(name.begin(), name.end());
236     m_handle = CreateMutexW(0, false, wname.c_str());
237     if (!m_handle) {
238         throw std::system_error(std::error_code(::GetLastError(), std::system_category()), "Error opening mutex");
239     }
240 #else
241     m_shared_part = &shared_part;
242     static_cast<void>(path);
243     static_cast<void>(mutex_name);
244 #endif
245 }
246
247 inline void InterprocessMutex::set_shared_part(SharedPart& shared_part, File&& lock_file)
248 {
249 #ifdef REALM_ROBUST_MUTEX_EMULATION
250     static_cast<void>(shared_part);
251
252     free_lock_info();
253
254     std::lock_guard<Mutex> guard(*s_mutex);
255
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;
262     }
263     else {
264         // File exists and the lock info has been created in the map.
265         m_lock_info = result->second.lock();
266         lock_file.close();
267     }
268 #else
269     m_shared_part = &shared_part;
270     static_cast<void>(lock_file);
271 #endif
272 }
273
274 inline void InterprocessMutex::release_shared_part()
275 {
276 #ifdef REALM_ROBUST_MUTEX_EMULATION
277     if (!m_filename.empty())
278         File::try_remove(m_filename);
279
280     free_lock_info();
281 #else
282     m_shared_part = nullptr;
283 #endif
284 }
285
286 inline void InterprocessMutex::lock()
287 {
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();
292 #else
293
294 #ifdef _WIN32
295     DWORD d = WaitForSingleObject(m_handle, INFINITE);
296     REALM_ASSERT_RELEASE(d != WAIT_FAILED);
297 #else
298     REALM_ASSERT(m_shared_part);
299     m_shared_part->lock([]() {});
300 #endif
301 #endif
302 }
303
304 inline bool InterprocessMutex::try_lock()
305 {
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()) {
309         return false;
310     }
311     bool success = m_lock_info->m_file.try_lock_exclusive();
312     if (success) {
313         mutex_lock.release();
314         return true;
315     }
316     else {
317         return false;
318     }
319 #else
320
321 #ifdef _WIN32
322     DWORD ret = WaitForSingleObject(m_handle, 0);
323     REALM_ASSERT_RELEASE(ret != WAIT_FAILED);
324
325     if (ret == WAIT_OBJECT_0) {
326         return true;
327     }
328     else {
329         return false;
330     }
331 #else
332     REALM_ASSERT(m_shared_part);
333     return m_shared_part->try_lock([]() {});
334 #endif
335 #endif
336 }
337
338
339 inline void InterprocessMutex::unlock()
340 {
341 #ifdef REALM_ROBUST_MUTEX_EMULATION
342     m_lock_info->m_file.unlock();
343     m_lock_info->m_local_mutex.unlock();
344 #else
345 #ifdef _WIN32
346     bool b = ReleaseMutex(m_handle);
347     REALM_ASSERT_RELEASE(b);
348 #else
349     REALM_ASSERT(m_shared_part);
350     m_shared_part->unlock();
351 #endif
352 #endif
353 }
354
355
356 inline bool InterprocessMutex::is_valid() noexcept
357 {
358 #ifdef REALM_ROBUST_MUTEX_EMULATION
359     return true;
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.
364     return true;
365 #else
366     REALM_ASSERT(m_shared_part);
367     return m_shared_part->is_valid();
368 #endif
369 }
370
371
372 } // namespace util
373 } // namespace realm
374
375 #endif // #ifndef REALM_UTIL_INTERPROCESS_MUTEX