mirror of
https://git.openafs.org/openafs.git
synced 2025-01-18 15:00:12 +00:00
c6f5ebc4cf
The doc/txt directory has become the de facto home for text-based technical notes. Relocate the contents of the doc/arch directory to doc/txt. Relocate doc/examples to doc/txt/examples. Update the doc/README file to be more current and remove old work in progress comments. Change-Id: Iaa53e77eb1f7019d22af8380fa147305ac79d055 Reviewed-on: https://gerrit.openafs.org/12675 Tested-by: BuildBot <buildbot@rampaginggeek.com> Reviewed-by: Benjamin Kaduk <kaduk@mit.edu>
397 lines
19 KiB
Plaintext
397 lines
19 KiB
Plaintext
The Demand-Attach FileServer (DAFS) has resulted in many changes to how
|
|
many things on AFS fileservers behave. The most sweeping changes are
|
|
probably in the volume package, but significant changes have also been
|
|
made in the SYNC protocol, the vnode package, salvaging, and a few
|
|
miscellaneous bits in the various fileserver processes.
|
|
|
|
This document serves as an overview for developers on how to deal with
|
|
these changes, and how to use the new mechanisms. For more specific
|
|
details, consult the relevant doxygen documentation, the code comments,
|
|
and/or the code itself.
|
|
|
|
- The salvageserver
|
|
|
|
The salvageserver (or 'salvaged') is a new OpenAFS fileserver process in
|
|
DAFS. This daemon accepts salvage requests via SALVSYNC (see below), and
|
|
salvages a volume group by fork()ing a child, and running the normal
|
|
salvager code (it enters vol-salvage.c by calling SalvageFileSys1).
|
|
|
|
Salvages that are initiated from a request to the salvageserver (called
|
|
'demand-salvages') occur automatically; whenever the fileserver (or
|
|
other tool) discovers that a volume needs salvaging, it will schedule a
|
|
salvage on the salvageserver without any intervention needed.
|
|
|
|
When scheduling a salvage, the vol id should be the id for the volume
|
|
group (the RW vol id). If the salvaging child discovers that it was
|
|
given a non-RW vol id, it will send the salvageserver a SALVSYNC LINK
|
|
command, and will exit. This will tell the salvageserver that whenever
|
|
it receives a salvage request for that vol id, it should schedule a
|
|
salvage for the corresponding RW id instead.
|
|
|
|
- FSSYNC/SALVSYNC
|
|
|
|
The FSSYNC and SALVSYNC protocols are the protocols used for
|
|
interprocess communication between the various fileserver processes.
|
|
FSSYNC is used for querying the fileserver for volume metadata,
|
|
'checking out' volumes from the fileserver, and a few other things.
|
|
SALVSYNC is used to schedule and query salvages in the salvageserver.
|
|
|
|
FSSYNC existed prior to DAFS, but it encompasses a much larger set of
|
|
commands with the advent of DAFS. SALVSYNC is entirely new to DAFS.
|
|
|
|
-- SYNC
|
|
|
|
FSSYNC and SALVSYNC are both layered on top of a protocol called SYNC.
|
|
SYNC isn't much a protocol in itself; it just handles some boilerplate
|
|
for the messages passed back and forth, and some error codes common to
|
|
both FSSYNC and SALVSYNC.
|
|
|
|
SYNC is layered on top of TCP/IP, though we only use it to communicate
|
|
with the local host (usually via a unix domain socket). It does not
|
|
handle anything like authentication, authorization, or even things like
|
|
serialization. Although it uses network primitives for communication,
|
|
it's only useful for communication between processes on the same
|
|
machine, and that is all we use it for.
|
|
|
|
SYNC calls are basically RPCs, but very simple. The calls are always
|
|
synchronous, and each SYNC server can only handle one request at a time.
|
|
Thus, it is important for SYNC server handlers to return as quickly as
|
|
possible; hitting the network or disk to service a SYNC request should
|
|
be avoided to the extent that such is possible.
|
|
|
|
SYNC-related source files are src/vol/daemon_com.c and
|
|
src/vol/daemon_com.h
|
|
|
|
-- FSSYNC
|
|
|
|
--- server
|
|
|
|
The FSSYNC server runs in the fileserver; source is in
|
|
src/vol/fssync-server.c.
|
|
|
|
As mentioned above, FSSYNC handlers should finish quickly when
|
|
servicing a request, so hitting the network or disk should be avoided.
|
|
In particular, you absolutely cannot make a SALVSYNC call inside an
|
|
FSSYNC handler; the SALVSYNC client wrapper routines actively prevent
|
|
this from happening, so even if you try to do such a thing, you will not
|
|
be allowed to. This prohibition is to prevent deadlock, since the
|
|
salvageserver could have made the FSSYNC request that you are servicing.
|
|
|
|
When a client makes a FSYNC_VOL_OFF or NEEDVOLUME request, the
|
|
fileserver offlines the volume if necessary, and keeps track that the
|
|
volume has been 'checked out'. A volume is left online if the checkout
|
|
mode indicates the volume cannot change (see VVolOpLeaveOnline_r).
|
|
|
|
Until the volume has been 'checked in' with the ON, LEAVE_OFFLINE, or
|
|
DONE commands, no other program can check out the volume.
|
|
|
|
Other FSSYNC commands include abilities to query volume metadata and
|
|
stats, to force volumes to be attached or offline, and to update the
|
|
volume group cache. See doc/arch/fssync.txt for documentation on the
|
|
individual FSSYNC commands.
|
|
|
|
--- clients
|
|
|
|
FSSYNC clients are generally any OpenAFS process that runs on a
|
|
fileserver and tries to access volumes directly. The volserver,
|
|
salvageserver, and bosserver all qualify, as do (sometimes) some
|
|
utilities like vol-info or vol-bless. For issuing FSSYNC commands
|
|
directly, there is the debugging tool fssync-debug. FSSYNC client code
|
|
is in src/vol/fssync-client.c, but it's not very interesting.
|
|
|
|
Any program that wishes to directly access a volume on disk must check
|
|
out the volume via FSSYNC (NEEDVOLUME or OFF commands), to ensure the
|
|
volume doesn't change while the program is using it. If the program
|
|
determines that the volume is somehow inconsistent and should be
|
|
salvaged, it should send the FSSYNC command FORCE_ERROR with reason code
|
|
FSYNC_SALVAGE to the fileserver, which will take care of salvaging it.
|
|
|
|
-- SALVSYNC
|
|
|
|
The SALVSYNC server runs in the salvageserver; code is in
|
|
src/vol/salvsync-server.c. SALVSYNC clients are just the fileserver, the
|
|
salvageserver run with the -client switch, and the salvageserver worker
|
|
children. If any other process notices that a volume needs salvaging, it
|
|
should issue a FORCE_ERROR FSSYNC command to the fileserver with the
|
|
FSYNC_SALVAGE reason code.
|
|
|
|
The SALVSYNC protocol is simpler than the FSSYNC protocol. The commands
|
|
are basically just to create, cancel, change, and query salvages. The
|
|
RAISEPRIO command increases the priority of a salvage job that hasn't
|
|
started yet, so volumes that are accessed more frequently will get
|
|
salvaged first. The LINK command is used by the salvageserver worker
|
|
children to inform the salvageserver parent that it tried to salvage a
|
|
readonly volume for which a read-write clone exists (in which case we
|
|
should just schedule a salvage for the parent read-write volume).
|
|
|
|
Note that canceling a salvage is just for salvages that haven't run
|
|
yet; it only takes a salvage job off of a queue; it doesn't stop a
|
|
salvageserver worker child in the middle of a salvage.
|
|
|
|
- The volume package
|
|
|
|
-- refcounts
|
|
|
|
Before DAFS, the Volume struct just had one reference count, vp->nUsers.
|
|
With DAFS, we know have the notion of an internal/lightweight reference
|
|
count, and an external/heavyweight reference count. Lightweight refs are
|
|
acquired with VCreateReservation_r, and released with
|
|
VCancelReservation_r. Heavyweight refs are acquired as before, normally
|
|
with a GetVolume or AttachVolume variant, and releasing the ref with
|
|
VPutVolume.
|
|
|
|
Lightweight references are only acquired within the volume package; a vp
|
|
should not be given to e.g. the fileserver code with an extra
|
|
lightweight ref. A heavyweight ref is generally acquired for a vp that
|
|
will be given to some non-volume-package code; acquiring a heavyweight
|
|
ref guarantees that the volume header has been loaded.
|
|
|
|
Acquiring a lightweight ref just guarantees that the volume will not go
|
|
away or suddenly become unavailable after dropping VOL_LOCK. Certain
|
|
operations like detachment or scheduling a salvage only occur when all
|
|
of the heavy and lightweight refs go away; see VCancelReservation_r.
|
|
|
|
-- state machine
|
|
|
|
Instead of having a per-volume lock, each vp always has an associated
|
|
'state', that says what, if anything, is occurring to a volume at any
|
|
particular time; or if the volume is attached, offline, etc. To do the
|
|
basic equivalent of a lock -- that is, ensure that nobody else will
|
|
change the volume when we drop VOL_LOCK -- you can put the volume in
|
|
what is called an 'exclusive' state (see VIsExclusiveState).
|
|
|
|
When a volume is in an exclusive state, no thread should modify the
|
|
volume (or expect the vp data to stay the same), except the thread that
|
|
put it in that state. Whenever you manipulate a volume, you should make
|
|
sure it is not in an exclusive state; first call VCreateReservation_r to
|
|
make sure the volume doesn't go away, and then call
|
|
VWaitExclusiveState_r. When that returns, you are guaranteed to have a
|
|
vp that is in a non-exclusive state, and so can me manipulated. Call
|
|
VCancelReservation_r when done with it, to indicate you don't need it
|
|
anymore.
|
|
|
|
Look at the definition of the VolState enumeration to see all volume
|
|
states, and a brief explanation of them.
|
|
|
|
-- VLRU
|
|
|
|
See: Most functions with VLRU in their name in src/vol/volume.c.
|
|
|
|
The VLRU is what dictates when volumes are detached after a certain
|
|
amount of inactivity. The design is pretty much a generational garbage
|
|
collection mechanism. There are 5 queues that a volume can be on the
|
|
VLRU (VLRUQueueName in volume.h). 'Candidate' volumes haven't seen
|
|
activity in a while, and so are candidates to be detached. 'New' volumes
|
|
have seen activity only recently; 'mid' volumes have seen activity for
|
|
awhile, and 'old' volumes have seen activity for a long while. 'Held'
|
|
volumes cannot be soft detached at all.
|
|
|
|
Volumes are moved from new->mid->old if they have had activity recently,
|
|
and are moved from old->mid->new->candidate if they have not had any
|
|
activity recently. The definition of 'recently' is configurable by the
|
|
-vlruthresh fileserver parameter; see VLRU_ComputeConstants for how they
|
|
are determined. Volumes start at 'new' on attachment, and if any
|
|
activity occurs when a volume is on 'candidate', it's moved to 'new'
|
|
immediately.
|
|
|
|
Volumes are generally promoted/demoted and soft-detached by
|
|
VLRU_ScannerThread, which runs every so often and moves volumes between
|
|
VLRU queues depending on their last access time and the various
|
|
thresholds (or soft-detaches them, in the case of the 'candidate'
|
|
queue). Soft-detaching just means the volume is taken offline and put
|
|
into the preattached state.
|
|
|
|
--- DONT_SALVAGE
|
|
|
|
The dontSalvage flag in volume headers can be set to DONT_SALVAGE to
|
|
indicate that a volume probably doesn't need to be salvaged. Before
|
|
DAFS, volumes were placed on an 'UpdateList' which was periodically
|
|
scanned, and dontSalvage was set on volumes that hadn't been touched in
|
|
a while.
|
|
|
|
With DAFS and the VLRU additions, setting dontSalvage now happens when a
|
|
volume is demoted a VLRU generation, and no separate list is kept. So if
|
|
a volume has been idle enough to demote, and it hasn't been accessed in
|
|
SALVAGE_INTERVAL time, dontSalvage will be set automatically by the VLRU
|
|
scanner.
|
|
|
|
-- Vnode
|
|
|
|
Source files: src/vol/vnode.c, src/vol/vnode.h, src/vol/vnode_inline.h
|
|
|
|
The changes to the vnode package are largely very similar to those in
|
|
the volume package. A Vnode is put into specific states, some of which
|
|
are exclusive and act like locks (see VnChangeState_r,
|
|
VnIsExclusiveState). Vnodes also have refcounts, incremented and
|
|
decremented with VnCreateReservation_r and VnCancelReservation_r like
|
|
you would expect. I/O should be done outside of any global locks; just
|
|
the vnode is 'locked' by being put in an exclusive state if necessary.
|
|
|
|
In addition to a state, vnodes also have a count of readers. When a
|
|
caller gets a vnode with a read lock, we of course must wait for the
|
|
vnode to be in a nonexclusive state (VnWaitExclusive_r), then the number
|
|
of readers is incremented (VnBeginRead_r), but the vnode is kept in a
|
|
non-exclusive state (VN_STATE_READ).
|
|
|
|
When a caller gets a vnode with a write lock, we must wait not only for
|
|
the vnode to be in a nonexclusive state, but also for there to be no
|
|
readers (VnWaitQuiescent_r), so we can actually change it.
|
|
|
|
VnLock still exists in DAFS, but it's almost a no-op. All we do for DAFS
|
|
in VnLock is set vnp->writer to the current thread id for a write lock,
|
|
for some consistency checks later (read locks are actually no-ops).
|
|
Actual mutual exclusion in DAFS is done by the vnode state machine and
|
|
the reader count.
|
|
|
|
- viced state serialization
|
|
|
|
See src/viced/serialize_state.* and ShutDownAndCore in
|
|
src/viced/viced.c
|
|
|
|
Before DAFS, whenever a fileserver restarted, it lost all information
|
|
about all clients, what callbacks they had, etc. So when a client with
|
|
existing callbacks contacted the fileserver, all callback information
|
|
needed to be reset, potentially causing a bunch of unnecessary traffic.
|
|
And of course, if the client does not contact the fileserver again, it
|
|
could not get sent callbacks it should get sent.
|
|
|
|
DAFS now has the ability to save the host and CB data to a file on
|
|
shutdown, and restore it when it starts up again. So when a fileserver
|
|
is restarted, the host and CB information should be effectively the same
|
|
as when it shut down. So a client may not even know if a fileserver was
|
|
restarted.
|
|
|
|
Getting this state information can be a little difficult, since the host
|
|
package data structures aren't necessarily always consistent, even after
|
|
H_LOCK is dropped. What we attempt to do is stop all of the background
|
|
threads early in the shutdown process (set fs_state.mode -
|
|
FS_MODE_SHUTDOWN), and wait for the background threads to exit (or be
|
|
marked as 'tranquil'; see the fs_state struct) later on, before trying
|
|
to save state. This makes it a lot less likely for anything to be
|
|
modifying the host or CB structures by the time we try to save them.
|
|
|
|
- volume group cache
|
|
|
|
See: src/vol/vg_cache* and src/vol/vg_scan.c
|
|
|
|
The VGC is a mechanism in DAFS to speed up volume salvages. Pre-VGC,
|
|
whenever the salvager code salvaged an individual volume, it would need
|
|
to read all of the volume headers on the partition, so it knows what
|
|
volumes are in the volume group it is salvaging, so it knows what
|
|
volumes to tell the fileserver to take offline. With demand-salvages,
|
|
this can make salvaging take a very long time, since the time to read in
|
|
all volume headers can take much more time than the time to actually
|
|
salvage a single volume group.
|
|
|
|
To prevent the need to scan the partition volume headers every single
|
|
time, the fileserver maintains a cache of which volumes are in what
|
|
volume groups. The cache is populated by scanning a partition's volume
|
|
headers, and is started in the background upon receiving the first
|
|
salvage request for a partition (VVGCache_scanStart_r,
|
|
_VVGC_scan_start).
|
|
|
|
After the VGC is populated, it is kept up to date with volumes being
|
|
created and deleted via the FSSYNC VG_ADD and VG_DEL
|
|
commands. These are called every time a volume header is created,
|
|
removed, or changed when using the volume header wrappers in vutil.c
|
|
(VCreateVolumeDiskHeader, VDestroyVolumeDiskHeader,
|
|
VWriteVolumeDiskHeader). These wrappers should always be used to
|
|
create/remove/modify vol headers, to ensure that the necessary FSSYNC
|
|
commands are called.
|
|
|
|
-- race prevention
|
|
|
|
In order to prevent races between volume changes and VGC partition scans
|
|
(that is, someone scans a header while it is being written and not yet
|
|
valid), updates to the VGC involving adding or modifying volume headers
|
|
should always be done under the 'partition header lock'. This is a
|
|
per-partition lock to conceptually lock the set of volume headers on
|
|
that partition. It is only read-held when something is writing to a
|
|
volume header, and it is write-held for something that is scanning the
|
|
partition for volume headers (the VGC or partition salvager). This is a
|
|
little counterintuitive, but it is what we want. We want multiple
|
|
headers to be written to at once, but if we are the VGC scanner, we want
|
|
to ensure nobody else is writing when we look at a header file.
|
|
|
|
Because the race described above is so rare, vol header scanners don't
|
|
actually hold the lock unless a problem is detected. So, what they do is
|
|
read a particular volume header without any lock, and if there is a
|
|
problem with it, they grab a write lock on the partition vol headers,
|
|
and try again. If it still has a problem, the header is just faulty; if
|
|
it's okay, then we avoided the race.
|
|
|
|
Note that destroying vol headers does not require any locks, since
|
|
unlink()s are atomic and don't cause any races for us here.
|
|
|
|
- partition and volume locking
|
|
|
|
Previously, whenever the volserver would attach a volume or the salvager
|
|
would salvage anything, the partition would be locked
|
|
(VLockPartition_r). This unnecessarily serializes part of most volserver
|
|
operations. It also makes it so only one salvage can run on a partition
|
|
at a time, and that a volserver operation cannot occur at the same time
|
|
as a salvage. With the addition of the VGC (previous section), the
|
|
salvager partition lock is unnecessary on namei, since the salvager does
|
|
not need to scan all volume headers.
|
|
|
|
Instead of the rather heavyweight partition lock, in DAFS we now lock
|
|
individual volumes. Locking an individual volume is done by locking a
|
|
certain byte in the file /vicepX/.volume.lock. To lock volume with ID
|
|
1234, you lock 1 byte at offset 1234 (with VLockFile: fcntl on unix,
|
|
LockFileEx on windows as of the time of this writing). To read-lock the
|
|
volume, acquire a read lock; to write-lock the volume, acquire a write
|
|
lock.
|
|
|
|
Due to the potentially very large number of volumes attached by the
|
|
fileserver at once, the fileserver does not keep volumes locked the
|
|
entire time they are attached (which would make volume locking
|
|
potentially very slow). Rather, it locks the volume before attaching,
|
|
and unlocks it when the volume has been attached. However, all other
|
|
programs are expected to acquire a volume lock for the entire duration
|
|
they interact with the volume. Whether a read or write lock is obtained
|
|
is determined by the attachment mode, and whether or not the volume in
|
|
question is an RW volume (see VVolLockType()).
|
|
|
|
These locks are all acquired non-blocking, so we can just fail if we
|
|
fail to acquire a lock. That is, an errant process holding a file-level
|
|
lock cannot cause any process to just hang, waiting for a lock.
|
|
|
|
-- re-reading volume headers
|
|
|
|
Since we cannot know whether a volume is writable or not until the
|
|
volume header is read, and we cannot atomically upgrade file-level
|
|
locks, part of attachment can now occur twice (see attach2 and
|
|
attach_volume_header). What occurs is we read the vol header, assuming
|
|
the volume is readonly (acquiring a read or write lock as necessary).
|
|
If, after reading the vol header, we discover that the volume is
|
|
writable and that means we need to acquire a write lock, we read the vol
|
|
header again while acquiring a write lock on the header.
|
|
|
|
-- verifying checkouts
|
|
|
|
Since the fileserver does not hold volume locks for the entire time a
|
|
volume is attached, there could have been a potential race between the
|
|
fileserver and other programs. Consider when a non-fileserver program
|
|
checks out a volume from the fileserver via FSSYNC, then locks the
|
|
volume. Before the program locked the volume, the fileserver could have
|
|
restarted and attached the volume. Since the fileserver releases the
|
|
volume lock after attachment, the fileserver and the other program could
|
|
both think they have control over the volume, which is a problem.
|
|
|
|
To prevent this non-fileserver programs are expected to verify that
|
|
their volume is checked out after locking it (FSYNC_VerifyCheckout).
|
|
What this does is ask the fileserver for the current volume operation on
|
|
the specific volume, and verifies that it matches how the program
|
|
checked out the volume.
|
|
|
|
For example, programType X checks out volume V from the fileserver, and
|
|
then locks it. We then ask the fileserver for the current volume
|
|
operation on volume V. If the programType on the vol operation does not
|
|
match (or the PID, or the checkout mode, or other things), we know the
|
|
fileserver must have restarted or something similar, and we do not have
|
|
the volume checked out like we thought we did.
|
|
|
|
If the program determines that the fileserver may have restarted, it
|
|
then must retry checking out and locking the volume (or return an
|
|
error).
|