Re: [PATCH v3 07/12] fs: Add locking for a dynamic DAX state

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, Feb 12, 2020 at 08:49:17AM +1100, Dave Chinner wrote:
> On Tue, Feb 11, 2020 at 12:14:31PM -0800, Ira Weiny wrote:
> > On Tue, Feb 11, 2020 at 07:00:35PM +1100, Dave Chinner wrote:
> > > On Sat, Feb 08, 2020 at 11:34:40AM -0800, ira.weiny@xxxxxxxxx wrote:
> > > > From: Ira Weiny <ira.weiny@xxxxxxxxx>
> > > > 
> > > > DAX requires special address space operations but many other functions
> > > > check the IS_DAX() state.
> > > > 
> > > > While DAX is a property of the inode we perfer a lock at the super block
> > > > level because of the overhead of a rwsem within the inode.
> > > > 
> > > > Define a vfs per superblock percpu rs semaphore to lock the DAX state
> > > 
> > > ????
> > 
> > oops...  I must have forgotten to update the commit message when I did the
> > global RW sem.  I implemented the per-SB, percpu rwsem first but it was
> > suggested that the percpu nature of the lock combined with the anticipated
> > infrequent use of the write side made using a global easier.
> > 
> > But before I proceed on V4 I'd like to get consensus on which of the 2 locking
> > models to go with.
> > 
> > 	1) percpu per superblock lock
> > 	2) per inode rwsem
> > 
> > Depending on my mood I can convince myself of both being performant but the
> > percpu is very attractive because I don't anticipate many changes of state
> > during run time.  OTOH concurrent threads accessing the same file at run time
> > may also be low so there is likely to be little read contention across CPU's on
> > the per-inode lock?
> > 
> > Opinions?
> > 
> > > 
> > > > while performing various VFS layer operations.  Write lock calls are
> > > > provided here but are used in subsequent patches by the file systems
> > > > themselves.
> > > > 
> > > > Signed-off-by: Ira Weiny <ira.weiny@xxxxxxxxx>
> > > > 
> > > > ---
> > > > Changes from V2
> > > > 
> > > > 	Rebase on linux-next-08-02-2020
> > > > 
> > > > 	Fix locking order
> > > > 	Change all references from mode to state where appropriate
> > > > 	add CONFIG_FS_DAX requirement for state change
> > > > 	Use a static branch to enable locking only when a dax capable
> > > > 		device has been seen.
> > > > 
> > > > 	Move the lock to a global vfs lock
> > > > 
> > > > 		this does a few things
> > > > 			1) preps us better for ext4 support
> > > > 			2) removes funky callbacks from inode ops
> > > > 			3) remove complexity from XFS and probably from
> > > > 			   ext4 later
> > > > 
> > > > 		We can do this because
> > > > 			1) the locking order is required to be at the
> > > > 			   highest level anyway, so why complicate xfs
> > > > 			2) We had to move the sem to the super_block
> > > > 			   because it is too heavy for the inode.
> > > > 			3) After internal discussions with Dan we
> > > > 			   decided that this would be easier, just as
> > > > 			   performant, and with slightly less overhead
> > > > 			   than in the VFS SB.
> > > > 
> > > > 		We also change the functions names to up/down;
> > > > 		read/write as appropriate.  Previous names were over
> > > > 		simplified.
> > > 
> > > This, IMO, is a bit of a train wreck.
> > > 
> > > This patch has nothing to do with "DAX state", it's about
> > > serialising access to the aops vector.
> > 
> > It is a bit more than just the aops vector.  It also has to protect against
> > checks of IS_DAX() which occur through many of the file operations.
> 
> I think you are looking at this incorrectly. The IS_DAX() construct
> is just determining what path to the aops method we take. regardless
> of whether IS_DAX() is true or not, we need to execute an aop
> method, and so aops vector protection is required regardless of how
> IS_DAX() evaluates.
> 
> But we do require IS_DAX() to evaluate consistently through an
> entire aops protected region, as there may be multiple aops method
> calls in a aops context (e.g. write page faults). Hence we have to
> ensure that IS_DAX() changes atomically w.r.t. to the aops vector
> switches. This is simply:
> 
> 	sb_aops_lock()
> 	inode->i_flags |= S_DAX
> 	sb_aops_switch(new_aops)
> 	sb_aops_unlock().
> 
> This guarantees that inside sb_aops_start/end(), IS_DAX() checks
> are stable because we change the state atomically with the aops
> vector.
> 
> We are *not* providing serialisation of inode->i_flags access or
> updates here; all we need to do is ensure that the S_DAX flag is
> consistent and stable across an aops access region.  If we are not
> in an aops call chain or will not call an aops method, we just don't
> care if the IS_DAX() call is racy as whatever we call is still
> static and if it's DAAX sensitive it can call IS_DAX() itself when
> needed.
> 
> Again, this isn't about DAX at all, it's about being able to switch
> aops vectors in a safe and reliable manner. The IS_DAX() constraints
> are really a minor addition on top of the "stable aops vector"
> regions we are laying down here.
> 
> 
> > > Big problems I see here:
> > > 
> > > 1. static key to turn it on globally.
> > >   - just a gross hack around doing it properly with a per-sb
> > >     mechanism and enbaling it only on filesystems that are on DAX
> > >     capable block devices.
> > 
> > Fair enough.  This was a reaction to Darricks desire to get this out of the way
> > when DAX was not in the system.  The static branch seemed like a good thing for
> > users who don't have DAX capable hardware while running kernels and FS's which
> > have DAX enabled.
> > 
> > http://lkml.iu.edu/hypermail/linux/kernel/2001.1/05691.html
> 
> I think that's largely premature optimisation, and it backs us into
> the "all or nothing" corner which is a bad place to be for what is
> per-filesystem functionality.

