/* Efficient large actor read-write lock (C) 2016-2017 Niall Douglas (23 commits) File Created: Aug 2016 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 in the accompanying file Licence.txt or 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. Distributed under the Boost Software License, Version 1.0. (See accompanying file Licence.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ #ifndef LLFIO_SHARED_FS_MUTEX_MEMORY_MAP_HPP #define LLFIO_SHARED_FS_MUTEX_MEMORY_MAP_HPP #include "../../map_handle.hpp" #include "base.hpp" #include "quickcpplib/algorithm/hash.hpp" #include "quickcpplib/algorithm/small_prng.hpp" #include "quickcpplib/spinlock.hpp" //! \file memory_map.hpp Provides algorithm::shared_fs_mutex::memory_map LLFIO_V2_NAMESPACE_BEGIN namespace algorithm { namespace shared_fs_mutex { /*! \class memory_map \brief Many entity memory mapped shared/exclusive file system based lock \tparam Hasher A STL compatible hash algorithm to use (defaults to `fnv1a_hash`) \tparam HashIndexSize The size in bytes of the hash index to use (defaults to 4Kb) \tparam SpinlockType The type of spinlock to use (defaults to a `SharedMutex` concept spinlock) This is the highest performing filing system mutex in LLFIO, but it comes with a long list of potential gotchas. It works by creating a random temporary file somewhere on the system and placing its path in a file at the lock file location. The random temporary file is mapped into memory by all processes using the lock where an open addressed hash table is kept. Each entity is hashed into somewhere in the hash table and its individual spin lock is used to implement the exclusion. As with `byte_ranges`, each entity is locked individually in sequence but if a particular lock fails, all are unlocked and the list is randomised before trying again. Because this locking implementation is entirely implemented in userspace using shared memory without any kernel syscalls, performance is probably as fast as any many-arbitrary-entity shared locking system could be. As it uses shared memory, this implementation of `shared_fs_mutex` cannot work over a networked drive. If you attempt to open this lock on a network drive and the first user of the lock is not on this local machine, `errc::no_lock_available` will be returned from the constructor. - Linear complexity to number of concurrent users up until hash table starts to get full or hashed entries collide. - Sudden power loss during use is recovered from. - Safe for multithreaded usage of the same instance. - In the lightly contended case, an order of magnitude faster than any other `shared_fs_mutex` algorithm. Caveats: - No ability to sleep until a lock becomes free, so CPUs are spun at 100%. - Sudden process exit with locks held will deadlock all other users. - Exponential complexity to number of entities being concurrently locked. - Exponential complexity to concurrency if entities hash to the same cache line. Most SMP and especially NUMA systems have a finite bandwidth for atomic compare and swap operations, and every attempt to lock or unlock an entity under this implementation is several of those operations. Under heavy contention, whole system performance very noticeably nose dives from excessive atomic operations, things like audio and the mouse pointer will stutter. - Sometimes different entities hash to the same offset and collide with one another, causing very poor performance. - Memory mapped files need to be cache unified with normal i/o in your OS kernel. Known OSs which don't use a unified cache for memory mapped and normal i/o are QNX, OpenBSD. Furthermore, doing normal i/o and memory mapped i/o to the same file needs to not corrupt the file. In the past, there have been editions of the Linux kernel and the OS X kernel which did this. - If your OS doesn't have sane byte range locks (OS X, BSD, older Linuxes) and multiple objects in your process use the same lock file, misoperation will occur. - Requires `handle::current_path()` to be working. \todo memory_map::_hash_entities needs to hash x16, x8 and x4 at a time to encourage auto vectorisation */ template