Commit Graph
107 Commits
Author SHA1 Message Date
Zach Brown dfc2f7a4e8 Remove unused scoutfs_free_unused_locks nr arg
The nr argument wasn't used.  It always tries to free as many as the
shrinker call will let it.

Signed-off-by: Zach Brown <zab@versity.com>
2021-04-21 12:17:33 -07:00
Zach Brown 94dd86f762 Process lock invalidation after shutdown
Lock teardown during unmount involves first calling shutdown and then
destroy.  The shutdown call is meant to ensure that it's safe to tear
down the client network connections.  Once shutdown returns locking is
promising that it won't call into the client to send new lock requests.

The current shutdown implementation is very heavy handed and shuts down
everything.  This creates a deadlock.  After calling lock shutdown, the
client will send its farewell and wait for a response.  The server might
not send the farewell response until other mounts have unmounted if our
client is in the server's mount.  In this case we stil have to be
processing lock invalidation requests to allow other unmounting clients
to make forward progress.

This is reasonably easy and safe to do.  We only use the shutdown flag
to stop lock calls that would change lock state and send requests.  We
don't have it stop incoming requests processing in the work queueing
functions.  It's safe to keep processing incoming requests between
_shutdown and _destroy because the requests already come in through the
client.  As the client shuts down it will stop calling us.

Signed-off-by: Zach Brown <zab@versity.com>
2021-04-21 12:17:33 -07:00
Zach Brown 2949b6063f Clear lock invalidate_pending during destroy
Locks have a bunch of state that reflects concurrent processing.
Testing that state determines when it's safe to free a lock because
nothing is going on.

During unmount we abruptly stop processing locks.  Unmount will send a
farewell to the server which will remove all the state associated with
the client that's unmounting for all its locks, regardless of the state
the locks were in.

The client unmount path has to clean up the interupted lock state and
free it, carefully avoiding assertions that would otherwise indicate
that we're freeing used locks.  The move to async lock invalidation
forgot to clean up the invalidation state.  Previously a synchronous
work function would set and clear invalidate_pending while it was
running.  Once we finished waiting for it invalidate_pending would be
clear.  The move to async invalidation work meant that we can still have
invalidate_pending with no work executing.  Lock destruction removed
locks from the invalidation list but forgot to clear the
invalidate_pending flag.

This triggered assertions during unmount that were otherwise harmless.
There was other use of the lock, we just forgot to clean up the lock
state.

Signed-off-by: Zach Brown <zab@versity.com>
2021-04-21 12:17:33 -07:00
Zach Brown 4389c73c14 Fix deadlock between lock invalidate and evict
We've had a long-standing deadlock between lock invalidation and
eviction.  Invalidating a lock wants to lookup inodes and drop their
resources while blocking locks.  Eviction wants to get a lock to perform
final deletion while the inodes has I_FREEING set which blocks lookups.

We only saw this deadlock a handful of times in all of the time we've
run the code, but it's now much more common now that we're acquiring
locks in iput to test that nlink is zero instead of only when nlink is
zero.  I see unmount hang regularly when testing final inode deletion.

This adds a lookup variant for invalidation which will refuse to
return freeing inodes so they won't be waited on.  Once they're freeing
they can't be seen by future lock users so they don't need to be
invalidated.  This keeps the lock invalication promise and avoids
sleeping on freeing inodes which creates the deadlock.

Signed-off-by: Zach Brown <zab@versity.com>
2021-04-21 12:17:33 -07:00
Zach Brown 715c29aad3 Proactively drop dentry/inode caches outside locks
Previously we wouldn't try and remove cached dentries and inodes as
lock revocation removed cluster lock coverage.  The next time
we tried to use the cached dentries or inodes we'd acquire
a lock and refresh them.

But now cached inodes prevent final inode deletion.  If they linger
outside cluster locking then any final deletion will need to be deferred
until all its cached inodes are naturally dropped at some point in the
future across the cluster.  It might take refreshing the dentries or for
memory pressure to push out the old cached inodes.

This tries to proctively drop cached dentries and inodes as we lose
cluster lock coverage if they're not actively referenced.  We need to be
careful not to perform final inode deletion during lock invalidation
because it will deadlock, so we defer an iput which could delete during
evict out to async work.

Now deletion can be done synchronously in the task that is performing
the unlink because previous use of the inode on remote mounts hasn't
left unused cached inodes sitting around.

Signed-off-by: Zach Brown <zab@versity.com>
2021-04-21 12:17:33 -07:00
Zach Brown 22371fe5bd Fully destroy inodes after all mounts evict
Today an inode's items are deleted once its nlink reaches zero and the
final iput is called in a local mount.  This can delete inodes from
under other mounts which have opened the inode before it was unlinked on
another mount.

