scoutfs: introduce write locking

Introduce the concept of acquiring write locks around write operations.

The core idea is that reads are unlocked and that write lock contention
between nodes should be rare.  This first pass simply broadcasts write
lock requests to all the mounts in the volume.  It achieves a reasonable
degree of fairness and doesn't require centralizing state in a lock
server.

We have to flesh out a bit of initial infrastructure to support the
write locking protocol.  The roster manages cluster membership and
messaging and only understands mounts in the same kernel for now.
Creation needs to know which inodes to try and lock so we see the start
of per-mount free inode reservations.

The transformation of users is straight forward: they aquire the write
lock on the inodes they're working with instead of holding a
transaction.  The write lock machinery now manages transactions.

This passes single mount testing but that isn't saying much.  The next
step is to run multi-mount tests.

Signed-off-by: Zach Brown <zab@versity.com>
This commit is contained in:
Zach Brown
2016-05-23 17:25:06 -07:00
parent 4163236fc1
commit 0820a7b5bd
13 changed files with 1303 additions and 28 deletions
+57 -15
View File
@@ -22,6 +22,7 @@
#include "btree.h"
#include "dir.h"
#include "filerw.h"
#include "wrlock.h"
#include "scoutfs_trace.h"
/*
@@ -247,24 +248,69 @@ void scoutfs_update_inode_item(struct inode *inode)
trace_scoutfs_update_inode(inode);
}
static int alloc_ino(struct super_block *sb, u64 *ino)
/*
* This will need to try and find a mostly idle shard. For now we only
* have one :).
*/
static int get_next_ino_batch(struct super_block *sb)
{
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
struct scoutfs_super_block *super = &sbi->super;
DECLARE_SCOUTFS_WRLOCK_HELD(held);
int ret;
ret = scoutfs_wrlock_lock(sb, &held, 1, 1);
if (ret)
return ret;
spin_lock(&sbi->next_ino_lock);
if (super->next_ino == 0) {
ret = -ENOSPC;
} else {
*ino = le64_to_cpu(super->next_ino);
le64_add_cpu(&super->next_ino, 1);
ret = 0;
if (!sbi->next_ino_count) {
sbi->next_ino = le64_to_cpu(sbi->super.next_ino);
if (sbi->next_ino + SCOUTFS_INO_BATCH < sbi->next_ino) {
ret = -ENOSPC;
} else {
le64_add_cpu(&sbi->super.next_ino, SCOUTFS_INO_BATCH);
sbi->next_ino_count = SCOUTFS_INO_BATCH;
ret = 0;
}
}
spin_unlock(&sbi->next_ino_lock);
scoutfs_wrlock_unlock(sb, &held);
return ret;
}
/*
* Inode allocation is at the core of supporting parallel creation.
* Each mount needs to allocate from a pool of free inode numbers which
* map to a shard that it has locked.
*/
int scoutfs_alloc_ino(struct super_block *sb, u64 *ino)
{
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
int ret;
do {
/* don't really care if this is racey */
if (!sbi->next_ino_count) {
ret = get_next_ino_batch(sb);
if (ret)
break;
}
spin_lock(&sbi->next_ino_lock);
if (sbi->next_ino_count) {
*ino = sbi->next_ino++;
sbi->next_ino_count--;
ret = 0;
} else {
ret = -EAGAIN;
}
spin_unlock(&sbi->next_ino_lock);
} while (ret == -EAGAIN);
return ret;
}
@@ -273,18 +319,14 @@ static int alloc_ino(struct super_block *sb, u64 *ino)
* creating links to it and updating it. @dir can be null.
*/
struct inode *scoutfs_new_inode(struct super_block *sb, struct inode *dir,
umode_t mode, dev_t rdev)
u64 ino, umode_t mode, dev_t rdev)
{
DECLARE_SCOUTFS_BTREE_CURSOR(curs);
struct scoutfs_inode_info *ci;
struct scoutfs_key key;
struct inode *inode;
u64 ino;
int ret;
ret = alloc_ino(sb, &ino);
if (ret)
return ERR_PTR(ret);
inode = new_inode(sb);
if (!inode)