Thread (26 messages) 26 messages, 3 authors, 2021-02-05

Re: [PATCH 0/6] kernfs: proposed locking and concurrency improvement

From: Ian Kent <raven@themaw.net>
Date: 2021-01-13 07:48:53

On Wed, 2021-01-13 at 15:00 +0800, Fox Chen wrote:
On Wed, Jan 13, 2021 at 1:17 PM Ian Kent [off-list ref] wrote:
quoted
On Mon, 2021-01-11 at 17:02 +0800, Fox Chen wrote:
quoted
On Mon, Jan 11, 2021 at 4:42 PM Ian Kent [off-list ref]
wrote:
quoted
On Mon, 2021-01-11 at 15:04 +0800, Fox Chen wrote:
quoted
On Mon, Jan 11, 2021 at 12:20 PM Ian Kent [off-list ref]
wrote:
quoted
On Mon, 2021-01-11 at 11:19 +0800, Ian Kent wrote:
quoted
On Wed, 2021-01-06 at 10:38 +0800, Fox Chen wrote:
quoted
Hi Ian,

I am rethinking this problem. Can we simply use a
global
lock?

 In your original patch 5, you have a global mutex
attr_mutex
to
protect attr, if we change it to a rwsem, is it enough
to
protect
both
inode and attr while having the concurrent read
ability?

like this patch I submitted. ( clearly, I missed
__kernfs_iattrs
part,
but just about that idea )
https://lore.kernel.org/lkml/20201207084333.179132-1-foxhlchen@gmail.com/ (local)
I don't think so.