We fix this by adding cached inode tracking.  Each mount maintains
groups of cached inode bitmaps at the same granularity as inode locking.
As a mount performs its final iput it gets a bitmap from the server
which indicates if any other mount has inodes in the group open.

This makes the two fast paths of opening and closing linked files and of
deleting a file that was unlinked locally only pay a moderate cost of
either maintaining the bitmap locally and only getting the open map once
per lock group.  Removing many files in a group will only lock and get
the open map once per group.

Signed-off-by: Zach Brown <zab@versity.com>
2021-04-21 12:17:33 -07:00
Zach Brown da1af9b841 Add scoutfs inode ino lock coverage
Add lock coverage which tracks if the inode has been refreshed and is
covered by the inode group cluster lock.  This will be used by
drop_inode and evict_inode to discover that the inode is current and
doesn't need to be refreshed.

Signed-off-by: Zach Brown <zab@versity.com>
2021-04-13 12:10:35 -07:00
Andy Grover 6406f05350 cleanup: Remove struct net_lock_grant_response
We're not using the roots member of this struct, so we can just
use struct scoutfs_net_lock directly.

Signed-off-by: Andy Grover <agrover@versity.com>
2021-04-07 10:13:56 -07:00
Zach Brown 32e7978a6e Extend lock invalidate grace period
The grace period is intended to let lock holders squeeze in more bulk
work before another node pulls the lock out from under them.  The length
of the delay is a balance between getting more work done per lock hold
and adding latency to ping-ponging workloads.

The current grace period was too short.  To do work in the conflicting
case you often have to read the result that the other mount wrote as you
invalidated their lock.  The test was written in the LSM world where
we'd effectively read a single level 0 1MB segment.  In the btree world
we're checking bloom blocks and reading the other mount's btree.  It has
more dependent read latency.

So we turn up the grace period to let conflicting readers squeeze in
more work before pulling the lock out from under them.  This value was
chosen to make lock-conflicting-batch-commit pass in guests sharing nvme
metadata devices in debugging kernels.

Signed-off-by: Zach Brown <zab@versity.com>
2021-01-26 14:46:07 -08:00
Zach Brown 631801c45c Don't queue lock invalidation work during shutdown
The lock invalidation work function needs to be careful not to requeue
itself while we're shutting down or we can be left with invalidation
functions racing with shutdown.  Invalidation calls igrab so we can end
up with unmount warning that there are still inodes in use.

Signed-off-by: Zach Brown <zab@versity.com>
2021-01-26 14:46:07 -08:00
Andy GroverandZach Brown 73333af364 scoutfs: Use enum for lock mode
Signed-off-by: Andy Grover <agrover@versity.com>
2020-11-30 13:35:44 -08:00
Zach BrownandZach Brown ff532eba75 scoutfs: recover max lock write_version
Write locks are given an increasing version number as they're granted
which makes its way into items in the log btrees and is used to find the
most recent version of an item.

The initialization of the lock server's next write_version for granted
locks dates back to the initial prototype of the forest of log btrees.
It is only initialized to zero as the module is loaded.  This means that
reloading the module, perhaps by rebooting, resets all the item versions
to 0 and can lead to newly written items being ignored in favour of
older existing items with greater versions from a previous mount.

To fix this we initialize the lock server's write_version to the
greatest of all the versions in items in log btrees.  We add a field to
the log_trees struct which records the greatest version which is
maintained as we write out items in transactions.  These are read by the
server as it starts.

Then lock recovery needs to include the write_version so that the
lock_server can be sure to set the next write_version past the greatest
version in the currently granted locks.

Signed-off-by: Zach Brown <zab@versity.com>
2020-10-30 11:14:10 -07:00
Zach BrownandZach Brown 12067e99ab scoutfs: remove item granular work from forest
Now that the item cache is bearing the load of high frequency item
calls, we can remove all the item granular work that the forest was
trying to do.  The item cache amortizes the cost of the forest so its
remaining methods can go straight to the btrees and don't need
complicated state to reduce the overhead of item calls.

Signed-off-by: Zach Brown <zab@versity.com>
2020-08-26 14:39:12 -07:00
Zach BrownandZach Brown 6bacd95aea scoutfs: fs uses item cache instead of forest
Use the new item cache for all the item work in the fs instead of
calling into the forest of btrees.  Most of this is mechanical
conversion from the _forest calls to the _item calls.  The item cache
no longer supports the kvec argument for describing values so all the
callers pass in the value pointer and length directly.

