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