I have the need to open a file, read-lock it, then attempt to get a write lock but keep the read lock if it fails.
This works great in POSIX using fcntl locking.
In Windows I can use LockFileEx to get file locks. I can get both read and write locks (shared and exclusive).
However, it seems that in Windows I must take the exclusive write lock first and then add the read lock. This is the opposite order of what I do on POSIX and it causes problems for my abstraction layer. When I do it in that order in POSIX I lose the write lock by taking the read lock because fcntl replaces the existing lock instead of adding locks as Windows does.
I can hack it with #ifdefs to change the locking order at the call sites, but I am looking for good ideas to fix my abstraction code.
// This is the header file
struct LockFileImpl;
class LockFile {
protected:
boost::scoped_ptr<LockFileImpl> p;
public:
LockFile(const File &);
virtual ~LockFile();
void unlock() const;
void rd_lock() const;
void wr_lock() const;
bool rd_try() const;
bool wr_try() const;
};
class LockFileRead : public LockFile{
public:
LockFileRead(const File &f) : LockFile(f)
{ rd_lock(); }
};
class LockFileWrite : public LockFile{
public:
LockFileWrite(const File &f) : LockFile(f)
{ wr_lock(); }
};
// This is the Win32 implementation file. There's a different one for POSIX.
struct LockFileImpl
{
handle_t hFile;
bool rd_locked;
bool wr_locked;
LockFileImpl(handle_t x) : hFile(x), rd_locked(false), wr_locked(false)
{}
};
LockFile::LockFile(const File &f)
: p( new LockFileImpl(f.handle()) )
{
}
LockFile::~LockFile()
{
unlock();
}
void LockFile::unlock() const
{
if(p->wr_locked) {
throw_win32_err_if( UnlockFile(p->hFile, 0, 0, 1, 0) == 0 );
p->wr_locked = false;
}
if(p->rd_locked) {
throw_win32_err_if( UnlockFile(p->hFile, 0, 0, 1, 0) == 0 );
p->rd_locked = false;
}
}
void LockFile::rd_lock() const
{
OVERLAPPED over = {0};
over.Offset = 0;
throw_win32_err_if( !LockFileEx(p->hFile, 0, 0, 1, 0, &over) );
p->rd_locked = true;
if(p->wr_locked) {
throw_win32_err_if( UnlockFile(p->hFile, 0, 0, 1, 0) == 0 );
p->wr_locked = false;
}
}
void LockFile::wr_lock() const
{
OVERLAPPED over = {0};
over.Offset = 0;
throw_win32_err_if( !LockFileEx(p->hFile, LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &over) );
p->wr_locked = true;
}
bool LockFile::rd_try() const
{
OVERLAPPED over = {0};
over.Offset = 0;
bool r = !!LockFileEx(p->hFile, LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &over);
if(r) {
p->rd_locked = true;
if(p->wr_locked) {
throw_win32_err_if( UnlockFile(p->hFile, 0, 0, 1, 0) == 0 );
p->wr_locked = false;
}
}
return r;
}
bool LockFile::wr_try() const
{
OVERLAPPED over = {0};
over.Offset = 0;
bool r = !!LockFileEx(p->hFile, LOCKFILE_FAIL_IMMEDIATELY|LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &over);
if(r) {
p->wr_locked = true;
}
return r;
}