The item cache doesn't support saving items as they're deleted and later
restoring them from an error unwinding path.  There were only two users
of this.  Directory entries can easily guarantee that deletion won't
fail by dirtying the items first in the item cache.  Xattr updates were
a little trickier.  They can combine dirtying, creating, updating, and
deleting to atomically switch between items that describe different
versions of a multi-item value.  This also fixed a bug in the srch
xattrs where replacing an xattr would create a new id for the xattr and
leave existing srch items referencing a now deleted id.  Replacing now
reuses the old id.

And finally we add back in the locking and transaction item cache
integration.

Signed-off-by: Zach Brown <zab@versity.com>
2020-08-26 14:39:12 -07:00
Zach BrownandZach Brown d440056e6f scoutfs: remove unused xattr index code
Remove the last remnants of the indexed xattrs which used fs items.
This makes the significant change of renumbering the key zones so I
wanted it in its own commit.

Signed-off-by: Zach Brown <zab@versity.com>
2020-08-26 14:39:12 -07:00
Zach BrownandZach Brown ca6b7f1e6d scoutfs: lock invalidate only syncs dirty
Lock invalidation has to make sure that changes are visible to future
readers.  It was syncing if the current transaction is dirty.  This was
never optimal, but it wasn't catastrophic when concurrent invalidation
work could all block on one sync in progress.

With the move to a single invalidation worker serially invalidating
locks it became unacceptable.  Invalidation happening in the presence of
writers would constantly sync the current transaction while very old
unused write locks were invalidated.  Their changes had long since been
committed in previous transactions.

We add a lock field to remember the transaction sequence which could
have been dirtied under the lock.  If that transaction has already been
comitted by the time we invalidate the lock it doesn't have to sync.

Signed-off-by: Zach Brown <zab@versity.com>
2020-08-26 14:39:12 -07:00
Zach BrownandZach Brown 55dde87bb1 scoutfs: fix lock invalidation work deadlock
The client lock network message processing callbacks were built to
simply perform the processing work for the message in the networking
work context that it was called in.  This particularly makes sense for
invalidation because it has to interact with other components that
require blocking contexts (syncing commits, invalidating inodes,
truncating pages, etc).

The problem is that these messages are per-lock.  With the right
workloads we can use all the capacity for executing work just in lock
invalidation work.  There is no more work execution available for other
network processing.  Critically, the blocked invalidation work is
waiting for the commit thread to get its network responses before
invalidation can make forward progress.  I was easily reproducing
deadlocks by leaving behind a lot of locks and then triggering a flood
of invalidation requests on behalf of shrinking due to memory pressure.

The fix is to put locks on lists and have a small fixed number of work
contexts process all the locks pending for each message type.  The
network callbacks don't block, they just put the lock on the list and
queue the work that will walk the lists.  Invalidation now blocks one
work context, not the number of incoming requests.

There were some wait conditions in work that used to use the lock workq.
Other paths that change those conditions now have to know to queue the
work specifically, not just wake tasks which included blocked work
executors.

The other subtle impact of the change is that we can no longer rely on
networking to shutdown message processing work that was happening in its
callbacks.  We have to specifically stop our work queues in _shutdown.

Signed-off-by: Zach Brown <zab@versity.com>
2020-08-26 14:39:12 -07:00
Zach BrownandZach Brown cca83b1758 scoutfs: rework get_fs_roots to get_roots
The get_fs_roots rpc and server interfaces were built around individual
roots.  Rebuild it around passing around a struct so that we can add
roots without impacting all the current users.

Signed-off-by: Zach Brown <zab@versity.com>
2020-08-26 14:39:12 -07:00
Zach BrownandZach Brown b7943c5412 scoutfs: avoid reading forest roots with block IO
The forest item operations were reading the super block to find the
roots that it should read items from.

This was easiest to implement to start, but it is too expensive.  We
have to find the roots for every newly acquired lock and every call to
walk the inode seq indexes.

To avoid all these reads we first send the current stable versions of
the fs and logs btrees roots along with root grants.  Then we add a net
command to get the current stable roots from the server.  This is used
to refresh the roots if stale blocks are encountered and on the seq
index queries.

Signed-off-by: Zach Brown <zab@versity.com>
2020-08-26 14:39:12 -07:00
Zach BrownandZach Brown c10c7d9748 scoutfs: clean up forest lock data
The client lock code forgot to call into the forest to clear its
per-lock tracking before freeing the lock.  This would result in a slow
memory leak over time as locks were reclaimed by memory pressure.  It
shouldn't have affected consistency.