Oh, wow, uh... this was a total misunderstanding.  Having a per-inode
primitive to grant ourselves the ability to change fops/aops safely was
fine, I just wanted to have it compile out of existence if CONFIG_DAX=n
(or some other cleverish way if this mount will never support DAX (e.g.
scsi disk)).  I wasn't asking for it to move to the superblock or become
a Big Kernel Primitive.

--D

> 
> > >   - you're already passing in an inode to all these functions. It's
> > >     trivial to do:
> > > 
> > > 	if (!inode->i_sb->s_flags & S_DYNAMIC_AOPS)
> > > 		return
> > > 	/* do sb->s_aops_lock manipulation */
> > 
> > Yea that would be ok IMO.
> > 
> > Darrick would just having this be CONFIG_FS_DAX as per this patch be ok with
> > you.  I guess the static key may have been a bit of overkill?
> > 
> > > 
> > > 2. global lock
> > >   - OMG!
> > 
> > I know.  The thinking on this is that the locking is percpu which is near
> > 0 overhead in the read case and we are rarely going to take exclusive access.
> 
> The problem is that users can effectively run:
> 
> $ xfs_io -c "chattr -R -D -x" /path/to/dax_fs
> 
> And then it walks over millions of individual inodes turning off
> the DAX flag on each of them. And if each of those inodes takes a
> global per-cpu rwsem that blocks all read/write IO and page faults
> on everything even for a short time, then this is will have a major
> impact on the entire system and users will be very unhappy.
> 
> > >   - global lock will cause entire system IO/page fault stalls
> > >     when someone does recursive/bulk DAX flag modification
> > >     operations. Per-cpu rwsem Contention on  large systems will be
> > >     utterly awful.
> > 
> > Yea that is probably bad...  I certainly did not test the responsiveness of
> > this.  FWIW if the system only has 1 FS the per-SB lock is not going to be any
> > different from the global which was part of our thinking.
> 
> Don't forget that things like /proc, /sys, etc are all filesystems.
> Hence the global lock will affect accesses to -everything- in the
> system, not just the DAX enabled filesystem. Global locks are -bad-.
> 
> > >   - ext4's use case almost never hits the exclusive lock side of the
> > >     percpu-rwsem - only when changing the journal mode flag on the
> > >     inode. And it only affects writeback in progress, so it's not
> > >     going to have massive concurrency on it like a VFS level global
> > >     lock has. -> Bad model to follow.
> > 
> > I admit I did not research the ext4's journal mode locking that much.
> > 
> > >   - per-sb lock is trivial - see #1 - which limits scope to single
> > >     filesystem
> > 
> > I agree and...  well the commit message shows I actually implemented it that
> > way at first...  :-/
> > 
> > >   - per-inode rwsem would make this problem go away entirely.
> > 
> > But would that be ok for concurrent read performance which is going to be used
> > 99.99% of the time?  Maybe Darricks comments made me panic a little bit too
> > much overhead WRT locking and its potential impact on users not using DAX?
> 
> I know that a rwsem in shared mode can easily handle 2M lock
> cycles/s across a 32p machine without contention (concurrent AIO-DIO
> read/writes to a single file) so the baseline performance of a rwsem
> is likely good enough to start from.
> 
> It's simple, and we can run tests easily enough to find out where it
> starts to become a performance limitation. This whole "global percpu
> rwsem thing stinks like premature optimisation. Do the simple,
> straight forward thing first, then get numbers and analysis the
> limitations to determine what the second generation of the
> functionality needs to fix.
> 
> IMO, we don't have to solve every scalability problem with the
> initial implementation. Let's just make it work first, then worry
> about extreme scalability when we have some idea of where those
> scalability problems are.
> 
> > > 3. If we can use a global per-cpu rwsem, why can't we just use a
> > >    per-inode rwsem?
> > 
> > Per-cpu lock was attractive because of its near 0 overhead to take the read
> > lock which happens a lot during normal operations.
> > 
> > >   - locking context rules are the same
> > >   - rwsem scales pretty damn well for shared ops
> > 
> > Does it?  I'm not sure.
> 
> If you haven't tested it, then we are definitely in the realm of
> premature optimisation...
> 
> > 
> > >   - no "global" contention problems
> > >   - small enough that we can put another rwsem in the inode.
> > 
> > Everything else I agree with...  :-D
> > 
> > > 
> > > 4. "inode_dax_state_up_read"
> > >   - Eye bleeds.
> > >   - this is about the aops structure serialisation, not dax.
> > >   - The name makes no sense in the places that it has been added.
> > 
> > Again it is about more than just the aops.  IS_DAX() needs to be protected in
> > all the file operations calls as well or we can get races with the logic in
> > those calls and a state switch.
> > 
> > > 
> > > 5. We already have code that does almost exactly what we need: the
> > >    superblock freezing infrastructure.
> > 
> > I think freeze does a lot more than we need.
> > 
> > >   - freezing implements a "hold operations on this superblock until
> > >     further notice" model we can easily follow.
> > >   - sb_start_write/sb_end_write provides a simple API model and a
> > >     clean, clear and concise naming convention we can use, too.
> > 
> > Ok as a model...  If going with the per-SB lock.
> 
> Ok, you completely missed my point. You're still looking at this as
> a set of "locks" and serialisation.
> 
> Freezing is *not a lock* - it provides a series of "drain points"
> where we can transparently block new operations, then wait for all
> existing operations to complete so we can make a state change, and
> then once that is done we unblock all the waiters....
> 
> IOWs, the freeze model provides an ordered barrier mechanism, and
> that's precisely what we need for aops protection...
> 
> Yes, it may be implemented with locks internally, but that's
> implementation detail of the barrier mechanism, not an indication
> what functionality it is actually providing.
> 
> > After working through my
> > response I'm leaning toward a per-inode lock again.  This was the way I did
> > this to begin with.
> > 
> > I want feedback before reworking for V4, please.
> 
> IMO, always do the simple thing first.
> 
> Only do a more complex thing if the simple thing doesn't work or
> doesn't perform sufficiently well for an initial implemenation.
> Otherwise we end up with crazy solutions from premature optimisation
> that simply aren't viable.
> 
> > > Really, I'm struggling to understand how we got to "global locking
> > > that stops the world" from "need to change per-inode state
> > > atomically". Can someone please explain to me why this patch isn't
> > > just a simple set of largely self-explanitory functions like this:
> > > 
> > > XX_start_aop()
> > > XX_end_aop()
> > > 
> > > XX_lock_aops()
> > > XX_switch_aops(aops)
> > > XX_unlock_aops()
> > > 
> > > where "XX" is "sb" or "inode" depending on what locking granularity
> > > is used...
> > 
> > Because failing to protect the logic around IS_DAX() results in failures in the
> > read/write and direct IO paths.
> > 
> > So we need to lock the file operations as well.
> 
> Again, there is no "locking" here. We just need to annotate regions
> where the aops vector must remain stable. If a fop calls an aop that
> the aops vector does not change while it is the middle of executing
> the fop. Hence such fops calls will need to be inside
> XX_start_aop()/XX_end_aop() markers.
> 
> Cheers,
> 
> Dave.
> -- 
> Dave Chinner
> david@xxxxxxxxxxxxx



[Index of Archives]     [XFS Filesystem Development (older mail)]     [Linux Filesystem Development]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux RAID]     [Linux SCSI]


  Powered by Linux