kernfs_refresh_inode() writes to the inode so taking a
read
lock
will allow multiple processes to concurrently update it
which
is
what we need to avoid.
Oh, got it. I missed the inode part. my bad. :(
quoted
quoted
It's possibly even more interesting.

For example, kernfs_iop_rmdir() and kernfs_iop_mkdir()
might
alter
the inode link count (I don't know if that would be the
sort
of
thing
they would do but kernfs can't possibly know either).
Both of
these
functions rely on the VFS locking for exclusion but the
inode
link
count is updated in kernfs_refresh_inode() too.

That's the case now, without any patches.
So it's not so easy to get the inode from just the kernfs
object
so these probably aren't a problem ...
IIUC only when dop->revalidate, iop->lookup being called, the
result
of rmdir/mkdir will be sync with vfs.
Don't quite get what you mean here?

Do you mean something like, VFS objects are created on user
access
to the file system. Given that user access generally means path
resolution possibly followed by some operation.

I guess those VFS objects will go away some time after the
access
but even thought the code looks like that should happen pretty
quickly after I've observed that these objects stay around
longer
than expected. There wouldn't be any use in maintaining a least
recently used list of dentry candidates eligible to discard.
Yes, that is what I meant. I think the duration may depend on the
current ram pressure. though not quite sure, I'm still digging
this
part of code.
quoted
quoted
kernfs_node is detached from vfs inode/dentry to save ram.
quoted
quoted
I'm not entirely sure what's going on in
kernfs_refresh_inode().

It could be as simple as being called with a NULL inode
because
the dentry concerned is negative at that point. I haven't
had
time to look closely at it TBH but I have been thinking
about
it.
um, It shouldn't be called with a NULL inode, right?

inode->i_mode = kn->mode;

otherwise will crash.
Yes, you're right about that.
quoted
quoted
Certainly this can be called without a struct iattr having
been
allocated ... and given it probably needs to remain a
pointer
rather than embedded in the node the inode link count
update
can't easily be protected from concurrent updates.

If it was ok to do the allocation at inode creation the
problem
becomes much simpler to resolve but I thought there were
concerns
about ram consumption (although I don't think that was
exactly
what
was said?).
you meant iattr to be allocated at inode creation time??
yes, I think so. it's due to ram consumption.
I did, yes.

The actual problem is dealing with multiple concurrent updates
to
the inode link count, the rest can work.
Umm ... maybe I've been trying to do this in the wrong place all
along.

You know the inode i_lock looks like the sensible thing to use to
protect these updates.

Something like this for that last patch should work:

kernfs: use i_lock to protect concurrent inode updates

From: Ian Kent <raven@themaw.net>

The inode operations .permission() and .getattr() use the kernfs
node
write lock but all that's needed is to keep the rb tree stable
while
updating the inode attributes as well as protecting the update
itself
against concurrent changes.

And .permission() is called frequently during path walks and can
cause
quite a bit of contention between kernfs node opertations and path
walks when the number of concurrant walks is high.

To change kernfs_iop_getattr() and kernfs_iop_permission() to take
the rw sem read lock instead of the write lock an addtional lock is
needed to protect against multiple processes concurrently updating
the inode attributes and link count in kernfs_refresh_inode().

The inode i_lock seems like the sensible thing to use to protect
these
inode attribute updates so use it in kernfs_refresh_inode().

Signed-off-by: Ian Kent <raven@themaw.net>
---
 fs/kernfs/inode.c |   10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/fs/kernfs/inode.c b/fs/kernfs/inode.c
index ddaf18198935..e26fa5115821 100644
--- a/fs/kernfs/inode.c
+++ b/fs/kernfs/inode.c
@@ -171,6 +171,7 @@ static void kernfs_refresh_inode(struct
kernfs_node *kn, struct inode *inode)
 {
        struct kernfs_iattrs *attrs = kn->iattr;

+       spin_lock(inode->i_lock);
        inode->i_mode = kn->mode;
        if (attrs)
                /*
@@ -181,6 +182,7 @@ static void kernfs_refresh_inode(struct
kernfs_node *kn, struct inode *inode)

        if (kernfs_type(kn) == KERNFS_DIR)
                set_nlink(inode, kn->dir.subdirs + 2);
+       spin_unlock(inode->i_lock);
 }

 int kernfs_iop_getattr(const struct path *path, struct kstat
*stat,
@@ -189,9 +191,9 @@ int kernfs_iop_getattr(const struct path *path,
struct kstat *stat,
        struct inode *inode = d_inode(path->dentry);
        struct kernfs_node *kn = inode->i_private;

-       down_write(&kernfs_rwsem);
+       down_read(&kernfs_rwsem);
        kernfs_refresh_inode(kn, inode);
-       up_write(&kernfs_rwsem);
+       up_read(&kernfs_rwsem);

        generic_fillattr(inode, stat);
        return 0;
@@ -281,9 +283,9 @@ int kernfs_iop_permission(struct inode *inode,
int mask)

        kn = inode->i_private;

-       down_write(&kernfs_rwsem);
+       down_read(&kernfs_rwsem);
        kernfs_refresh_inode(kn, inode);
-       up_write(&kernfs_rwsem);
+       up_read(&kernfs_rwsem);

        return generic_permission(inode, mask);
 }
It looks good on my local machine, let me test my benchmark on a big
machine. :)

Also, I wonder why i_lock?? what if I use a local spin_lock, will
there be any difference???
I think that amounts to using a global lock (ie. static) not a
per-object lock which is needed to reduce contention.
static void kernfs_refresh_inode(struct kernfs_node *kn, struct inode
*inode)
{
        struct kernfs_iattrs *attrs = kn->iattr;
        static DEFINE_SPINLOCK(inode_lock);

        spin_lock(&inode_lock);
        inode->i_mode = kn->mode;
        if (attrs)
                /*
                 * kernfs_node has non-default attributes get them
from
                 * persistent copy in kernfs_node.
                 */
                set_inode_attr(inode, attrs);

        if (kernfs_type(kn) == KERNFS_DIR)
                set_nlink(inode, kn->dir.subdirs + 2);
        spin_unlock(&inode_lock);
}



thanks,
fox
  
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help