Signed-off-by: Zach Brown <zab@versity.com>
2020-02-28 11:34:02 -08:00
Zach BrownandZach Brown dee9fbcf66 scoutfs: use packed extents and bitmaps
The btree forest item storage doesn't have as much item granular state
as the item cache did.  The item cache could tell if a cached item was
populated from persistent storage or was created in memory.  It could
simply remove created items rather than leaving behind a deletion item.

The cached btree blocks in the btree forest item storage mechanism can't
do this.  It has to create deletion items when deleting newly created
items because it doesn't know if the item already exists in the
persistent record or not.

This created a problem with the extent storage we were using.  The
individual extent items were stored with a key set to the last logical
block of their extent.  As extents grew or shrank they often were
deleted and created at different key values during a transaction.  In
the btree forest log trees this left a huge stream of deletion items
beind, one for every previous version of the extent.  Then searches for
an extent covering a block would have to skip over all these deleted
items before hitting the current stored extent.

Streaming writes would operate on O(n) for every extent operation.  It
got to be out of hand.  This large change solves the problem by using
more coarse and stable item storage to track free blocks and blocks
mapped into file data.

For file data we now have large packed extent items which store packed
representations of all the logical mappings of a fixed region of a file.
The data code has loading and storage functions which transfer that
persistent version to and from the version that is modified in memory.

Free blocks are stored in bitmaps that are similarly efficiently packed
into fixed size items.  The client is no longer working with free extent
items managed by the forest, it's working with free block bitmap btrees
directly.  It needs access to the client's metadata block allocator and
block write contexts so we move those two out of the forest code and up
into the transaction.

Previously the client and server would exchange extents with network
messages.  Now the roots of the btrees that store the free block bitmap
items are communicated along with the roots of the other trees involved
in a transaction.  The client doesn't need to send free extents back to
the server so we can remove those tasks and rpcs.

The server no longer has to manage free extents.  It transfers block
bitmap items between trees around commits.   All of its extent
manipulation can be removed.

The item size portion of transaction item counts are removed because
we're not using that level of granularity now that metadata transactions
are dirty btree blocks instead of dirty items we pack into fixed sized
segments.

Signed-off-by: Zach Brown <zab@versity.com>
2020-01-17 11:21:36 -08:00
Zach BrownandZach Brown fbffad1d51 scoutfs: add initial lock write_version
We need a way to compare two items in different log btrees and learn
which is the most recent.  Each time we grant a new write lock we give
it a larger write version.  Items store the version of the lock they're
written under.  Readers can now easily see which item is newer.

This is a trivial initial implementation which is not consistent across
unmount or server failover.   We'll need to recover the greatest
write_version from locks during recovery and from log trees as the
server starts up.

Signed-off-by: Zach Brown <zab@versity.com>
2020-01-17 11:21:36 -08:00
Zach BrownandZach Brown 58f062a2c1 scoutfs: use forest in locking and transaction
Transaction commit now has to ask the forest to write the btrees during
a transaction commit instead of writing dirty items in segments.  It
also determines if holds fit in the dirty transaction by looking at
dirty btree blocks instead of item counts.

Locking no longer has to invalidate a private item cache because the
forest paths use the btree block cache where inconsistency is discovered
and invalidated as blocks are read.

Signed-off-by: Zach Brown <zab@versity.com>
2020-01-17 11:21:36 -08:00
Zach BrownandZach Brown ac2d00629c scoutfs: add scoutfs_lock_protected()
The item code had a manual comparison of lock modes when testing if a
given access was protected by a held lock.  Let's offer a proper
interface from the lock code.

Signed-off-by: Zach Brown <zab@versity.com>
2020-01-17 11:21:36 -08:00
Zach BrownandZach Brown 15becd6ef8 scoutfs: force locks idle as shutdown frees
Usually lock_free() is called as users finish using a lock and when its
state shows that it is idle and won't be freed out from under another
use.

During shutdown we manually call lock_free() on all locks because
shutdown promises that there will be no more lock users, including
networking callbacks.  But there is a case where network requests can be
pending and we shutdown before waiting for their reply.  This trips
BUG_ON assertions in lock_free() that would otherwise catch unsafe calls
of lock_free().

This is easiest to reproduce by interrupting a mount (which is waiting
on a lock to read the root inode).

The fix is to update each lock's state during shutdown to reflect the
promise made by shutdown.  Requests aren't actually pending because
we've shutdown networking befrore getting here.

Signed-off-by: Zach Brown <zab@versity.com>
2019-09-10 09:57:37 -07:00
Zach BrownandZach Brown 6f5cfd8cc2 scoutfs: use rid instead of node_id in items
Use the mount's generated random id in persistent items and the lock
that protects them instead of the assigned node_id.

