LockFileEx чтение / запись обновления / понижения - PullRequest
4 голосов
/ 02 июля 2010

Мне нужно открыть файл, заблокировать чтение, затем попытаться получить блокировку записи, но сохранить блокировку чтения в случае сбоя.

Это прекрасно работает в POSIX с использованием блокировки fcntl.

В Windows я могу использовать LockFileEx для блокировки файлов. Я могу получить блокировки чтения и записи (общие и эксклюзивные).

Однако, похоже, что в Windows я должен взять эксклюзивную блокировку записи сначала , а затем добавить блокировку чтения. Это порядок, противоположный тому, что я делаю в POSIX, и это вызывает проблемы для моего уровня абстракции. Когда я делаю это в таком порядке в POSIX, я теряю блокировку записи, беря блокировку чтения, потому что fcntl заменяет существующую блокировку вместо добавления блокировок, как это делает Windows.

Я могу взломать его с помощью #ifdefs, чтобы изменить порядок блокировки на сайтах вызовов, но я ищу хорошие идеи для исправления кода абстракции.

// 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;
}

Ответы [ 2 ]

0 голосов
/ 15 июля 2013

У нас очень ограниченные требования к блокировке, но следующий код, кажется, работает для имитации POSIX fcntl, достаточного для наших целей.Обратите внимание на хак, чтобы различить блокировки чтения и записи на основе размера заблокированной области (этот хак может работать для вас на основе вашего примера).В приведенном ниже коде предполагается, что файлы имеют размер менее 4 ГБ.

// fcntl flock definitions
#define F_SETLK  8   // Non-Blocking set or clear a lock
#define F_SETLKW 9   // Blocking set or clear a lock
#define F_RDLCK  1   // read lock
#define F_WRLCK  2   // write lock
#define F_UNLCK  3   // remove lock
struct flock {
    short l_type;   // F_RDLCK, F_WRLCK, or F_UNLCK
    short l_whence; // flag to choose starting offset, must be SEEK_SET
    long  l_start;  // relative offset, in bytes, must be 0
    long  l_len;    // length, in bytes; 0 means lock to EOF, must be 0
    short l_pid;    // unused (returned with the unsupported F_GETLK)
    short l_xxx;    // reserved for future use
};

