//------------------------------------------------------------------------------
//! @file LockTracker.hh
//! @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 .*
************************************************************************/
#ifndef __EOS_MGM_LOCKTRACKER_HH__
#define __EOS_MGM_LOCKTRACKER_HH__
#include
#include
#include
#include
#include
#include
#include
#include
#include "mgm/Namespace.hh"
#include "common/Assert.hh"
EOSMGMNAMESPACE_BEGIN
template
bool isPointBetween(const T& start, const T& target, const T& end)
{
return target >= start && target < end;
}
template
bool isPointBetweenOrTouching(const T& start, const T& target, const T& end)
{
return target >= start && target <= end;
}
/*----------------------------------------------------------------------------*/
typedef off_t Offset;
/*----------------------------------------------------------------------------*/
class ByteRange;
class Lock;
std::ostream& operator<< (std::ostream& os, const ByteRange& range);
std::ostream& operator<< (std::ostream& os, const Lock& lock);
/*----------------------------------------------------------------------------*/
class ByteRange
/*----------------------------------------------------------------------------*/
{
public:
ByteRange(Offset start, Offset len) : start_(start), len_(len)
{
if (!overlap(*this)) {
std::cerr <<
"ByteRange assertion failed: range does not overlap with itself! start: "
<< start << ", len: " << len << std::endl;
exit(EXIT_FAILURE);
}
}
bool operator==(const ByteRange& rhs) const
{
return this->start() == rhs.start() && this->end() == rhs.end();
}
Offset start() const
{
return start_;
}
Offset len() const
{
return len_;
}
Offset f_lock_len() const
{
if (len_ == -1) {
return 0;
} else {
return len_;
}
}
Offset end() const
{
if (len_ == -1) {
return std::numeric_limits::max();
}
return start_ + len_;
}
// Absorb the other range if possible, expand myself
// to contain both ranges
bool absorb(const ByteRange& other)
{
if (!overlapOrTouch(other)) {
return false;
}
Offset myend = end();
start_ = std::min(start_, other.start_);
updateEnd(std::max(myend, other.end()));
return true;
}
bool contains(const ByteRange& other) const
{
return start() <= other.start() &&
other.end() <= end();
}
// Return what happens when removing the range "other" from "this".
// Might return 0, 1 or 2 resulting ranges.
std::vector minus(const ByteRange& other) const
{
// Case 1: "other" fully to the left, no overlap
if (other.end() <= this->start()) return { *this};
// Case 2: "other" fully to the right, no overlap
if (this->end() <= other.start()) return { *this};
// Case 3: "other" eats the entire thing, no output
if (other.contains(*this)) return {};
// Case 4: "other" eats the start, but not the end
if (isPointBetween(other.start(),
this->start(), other.end()) && other.end() < this->end()) {
return { ByteRange(other.end(), this->end() - other.end())};
}
// Case 5: "other" eats the end, but not the start
if (isPointBetween(other.start(), this->end() - 1,
other.end()) && this->start() < other.start()) {
return { ByteRange(this->start(), other.start() - this->start())};
}
// Case 6: "other" eats the middle
return { ByteRange(this->start(), other.start() - this->start()),
ByteRange(other.end(), this->end() - other.end())};
}
// checks whether the two ranges overlap, or at least touch
bool overlapOrTouch(const ByteRange& other) const
{
// case 1: Is other.start between this->start and this->end ?
if (isPointBetweenOrTouching(this->start(),
other.start(), this->end())) {
return true;
}
// case 2: Is this->start between other.start and other.end ?
if (isPointBetweenOrTouching(other.start(),
this->start(), other.end())) {
return true;
}
// case 3: ranges don't overlap or touch
return false;
}
bool overlap(const ByteRange& other) const
{
// case 1: 0 ranges at the same offset overlap
if ((this->start() == this->end()) &&
(other.start() == other.end()) &&
(this->start() == other.start())) {
return true;
}
// case 2: Is other.start between this->start and this->end ?
if (isPointBetween(this->start(), other.start(), this->end())) {
return true;
}
// case 3: Is this->start between other.start and other.end ?
if (isPointBetween(other.start(), this->start(), other.end())) {
return true;
}
// case 4: ranges don't overlap
return false;
}
private:
void updateEnd(Offset newend)
{
if (newend <= start()) {
std::cerr << "ByteRange assertion failed: tried to update end to "
<< newend << ", while start = " << start() << std::endl;
exit(EXIT_FAILURE);
}
if (newend == std::numeric_limits::max()) {
len_ = -1;
} else {
len_ = newend - start_;
}
}
Offset start_;
Offset len_;
} ;
/*----------------------------------------------------------------------------*/
class Lock
/*----------------------------------------------------------------------------*/
{
public:
Lock(const ByteRange& range, pid_t pid,
const std::string& owner = "") : range_(range), pid_(pid), owner_(owner)
{
}
pid_t pid() const
{
return pid_;
}
std::string owner() const
{
return owner_;
}
const ByteRange& range() const
{
return range_;
}
bool overlap(const Lock& other) const
{
if (pid() != other.pid()) {
return false;
}
return range_.overlap(other.range_);
}
bool contains(const Lock& other) const
{
if (pid() != other.pid()) {
return false;
}
return range_.contains(other.range_);
}
bool absorb(const Lock& other)
{
if (pid() != other.pid()) {
return false;
}
return range_.absorb(other.range_);
}
std::vector minus(const Lock& other)
{
if (pid() != other.pid()) return { *this};
std::vector ranges = range_.minus(other.range_);
std::vector locks;
for (size_t i = 0; i < ranges.size(); i++) {
locks.emplace_back(ranges[i], pid());
}
return locks;
}
bool operator==(const Lock& rhs) const
{
return pid() == rhs.pid() && range() == rhs.range();
}
private:
ByteRange range_;
pid_t pid_;
std::string owner_;
} ;
class LockSet
{
public:
void add(const Lock& l); // adds lock, merging appropriately any overlaps
bool overlap(const Lock& l)
const; // check if overlaps with locks from the *same* process
bool overlap(const ByteRange& r)
const; // check if overlaps with locks from *any* process
void remove(const Lock&
l); // remove any contained locks, shrink any overlapping
void remove(const pid_t pid); // remove all locks for a given pid
void remove(const std::string& owner); // remove all locks for a given owner
// check if there's a conflict between this lock and any other in the set.
// If two locks overlap, but have the same PID, this is not a conflict!
bool conflict(const Lock& l) const;
bool getconflict(Lock& l);
size_t nlocks(); // how many locks there are in total (after coalescing)
size_t nlocks(pid_t
pid); // how many locks are held by a specific pid (after coalescing)
std::set lslocks(const std::string&
owner); // return all pids belonging to owner
private:
std::vector locks;
} ;
class LockTracker
{
public:
int getlk(pid_t pid, struct flock* lock);
int setlk(pid_t pid, struct flock* lock, int sleep, const std::string& owner);
std::set getrlks(const std::string& owner);
std::set getwlks(const std::string& owner);
int removelk(pid_t pid);
int removelk(const std::string& owner);
bool inuse();
private:
std::mutex mtx;
bool addLock(pid_t pid, struct flock* lock, const std::string& owner);
bool canLock(pid_t pid, struct flock* lock);
LockSet rlocks;
LockSet wlocks;
} ;
EOSMGMNAMESPACE_END
#endif