Signed-off-by: Zach Brown <zab@versity.com>
2019-08-20 15:52:13 -07:00
Zach BrownandZach Brown 7dfbd3950f scoutfs: add index of inodes by xattr names
Add a .indx. xattr tag which adds the inode to an index of inodes keyed
by the hash of xattr names.  An ioctl is added which then returns all
the inodes which may contain an xattr of the given name.  Dropping all
xattrs now has to parse the name to find out if it also has to delete an
index item.

Signed-off-by: Zach Brown <zab@versity.com>
2019-06-24 09:58:22 -07:00
Zach BrownandZach Brown a6782fc03f scoutfs: add data waiting
One of the core features of scoutfs is the ability to transparently
migrate file contents to and from an archive tier.  For this to be
transparent we need file system operations to trigger staging the file
contents back into the file system as needed.

This adds the infrastructure which operations use to wait for offline
extents to come online and which provides userspace with a list of
blocks that the operations are waiting for.

We add some waiting infrastructure that callers use to lock, check for
offline extents, and unlock and wait before checking again to see if
they're still offline.  We add these checks and waiting to data io
operations that could encounter offline extents.

This has to be done carefully so that we don't wait while holding locks
that would prevent staging.  We use per-task structures to discover when
we are the first user of a cluster lock on an inode, indicating that
it's safe for us to wait because we don't hold any locks.

And while we're waiting our operation is tracked and reported to
userspace through an ioctl.  This is a non-blocking ioctl, it's up to
userspace to decide how often to check and how large a region to stage.

Waiters are woken up when the file contents could have changed, not
specifically when we know that the extent has come online.  This lets us
wake waiters when their lock is revoked so that they can block waiting
to reacquire the lock and test the extents again.  It lets us provide
coherent demand staging across the cluster without fine grained waiting
protocols sent betwen the nodes.  It may result in some spurious wakeups
and work but hopefully it won't, and it's a very simple and functional
first pass.

Signed-off-by: Zach Brown <zab@versity.com>
2019-05-21 11:33:26 -07:00
Zach BrownandZach Brown a546bd0aab scoutfs: check for newlines in msg.h wrappers
The message formatter adds a newline so callers don't have to.  But
sometimes they do and we get double newlines.  Add a build check that
the format string doesn't end in a newline so that we stop adding these.
And fix up all the current offenders.

Signed-off-by: Zach Brown <zab@versity.com>
2019-04-12 10:54:07 -07:00
Zach BrownandZach Brown ec0fb5380a scoutfs: implement lock recovery
When a server crashes all the connected clients still have operational
locks and can be using them to protect IO.  As a new server starts up
its lock service needs to account for those outstanding locks before
granting new locks to clients.

This implements lock recovery by having the lock service recover locks
from clients as it starts up.

First the lock service stores records of connected clients in a btree
off the super block.  Records are added as the server receives their
greeting and are removed as the server receives their farewell.

Then the server checks for existing persistent records as it starts up.
If it finds any it enters recovery and waits for all the old clients to
reconnect before resuming normal processing.

We add lock recover request and response messages that are used to
communicate locks from the clients to the server.

Signed-off-by: Zach Brown <zab@versity.com>
2019-04-12 10:54:07 -07:00
Zach BrownandZach Brown 08a140c8b0 scoutfs: use our locking service
Convert client locking to call the server's lock service instead of
using a fs/dlm lockspace.

The client code gets some shims to send and receive lock messages to and
from the server.  Callers use our lock mode constants instead of the
DLM's.

Locks are now identified by their starting key instead of an additional
scoped lock name so that we don't have more mapping structures to track.
The global rename lock uses keys that are defined by the format as only
used for locking.

The biggest change is in the client lock state machine.  Instead of
calling the dlm and getting callbacks we send messages to our server and
get called from incoming message processing.  We don't have everything
come through a per-lock work queue.  Instead we send requests either
from the blocking lock caller or from a shrink work queue.  Incoming
messages are called in the net layer's blocking work contexts so we
don't need to do any more work to defer to other contexts.

The different processing contexts leads to a slightly different lock
life cycle.  We refactor and seperate allocation and freeing from
tracking and removing locks in data structures.  We add a _get and _put
to track active use of locks and then async references to locks by
holders and requests are tracked seperately.

Our lock service's rules are a bit simpler in that we'll only ever send
one request at a time and the server will only ever send one request at
a time.  We do have to do a bit of work to make sure we process back to
back grant reponses and invalidation requests from the server.

As of this change the lock setup and destruction paths are a little
wobbly.  They'll be shored up as we add lock recovery between the client
and server.

