Zohar81
Zohar81

Reputation: 5118

using flock, open and close file to implement many readers single writer lock

I've got a project that consist of multiple processes that can read or write into a single data base. I wish to implement single writer / multi readers locks synchronized by a lock file using the system calls flock/open/close.

Upon lock failure, any re-attempt to take the lock again, will be made by the higher level that requested the lock (unlike spin-lock).

Unfortunately, while testing this model, it failed on scenario of unlocking that wasn't preceded by locking. perhaps you can help me find what did i do wrong here:

// keep read and write file descriptors as global variables.
// assuming no more than 1 thread can access db on each process. 

int write_descriptor=0;
int read_descriptor=0;

int lock_write() {
    if((write_descriptor = open(LOCKFILE, O_RDWR|O_CREAT,0644))<0) {
        return LOCK_FAIL;
    }

    if(flock(write_descriptor, LOCK_EX)<0) {
        close(write_descriptor);
        write_descriptor = 0;
        return LOCK_FAIL;
    }
    return LOCK_SUCCESS;
}

int unlock_write() {
    if(!write_descriptor) {
        // sanity: try to unlock before lock.
        return LOCK_FAIL;
    }

    if(flock(write_descriptor,LOCK_UN)<0) {
        // doing nothing because even if unlock failed, we
        // will close the fd anyway to release all locks.
    }
    close(write_descriptor);
    write_descriptor = 0;
    return LOCK_SUCCESS;
}


int lock_read() {
    if((read_descriptor = open(LOCKFILE,O_RDONLY))<0) {
        return LOCK_FAIL;
    }

    if(flock(read_descriptor, LOCK_SH)<0) {
        close(read_descriptor);
        return LOCK_FAIL;
    }
    return LOCK_SUCCESS;
}

int unlock_read() {
    if(!read_descriptor) {
        // sanity : try to unlock before locking first.
        return LOCK_FAIL;
    }

    if(flock(read_descriptor, LOCK_UN)<0) {
        // doing nothing because even if unlock failed, we
        // will close the fd anyway to release all locks.
    }
    close(read_descriptor);
    read_descriptor = 0;
    return LOCK_SUCCESS;
}


int read_db() {
    if(lock_read() != LOCK_SUCCESS) {
        return DB_FAIL;
    }
    // read from db
    if(unlock_read() != LOCK_SUCCESS) {
        // close fd also unlock - so we can fail here (can i assume that ?)
    }
}

int write_db() {
    if(lock_write() != LOCK_SUCCESS) {
        return DB_FAIL;
    }
    //write to db.
    if(unlock_write() != LOCK_SUCCESS) {
        // close fd also unlock - so we can fail here (can i assume that ?)
    }
}

Upvotes: 1

Views: 2002

Answers (1)

David Schwartz
David Schwartz

Reputation: 182829

In both lock_read and lock_write add this as the first line:

assert ((read_descriptor == 0) && (write_descriptor == 0));

In unlock_read, add this:

assert (read_descriptor != 0);

And in unlock_write, add this:

assert (write_descriptor != 0);

And change code like:

if(flock(read_descriptor, LOCK_SH)<0) {
    close(read_descriptor);
    return LOCK_FAIL;
}

to:

if(flock(read_descriptor, LOCK_SH)<0) {
    close(read_descriptor);
    read_descriptor = 0;
    return LOCK_FAIL;
}

Do the same for the write code so that any time a descriptor is closed, the corresponding global is set to zero. (You really should use -1 for an invalid file descriptor since zero is legal.)

Make a debug build and run it. When an assert trips, you'll have your culprit.

Upvotes: 2

Related Questions