// only works for (SEEK_SET, start=0, len=0) file locking.
__inline int fcntl(int fd, int cmd, ...)
{
    va_list a;
    va_start(a, cmd);
    switch(cmd)
    {
    case F_SETLK:
        {
            struct flock *l = va_arg(a, struct flock*);
            switch(l->l_type)
            {
            case F_RDLCK:
                {
                    OVERLAPPED o = { 0 };
                    HANDLE h = (HANDLE)_get_osfhandle(fd);
                    if (l->l_whence != SEEK_SET || l->l_start != 0 || l->l_len != 0)
                    {
                        _set_errno(ENOTSUP);
                        return -1;
                    }
                    if (!LockFileEx(h, LOCKFILE_FAIL_IMMEDIATELY, 0, 0, 1, &o)) // read lock
                    {
                        unsigned long x = GetLastError();
                        _set_errno(GetLastError() == ERROR_LOCK_VIOLATION ? EAGAIN : EBADF);
                        return -1;
                    }
                    UnlockFile(h, 0, 0, 1, 1); // write lock
                }
                break;
            case F_WRLCK:
                {
                    OVERLAPPED o = { 0 };
                    HANDLE h = (HANDLE)_get_osfhandle(fd);
                    if (l->l_whence != SEEK_SET || l->l_start != 0 || l->l_len != 0)
                    {
                        _set_errno(ENOTSUP);
                        return -1;
                    }
                    if (!LockFileEx(h, LOCKFILE_FAIL_IMMEDIATELY|LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 1, &o)) // write lock
                    {
                        unsigned long x = GetLastError();
                        _set_errno(GetLastError() == ERROR_LOCK_VIOLATION ? EAGAIN : EBADF);
                        return -1;
                    }
                    UnlockFile(h, 0, 0, 0, 1); // read lock
                }
                break;
            case F_UNLCK:
                {
                    HANDLE h = (HANDLE)_get_osfhandle(fd);
                    if (l->l_whence != SEEK_SET || l->l_start != 0 || l->l_len != 0)
                    {
                        _set_errno(ENOTSUP);
                        return -1;
                    }
                    UnlockFile(h, 0, 0, 0, 1); // read lock
                    UnlockFile(h, 0, 0, 1, 1); // write lock
                }
                break;
            default:
                _set_errno(ENOTSUP);
                return -1;
            }
        }
        break;
    case F_SETLKW:
        {
            struct flock *l = va_arg(a, struct flock*);
            switch(l->l_type)
            {
            case F_RDLCK:
                {
                    OVERLAPPED o = { 0 };
                    HANDLE h = (HANDLE)_get_osfhandle(fd);
                    if (l->l_whence != SEEK_SET || l->l_start != 0 || l->l_len != 0)
                    {
                        _set_errno(ENOTSUP);
                        return -1;
                    }
                    if(!LockFileEx(h, 0, 0, 0, 1, &o)) // read lock
                    {
                        unsigned long x = GetLastError();
                        return -1;
                    }
                    UnlockFile(h, 0, 0, 1, 1); // write lock
                }
                break;
            case F_WRLCK:
                {
                    OVERLAPPED o = { 0 };
                    HANDLE h = (HANDLE)_get_osfhandle(fd);
                    if (l->l_whence != SEEK_SET || l->l_start != 0 || l->l_len != 0)
                    {
                        _set_errno(ENOTSUP);
                        return -1;
                    }
                    if (!LockFileEx(h, LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 1, &o)) // write lock
                    {
                        unsigned long x = GetLastError();
                        return -1;
                    }
                    UnlockFile(h, 0, 0, 0, 1); // read lock
                }
                break;
            case F_UNLCK:
                {
                    flock *l = va_arg(a, flock*);
                    HANDLE h = (HANDLE)_get_osfhandle(fd);
                    if (l->l_whence != SEEK_SET || l->l_start != 0 || l->l_len != 0)
                    {
                        _set_errno(ENOTSUP);
                        return -1;
                    }
                    UnlockFile(h, 0, 0, 0, 1); // read lock
                    UnlockFile(h, 0, 0, 1, 1); // write lock
                }
                break;
            default:
                _set_errno(ENOTSUP);
                return -1;
            }
        }
        break;
    default:
        _set_errno(ENOTSUP);
        return -1;
    }

    return 0;
}

Основная проблема с блокировкой fcntl для блокировки FileLock, как вы заметили, состоит в том небольшом предостережении (из документов):

Если один и тот же диапазон заблокирован эксклюзивной и общей блокировкой, для разблокирования региона необходимы две операции разблокировки;первая операция разблокировки разблокирует эксклюзивную блокировку, вторая операция разблокировки разблокирует разделяемую блокировку.

Это означает, что вы не можете перейти от просто общей блокировки к просто эксклюзивной блокировке того же региона, не отпустив сначалаполностью заблокировать этот регион.Ваш метод использования флагов близок, и я думаю, что если вы добавите еще один флаг, который говорит i_have_both_locks_but_only_really_want_the_exclusive_lock, то ваше решение может работать.У нас не было роскоши писать свой собственный интерфейс (мы застряли с fcntl).Но, к счастью, код, использующий fcntl, просто хотел заблокировать весь файл, а файлы были маленькими.Альтернативным решением было бы поместить std :: map в вызов fcntl, чтобы отслеживать блокировки fcntl по сравнению с собственными блокировками FileLock.

0 голосов
/ 03 ноября 2010

Почему бы не использовать подход с прыщами? Во всяком случае, ты почти там. Создайте WinLockFileImpl и PosixLockFileImpl, которые оба наследуют абстрактный LockFileImpl. Затем поместите один ifdef вокруг следующего кода, чтобы определить, какой класс используется во время компиляции. У вас уже должен быть ifdef, который удаляет код Windows при компиляции на других платформах, верно?

LockFile::LockFile(const File &f)
#ifdef POSIX
    : p( new PosixLockFileImpl(f.handle()) )
#else
    : p( new WinLockFileImpl(f.handle()) )
#endif

О, и вам нужно будет переместить ваш код в классы реализации, которые изменяются LockFile, чтобы выглядеть примерно так:

void LockFile::unlock() const
{
    p->unlock();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...