Signed-off-by: Zach Brown <zab@versity.com>
2019-04-12 10:54:07 -07:00
Zach BrownandZach Brown 7c8383eddd scoutfs: add scoutfs_lock_rename()
Add a specific lock method for locking the global rename lock instead of
having the caller specify it as a global lock.  We're getting rid of the
notion of lock scopes and requiring all locks to be related to keys.
The rename lock will use magic keys at the end of the volume.

Signed-off-by: Zach Brown <zab@versity.com>
2019-04-12 10:54:07 -07:00
Zach BrownandZach Brown 62d6c11e3c scoutfs: clean up workqueue flags
We had gotten a bit sloppy with the workqueue flags.  We needed _UNBOUND
in some workqueues where we wanted concurrency by scheduling across cpus
instead of waiting for the current (very long running) work on a cpu to
finish.  We add NON_REENTRANT out of an abundance of caution.  It has
gone away in modern kernels and is probably not needed here, but
according to the docs we would want it so we at least document that fact
by using it.

Signed-off-by: Zach Brown <zab@versity.com>
2018-08-28 15:34:30 -07:00
Zach BrownandZach Brown a72b7a9001 scoutfs: convert locks seq to trivial seq
Signed-off-by: Zach Brown <zab@versity.com>
2018-07-27 09:50:21 -07:00
Zach BrownandZach Brown 784cda9bee scoutfs: more carefully set lock bast mode
Locks get a bast call from the dlm when a remote node is blocked waiting
for the mode of a lock to change.  We'd set the mode that we need to
convert to and kick off lock work to make forward progress.

The bast calls can happen at any old time.  If a call came in as we were
unlocking a lock we'd set its bast mode even though it was being
unlocked and would not need to be down converted.

Usually this bad mode would be fine because the lock was idle and would
just be freed after being locked.

But if someone was actively waiting for the lock it would get stuck in
an unlocked state.  The bad bast mode would prevent it from being
upconverted, but the waiters would stop it from being freed.

We fix this by only setting the mode from the bast call if there is
really work to do.  This avoids setting the bast for unlocked locks
which will let the lock state machine re-acquire them and make forward
progress on behalf of the waiters.

Signed-off-by: Zach Brown <zab@versity.com>
2018-07-02 14:16:50 -07:00
Zach BrownandZach Brown 55e063d2a1 scoutfs: get rid of silly lock destroy BUG_ON
The BUG_ON() at the start of scoutfs_lock_destroy() was intended to
ensure that scoutfs_lock_shutdown() had been called first.  But that
doesn't happen in the case where we get an error during mount.

The _destroy() function is careful to notice active use and only tears
down resources that were created.  The BUG_ON() can just be removed.

Signed-off-by: Zach Brown <zab@versity.com>
2018-05-04 09:21:44 -07:00
Zach BrownandMark Swan 9148f24aa2 scoutfs: use single small key struct
Variable length keys lead to having a key struct point to the buffer
that contains the key.  With dirents and xattrs now using small keys we
can convert everyone to using a single key struct and significantly
simplify the system.

We no longer have a seperate generic key buf struct that points to
specific per-type key storage.  All items use the key struct and fill
out the appropriate fields.  All the code that paired a generic key buf
struct and a specific key type struct is collapsed down to a key struct.
There's no longer the difference between a key buf that shares a
read-only key, has it's own precise allocation, or has a max size
allocation for incrementing and decrementing.

Each key user now has an init function fills out its fields.  It looks a
lot like the old pattern but we no longer have seperate key storage that
the buf points to.

A bunch of code now takes the address of static key storage instead of
managing allocated keys.  Conversely, swapping now uses the full keys
instead of pointers to the keys.

We don't need all the functions that worked on the generic key buf
struct because they had different lengths.  Copy, clone, length init,
memcpy, all of that goes away.

The item API had some functions that tested the length of keys and
values.  The key length tests vanish, and that gets rid of the _same()
call.  The _same_min() call only had one user who didn't also test for
the value length being too large.  Let's leave caller key constraints in
callers instead of trying to hide them on the other side of a bunch of
item calls.

We no longer have to track the number of key bytes when calculating if
an item population will fit in segments.  This removes the key length
from reservations, transactions, and segment writing.

The item cache key querying ioctls no longer have to deal with variable
length keys.  The simply specify the start key, the ioctls return the
number of keys copied instead of bytes, and the caller is responsible
for incrementing the next search key.

The segment no longer has to store the key length.  It stores the key
struct in the item header.

The fancy variable length key formatting and printing can be removed.
We have a single format for the universal key struct.  The SK_ wrappers
that bracked calls to use preempt safe per cpu buffers can turn back
into their normal calls.

