//------------------------------------------------------------------------------ //! @file LockTracker.cc //! @author Gerogios Bitzes //! @brief POSIX lock class //------------------------------------------------------------------------------ /************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2017 CERN/Switzerland * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* ************************************************************************/ #include "LockTracker.hh" #include #include #include EOSMGMNAMESPACE_BEGIN USE_EOSMGMNAMESPACE /*----------------------------------------------------------------------------*/ std::ostream& operator<< (std::ostream& os, const ByteRange& range) { os << "[" << range.start() << ", " << range.end() << ")"; return os; } /*----------------------------------------------------------------------------*/ std::ostream& operator<< (std::ostream& os, const Lock& lock) { os << lock.range() << " on pid " << lock.pid() << std::endl; return os; } /*----------------------------------------------------------------------------*/ void /*----------------------------------------------------------------------------*/ LockSet::add(const Lock& l) /*----------------------------------------------------------------------------*/ { Lock newlock(l); // Absorb any overlapping ranges, removing the old ones auto it = locks.begin(); while (it != locks.end()) { if (newlock.absorb(*it)) { it = locks.erase(it); } else { it++; } } // Append the consolidated superlock locks.push_back(newlock); } /*----------------------------------------------------------------------------*/ bool /*----------------------------------------------------------------------------*/ LockSet::conflict(const Lock& l) const /*----------------------------------------------------------------------------*/ { auto it = locks.begin(); while (it != locks.end()) { if (it->pid() != l.pid() && l.range().overlap(it->range())) { return true; } it++; } return false; } /*----------------------------------------------------------------------------*/ bool /*----------------------------------------------------------------------------*/ LockSet::getconflict(Lock& l) /*----------------------------------------------------------------------------*/ { auto it = locks.begin(); while (it != locks.end()) { if (it->pid() != l.pid() && l.range().overlap(it->range())) { l = *it; return true; } it++; } return false; } /*----------------------------------------------------------------------------*/ bool /*----------------------------------------------------------------------------*/ LockSet::overlap(const Lock& l) const /*----------------------------------------------------------------------------*/ { auto it = locks.begin(); while (it != locks.end()) { if (l.overlap(*it)) { return true; } it++; } return false; } /*----------------------------------------------------------------------------*/ bool /*----------------------------------------------------------------------------*/ LockSet::overlap(const ByteRange& br) const /*----------------------------------------------------------------------------*/ { auto it = locks.begin(); while (it != locks.end()) { if (br.overlap(it->range())) { return true; } it++; } return false; } /*----------------------------------------------------------------------------*/ void /*----------------------------------------------------------------------------*/ LockSet::remove(const Lock& l) /*----------------------------------------------------------------------------*/ { std::vector queued; auto it = locks.begin(); while (it != locks.end()) { std::vector newlocks = it->minus(l); if (newlocks.size() == 0) { it = locks.erase(it); continue; } if (newlocks.size() >= 1) { *it = newlocks[0]; } if (newlocks.size() == 2) { queued.push_back(newlocks[1]); } it++; } // Cannot add new items to vector while iterating, as it invalides the iterators. // Store the items in a queue and add them later. for (size_t i = 0; i < queued.size(); i++) { locks.push_back(queued[i]); } } /*----------------------------------------------------------------------------*/ size_t /*----------------------------------------------------------------------------*/ LockSet::nlocks() /*----------------------------------------------------------------------------*/ { return locks.size(); } /*----------------------------------------------------------------------------*/ size_t /*----------------------------------------------------------------------------*/ LockSet::nlocks(pid_t pid) /*----------------------------------------------------------------------------*/ { size_t res = 0; auto it = locks.begin(); while (it != locks.end()) { if (it->pid() == pid) { res++; } it++; } return res; } /*----------------------------------------------------------------------------*/ void /*----------------------------------------------------------------------------*/ LockSet::remove(pid_t pid) { std::vector survivinglocks; for (auto it = locks.begin(); it != locks.end(); ++it) { if (it->pid() != pid) { survivinglocks.push_back(*it); } } locks = survivinglocks; } /*----------------------------------------------------------------------------*/ void /*----------------------------------------------------------------------------*/ LockSet::remove(const std::string& owner) { std::vector survivinglocks; for (auto it = locks.begin(); it != locks.end(); ++it) { if (it->owner() != owner) { survivinglocks.push_back(*it); } } locks = survivinglocks; } /*----------------------------------------------------------------------------*/ std::set /*----------------------------------------------------------------------------*/ LockSet::lslocks(const std::string& owner) /*----------------------------------------------------------------------------*/ { // return all pids belonging to owner std::set owner_pids; for (auto it = locks.begin(); it != locks.end(); ++it) { // fprintf(stderr, "lock: owner=%s (%s) pid=%u true=%d\n", it->owner().c_str(), // owner.c_str(), it->pid(), // (it->owner() == owner)); // } if (it->owner() == owner) { owner_pids.insert(it->pid()); } } return owner_pids; } /*----------------------------------------------------------------------------*/ int /*----------------------------------------------------------------------------*/ LockTracker::getlk(pid_t pid, struct flock* lock) /*----------------------------------------------------------------------------*/ { std::lock_guard guard(mtx); if (lock->l_type == F_UNLCK) { // TODO signal warning, should not happen (?) return 1; } if (canLock(pid, lock)) { lock->l_type = F_UNLCK; } else { // canLock filled the blocking lock } return 1; } /*----------------------------------------------------------------------------*/ int /*----------------------------------------------------------------------------*/ LockTracker::setlk(pid_t pid, struct flock* lock, int sleep, const std::string& owner) /*----------------------------------------------------------------------------*/ { if (!sleep) { return addLock(pid, lock, owner); } size_t cnt = 0; while (!addLock(pid, lock, owner)) { cnt++; // TODO wait on condition variable? std::this_thread::sleep_for(std::chrono::milliseconds(1)); if (cnt > 10) { // we give up after 10ms return 0; } } return 1; } /*----------------------------------------------------------------------------*/ bool /*----------------------------------------------------------------------------*/ LockTracker::canLock(pid_t pid, struct flock* f_lock) /*----------------------------------------------------------------------------*/ { Lock lock(ByteRange(f_lock->l_start, f_lock->l_len), pid); // Can always unlock if (f_lock->l_type == F_UNLCK) { return true; } // Are there any exclusive locks right now? if (wlocks.getconflict(lock)) { f_lock->l_start = lock.range().start(); f_lock->l_len = lock.range().f_lock_len(); f_lock->l_pid = lock.pid(); f_lock->l_whence = SEEK_SET; f_lock->l_type = F_WRLCK; return false; } // If this is a read lock, we can lock if (f_lock->l_type == F_RDLCK) { return true; } // If this is a write lock, we can lock only if there are no read locks if (f_lock->l_type == F_WRLCK) { bool rc = rlocks.getconflict(lock); if (rc) { f_lock->l_start = lock.range().start(); f_lock->l_len = lock.range().f_lock_len(); f_lock->l_pid = lock.pid(); f_lock->l_whence = SEEK_SET; f_lock->l_type = F_RDLCK; return false; } else { return true; } } // TODO raise warning, should never reach this point return false; } /*----------------------------------------------------------------------------*/ bool /*----------------------------------------------------------------------------*/ LockTracker::addLock(pid_t pid, struct flock* f_lock, const std::string& owner) /*----------------------------------------------------------------------------*/ { std::lock_guard guard(mtx); Lock lock(ByteRange(f_lock->l_start, f_lock->l_len), pid, owner); // Unlock? if (f_lock->l_type == F_UNLCK) { rlocks.remove(lock); wlocks.remove(lock); return true; } // Exclusive lock? if (f_lock->l_type == F_WRLCK) { // Conflict with any read locks? if (rlocks.conflict(lock)) { return false; } // Conflict with any write locks? if (wlocks.conflict(lock)) { return false; } // Add write lock wlocks.add(lock); // It could be that the process is converting a read lock into a write. // Remove any read locks on the same region. rlocks.remove(lock); return true; } // Read lock? if (f_lock->l_type == F_RDLCK) { // Conflict with any write locks? if (wlocks.conflict(lock)) { return false; } // Add read lock rlocks.add(lock); // It could be that the process is converting a write lock into a read. // Remove any write locks on the same region. wlocks.remove(lock); return true; } // TODO raise warning, should never reach this point std::cerr << "WARNING, something is wrong" << std::endl; return false; } /*----------------------------------------------------------------------------*/ int /*----------------------------------------------------------------------------*/ LockTracker::removelk(pid_t pid) { std::lock_guard guard(mtx); rlocks.remove(pid); wlocks.remove(pid); return 1; } /*----------------------------------------------------------------------------*/ int /*----------------------------------------------------------------------------*/ LockTracker::removelk(const std::string& owner) { std::lock_guard guard(mtx); rlocks.remove(owner); wlocks.remove(owner); return 1; } /*----------------------------------------------------------------------------*/ bool /*----------------------------------------------------------------------------*/ LockTracker::inuse() /*----------------------------------------------------------------------------*/ { std::lock_guard guard(mtx); return (rlocks.nlocks() + wlocks.nlocks()) ? true : false; } /*----------------------------------------------------------------------------*/ std::set /*----------------------------------------------------------------------------*/ LockTracker::getrlks(const std::string& owner) /*----------------------------------------------------------------------------*/ { std::lock_guard guard(mtx); return rlocks.lslocks(owner); } /*----------------------------------------------------------------------------*/ std::set /*----------------------------------------------------------------------------*/ LockTracker::getwlks(const std::string& owner) /*----------------------------------------------------------------------------*/ { std::lock_guard guard(mtx); return wlocks.lslocks(owner); } EOSMGMNAMESPACE_END