Manifest entries are now a fixed size.  We can simply split them between
btree keys and values and initialize them instead of allocating them.
This means that level 0 entries don't have their own format that sorts
by the seq.  They're sorted by the key like all the other levels.
Compaction needs to sweep all of them looking for the oldest and read
can stop sweeping once it can no longer overlap.  This makes rare
compaction more expensive and common reading less expensive, which is
the right tradeoff.

Signed-off-by: Zach Brown <zab@versity.com>
2018-04-04 09:15:27 -05:00
Zach BrownandZach Brown 0b54d71b98 scoutfs: avoid double unlock
We weren't sufficiently careful in reacting to basts.  If a bast arrived
whlie an unlock is in flight we'd turn around and try to unlock again,
returning an error, and exploding.

More carefully only act on basts if we have an active mode that needs to
be unlocked.  Now if the racey bast arrives we'll ignore it and end up
freeing the lock in processing after the unlock succeeds.

Signed-off-by: Zach Brown <zab@versity.com>
2018-03-13 13:14:26 -07:00
Zach BrownandZach Brown d58c8d5993 scoutfs: move lock work after dependencies
Some of the lock processing path was happening too early.  Both
maintainance of the locks on the LRU and waking waiters depends on
whether there is work pending and on the the granted mode.  Those are
changed in the middle by processing so we need to move these two bits of
work down so that they can consume the updated state.

Signed-off-by: Zach Brown <zab@versity.com>
2018-03-09 14:29:55 -08:00
Zach BrownandZach Brown 9ad0f81084 scoutfs: add some lock/item consistency checks
Add some tests to the locking paths to see if we violate item caching
rules.

As we finish locking calls we make sure that the item cache is
consistent with the lock mode.  And we make sure that we don't free
locks before they've been unlocked and had a chance to check the
item cache.

Signed-off-by: Zach Brown <zab@versity.com>
2018-03-09 14:29:55 -08:00
Zach BrownandZach Brown f9e282048f scoutfs: revalidate dentries by checking items
Initially we had d_revalidate always return that the dentry was invalid.
This avoids dentry cache consistency problems across the cluster by
always performing lookups.  That's slow by itself, but it turns out that
the dentry invalidation that happens on revalidation failure is very
expensive if you have lots of dentries.

So we switched to forcefully dropping dirents as we revoked their lock.
That avoided the cost of revalidation failure but it adds the problem
that dentries are unhashed when their locks are dropped.  This causes
paths like getcwd() to return errors when they see unhashed dentries
instead of trying to revalidate them.

This implements a d_revalidate which actually does work to determine if
the dentry is still valid.  When we populate dentries under a lock we
add them to a list on the lock.  As we drop the lock we remove them from
the list.  But the dentry is not modified.  This lets paths like
getcwd() still work.  Then we implement revalidation that does the
actual item lookups if the dentry's lock has been dropped.  This lets
revalidation return success and avoid the terrible invalidation costs
from returning failure and then calling lookup to populate a new dentry.

This brings us more in line with the revalidation behaviour of other
systems that maintain multi-node dcache consistency.

Signed-off-by: Zach Brown <zab@versity.com>
2018-02-28 11:18:57 -08:00
Zach BrownandZach Brown e31e828aff scoutfs: don't livelock conflicting waiters
If there are two tasks waiting for conflicting modes, say a writer
waiting for a CW index lock and a index walker waiting for a PR index
lock, they can livelock.

In the ast one of their modes will be granted.  We'll wake them under
the lock now that they can see that their mode is ready.  But then while
still under the lock we see a conflicting waiter, and no users, so we
immediately start converting the lock away to the other waiting
conflicting mode.  The woken waiter is scheduled but now sees that the
lock isn't granted anymore because it's converting.  This bounces back
and forth forever.

The fix is to refuse to start conversion while there are still waiters
for the currently granted mode.  Once they finish it'll be able to
convert.

Signed-off-by: Zach Brown <zab@versity.com>
2018-02-21 15:14:29 -08:00
Zach BrownandZach Brown f52dc28322 scoutfs: simplify lock use of kernel dlm
We had an excessive number of layers between scoutfs and the dlm code in
the kernel.  We had dlmglue, the scoutfs locks, and task refs.  Each
layer had structs that track the lifetime of the layer below it.  We
were about to add another layer to hold on to locks just a bit longer so
that we can avoid down conversion and transaction commit storms under
contention.

This collapses all those layers into simple state machine in lock.c that
manages the mode of dlm locks on behalf of the file system.

The users of the lock interface are mainly unchanged.  We did change
from a heavier trylock to a lighter nonblock lock attempt and have to
change the single rare readpage use.  Lock fields change so a few
external users of those fields change.

This not only removes a lot of code it also contains functional
improvements.  For example, it can now convert directly to CW locks with
a single lock request instead of having to use two by first converting
to NL.

It introduces the concept of an unlock grace period.  Locks won't be
dropped on behalf of other nodes soon after being unlocked so that tasks
have a chance to batch up work before the other node gets a chance.
This can result in two orders of magnitude improvements in the time it
takes to, say, change a set of xattrs on the same file population from
two nodes concurrently.

There are significant changes to trace points, counters, and debug files
that follow the implementation changes.

Signed-off-by: Zach Brown <zab@versity.com>
2018-02-14 15:00:17 -08:00
Zach BrownandMark Fasheh a49061a7d9 scoutfs: remove the size index
We aren't using the size index. It has runtime and code maintenance
costs that aren't worth paying.  Let's remove it.

Removing it from the format and no longer maintaining it are straight
forward.

The bulk of this patch is actually the act of removing it from the index
locking functions.  We no longer have to predict the size that will be
stored during the transaction to lock the index items that will be
created during the transaction.  A bunch of code to predict the size and
then pass it into locking and transactions goes away.  Like other inode
fields we now update the size as it changes.

Signed-off-by: Zach Brown <zab@versity.com>
2018-01-30 15:03:35 -08:00
Zach BrownandMark Fasheh 9cc750c4ec scoutfs: remove lock idr in free, not put
The idr entry that identifies a lock's position in the debugfs locks
file is allocated early in the process of building up a lock.  Today the
idr entry is only destroyed in put_(), which is called later once
reference counts are established.  Errors before then just call free_()
and can leave idrs around that reference freed memory.

This always destroys the idr entry in free_().  We no longer leave idr
entries around that reference freed memory.

This fixes use after free while walking the debugfs file which can hit
in scoutfs/006 which uses the locks file.

Signed-off-by: Zach Brown <zab@versity.com>
2018-01-30 11:04:47 -08:00
Zach BrownandMark Fasheh e803b10bca scoutfs: drop lock refcnt/users under task ref
If scoutfs_unlock() sees that it isn't the last task using a lock it
just returns.  It doesn't unlock the lock and it doesn't drop the lock
refcnt and users.

This leaks the lock refcnt and users because find_alloc_scoutfs_lock()
always increments them when it finds a lock.  Inflated counts will stop
the shrinker from freeing the locks and eventually the counts will wrap
and could cause locks to be freed while they're still in use.

We can either always drop the refcnt/users in unlock or we can drop them
in lock as we notice that our task already has the lock.  I chose to
have the task ref hold one refcnt/users which are only dropped as the
final task unlocks.

Signed-off-by: Zach Brown <zab@versity.com>
2018-01-15 10:04:15 -08:00
Zach BrownandMark Fasheh f54e59eef1 scoutfs: add debugfs "locks" for scoutfs_lock
Add a file for showing the scoutfs_lock struct contents.  This is the
layer above the detailed dlmglue/dlm info provided in the existing
"locking_state" file.

Signed-off-by: Zach Brown <zab@versity.com>
2018-01-15 10:04:15 -08:00
Mark FashehandZach Brown afc798599f scoutfs: invalidate cache when we free locks
We weren't invalidating our cache before freeing locks due to memory
pressure. This would cause stale data on the node which originally held
the lock. Fix this by firing a callback from dlmglue before we free a
lock from the system. On the scoutfs side, the callback is wired to
call our invalidate function. This will ensure that the right data and
metadata hit disk before another node is allowed to acquire that lock.

Signed-off-by: Mark Fasheh <mfasheh@versity.com>
2018-01-14 10:58:05 -08:00
Mark FashehandZach Brown 3661f06bec scoutfs: add trigger to drop lock cache
We have a corruption that can happen when a lock is reclaimed but it's
cache is still dirty. Detect this corruption by placing a trigger in
statfs which fires off lock reclaim. Statfs is nice because for scoutfs
it's lockless, which means there should not be any references on locks
when the trigger is fired.

Signed-off-by: Mark Fasheh <mfasheh@versity.com>
2018-01-14 10:58:05 -08:00
Mark FashehandZach Brown 5cc05d663e scoutfs: count some lock events by type
We use a new event callback in dlmglue so that scout has a chance to do
some per-lock type counters. I included the most important dlmglue
events - basically those which can cost us network or disk traffic.

Right now scout just counts downconvert events since those are the
most interesting to us. We also just count on the ino and index locks
for now.

Signed-off-by: Mark Fasheh <mfasheh@versity.com>
2017-12-21 13:20:56 -08:00