Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 13 Dec 1998 00:11:19 +0000 (GMT)
From:      Terry Lambert <tlambert@primenet.com>
To:        jkh@zippy.cdrom.com (Jordan K. Hubbard)
Cc:        vmg@novator.com, hackers@FreeBSD.ORG
Subject:   Re: Is it possible?
Message-ID:  <199812130011.RAA12494@usr01.primenet.com>
In-Reply-To: <85152.913104877@zippy.cdrom.com> from "Jordan K. Hubbard" at Dec 8, 98 00:14:37 am

next in thread | previous in thread | raw e-mail | index | archive | help
> > I have run into the proverbial brick wall.  I am the administrator of
> > a fairly busy electronic commerce Web site, www.ftd.com.  Because of
> > the demand placed on a single server, I implemented a load balancing
> > solution that utilizes NFS in the back end.  The versions of FreeBSD
> 
> Hmmm.  Well, as you've already noted, NFS is not really sufficient to
> this task and never has been.  There has never been any locking with
> our NFS and, as evidence would tend to suggest, never a degree of
> interest on anyone's part sufficient to actually motivate them to
> implement the functionality.

This isn't true.

Actually, Jordan was going to do this as a project in a class he
was taking taught by Kirk McKusick...

> Even with working NFS locks, it's also probably an inferior solution
> to what many folks are doing and that's load balancing at the IP
> level.  Something like the Coyote Point Systems Equalizer package
> (which is also based on FreeBSD, BTW) which takes n boxes and switches
> the traffic for them from one FreeBSD box using load metrics and other
> heuristics to determine the best match for a request would be a fine
> solution, as would any of the several other similar products on the
> market.

This is potentially true.

> Unless you're up for doing an NFS lock implementation, that is.
> Terry's patches only address some purported bugs in the general NFS
> code, they don't actually implement the lock daemon and other
> functionality you'd need to have truly working NFS locks. Evidently,
> this isn't something which has actually interested Terry enough to do
> either. :-)


Actually, my patches addressed all of the kernel locking issues not
related to implementation of the NFS client RPC code, and not related
to the requisite rpc.lockd code.

I didn't do the rpc.lockd code because you were going to.  I didn't
do the NFS client RPC code because I didn't have working rpc.lockd
on which to base an implemetnation.

The patches were *not* gratuitous reorganization, as I believe I can
prove; they addressed architectural issues only in as much as it was
required to address them for (1) binary compatability with previous
fcntl(2) based non-proxy locking, (2) support of the concept of
proxy locking at all, and (3) dealing with the issue of a stacking
VFS consuming an NFS client VFS layer, and the necessity of splitting
lock assertions across one or more inferior VFS's, and the
corresponding need to be able to abort a lock coelesce on a first
VFS if the operation could not be completed on the second.


Here is my architecture document, which should describe the patches
I've done (basically, all the generic kernel work), and the small
amount of work necessary to be done in user space, and in the NFS
client code.  Hopefully, someone with commit priviledges will
approach these ideas, since I've personally approached them three
times without success in getting them committed.


PS: I'm pretty sure BSDI examined my code before engaging in their
own implementation, given the emails I exchanged with them over it.


					Terry Lambert
					terry@lambert.org
---
Any opinions in this posting are my own and not those of my present
or previous employers.
==========================================================================
NFS LOCKING


1.0.0.0	Introduction

	NFS locking is generally desirable.  BSDI has implemented
	NFS locking, purportedly using some of my FreeBSD patchese
	as a starting point, to achieve the first implementation
	of NFS locking not derived from Sun source code.  What's
	unfortunate about this is that they neglected to release
	the code as open source (so far).


2.0.0.0	Server side locking

	Server side locking is, by far, the easiest NFS locking
	problem to solve,

	Server side locking is support for allowing clients to
	asser locks against files on an NFS server.

2.1.0.0	Theory of operation

	Server side locking is implemented by allowing the client
	to make RPC requests which are proxied to the server file
	space via one or more processes (generally, two: rpc.lockd
	and rpc.statd).

	Operations are proxied into the local collision domain,
	and enforced both against and by local locks, depending
	on order of operation.

2.2.0.0	rpc.statd

	The purpose of rpc.statd is to provide host status data
	to machines that it is monitoring.  This is generally used
	to allow client machines to reassert locks (since the NFS
	protocol is nominally stateless) following a server
	restart.  This means we can generally ignore rpc.statd
	for the purposes of this discussion.

2.3.0.0	rpc.lockd

	The purpose of rpc.lockd is to provide responses for file
	and record locking requests to client machines.

	Because NFS is nominally stateless, but locks themselves
	are nominally stateful, there must be a container for the
	lock state.  In a UNIX system, containers for lock state
	are called "processes".  They provide an ownership context
	for the locks, such that the locks can be discarded when
	th NFS services are discontinued.  As such, the rpc.lockd
	is an essential part of the resource and state tracking
	mechanism for NFS locks.

	The current FreeBSD rpc.lockd unconditionally grants lock
	requests; this is sufficient for Solaris interoperability,
	since Solaris will complain bitterly if there is not a lockd
	for a Solaris client to talk to, but is of rather limited
	utility otherwise, since locks are not enforced, even in
	the NFS collision domain, let alone between that domain
	and other processes on the FreeBSD machine.

	Note that it is possible to enforce the NFS locks within
	the NFS collision domain solely in the rpc.lockd itself,
	but this is generally not a sufficient answer, both because
	of architectural issues having to do with with the current
	rpc.lockd impelemtnation's handling of blocked requests
	(it has none) and the 

2.3.1.0	Interface problems in FreeBSD

	FreeBSD has a number of interface problems that prevent
	implementation of a functional rpc.lockd that enforces
	locks within both collision domains.

2.3.1.1	FreebSD problem #1: Conversion of NFS handles to FD's

	Historically, NFS locks have been asserted by converting
	an NFS file handle into an open file descriptor, and
	then asserting the proxy lock against the descriptor.

	SOLOUTION

		FreeBSD must implement an F_CNVT interface,
		to allow the rpc.lockd to convert an NFS
		handle into an open file descriptor.

	This is the first step in asserting a lock: get a file
	descriptor for use as a handle to the local locking
	mechanisms to perform operations on behalf of client
	machines.

2.3.1.2	FreeBSD problem #2: POSIX lock-release-on-close semantics

	The second problem FreeBSD faces is that a lock release
	by a process is implicit in POSIX locking semantics.

	This will not work in FreeBSD, since the same process
	proxies locks for multiple remote processes, and the
	semantic enforcement needs to occur on a per remote
	process basis, not on a per rpc.lockd basis.

	SOLOUTION

		FreeBSD must implement the fcntl option
		F_NONPOSIX for flagging descriptors on which
		POSIX unlock semantics must not be enforced.

	This resovles the proxy dissoloution problem, whereby a
	lock release by one remote client's process will not
	destroy the locks held by all other remote client's
	processes, as would happen if POSIX semantics were
	enforced on that descriptor.

	It also resolves the case where multiple locks are being
	proxied using one descriptor ("descriptor caching").  The
	rpc.lockd engages in descriptor caching by creating a hash
	based on the device/inode pair for each fd that results
	from a converted NFS file handle.

	The purpose of this is twofold: First, it allows a single
	descriptor to be resource counted for multiple clients
	such that descriptors are conserved.  Second, since the
	file handle presented by one client may not match the file
	handle presented by another, either because of intentional
	NFS server drift to prevent session hijacking, or because
	of local FS semantics, such as loopback mounts, union
	mounts, etc., it provides a common rendesvous point for
	the rpc.lockd.

2.3.1.3	FreeBSD problem #3: lack of support for proxy operations

	The FreeBSD fcntl(2) interface lacks the ability to note
	the use of a descriptor as proxy, as well as the identity
	of the proxied host id and process id.

	In general, what this means is that there is no support
	for proxying locks into the kernel.

	SunOS 4.1.3 solved this problem once; since that is the
	reference implemetnation for NFS locking, even today,
	inside Sun Microsystems, there is no need to reinvent
	the wheel (if someone feels the needs, at least this
	time, make it round).

	SOLOUTION

		FreeBSD must implement F_RGETLK, F_RSETLK, and
		F_RSETLKW.  In addition, the flock structure
		must be extended, as follows:

		/* old flock structure -- required for binary compatability*/
		struct oflock {
		    off_t   l_start;	/* starting offset */
		    off_t   l_len;	/* len = 0 means until end of file */
		    pid_t   l_pid;	/* lock owner */
		    short   l_type;	/* lock type: read/write, etc. */
		    short   l_whence;	/* type of l_start */
		};

		/* new flock structure -- required for NFS/SAMBA*/
		struct flock {
		    off_t   l_start;	/* starting offset */
		    off_t   l_len;	/* len = 0 means until end of file */
		    pid_t   l_pid;	/* lock owner */
		    short   l_type;	/* lock type: read/write, etc. */
		    short   l_whence;	/* type of l_start */
		    short   l_version;	/* avoid future compat. problems*/
		    long    l_rsys;	/* remote system id*/
		    pid_t   l_rpid;	/* remote lock owner*/
		};

	The use of an overlay structure solves the pending binary
	compatability easily an elegantly: the l_version, l_rpid, and
	l_rsys fields are defaulted for the F_GETLK, F_SETLK, and
	F_SETLKW commands.  This means that they are copied in using
	the same size as they previously used, and binary compatability
	is maintained.

	For the F_RGETLK, F_RSETLK, and F_RSETLKW commands, since they
	did not previously exist, binary compatability is unnecessary,
	and they can copy in the non-default l_version, l_rpid, l_rsys
	identifiers.

	By fiat, the oflock l_version is 0, and the flock version is
	1.  Also by fiat, the value of l_rsys is -1 for local locks.
	In particular, l_rsys is the IPv4 address of the requester,
	and -1 is illegal, and therefore useful as a cookie for
	"localhost".

	This provides the framework whereby proxy operations can be
	supported by FreeBSD.

2.3.1.4	FreeBSD problem #4: No support for l_rsys and l_rpid.

	Having an interface is only part of the battle.  FreeBSD
	also fails to support l_rsys and l_rpid internally.

	These values must be used as uniquifiers; that is, the
	value of l_pid alone is not sufficient.  When l_rsys is not
	-1 (localhost), the values of l_rsys and l_rpid must also
	be considered in determining whether or not locks may be
	coelesced.

	SOLOUTION

		Add Support to the FreeBSD locking subsystem
		to allow for support of these values to use in
		preventing coelescence and in determining lock
		equality.

	This work is rather trivial, but important.

	As we shall see in section 3, "Client side locking", we will
	want to defer our modifications until we have a complete
	picture of the issues for *both* client and server requiriments.

2.3.1.5	FreeBSD problem #5: Not all local FS's support locking

	We can say that any local FS that we may wish to mount
	really wants to be NFS exportable.

	Without getting into the issues of the FreeBSD VFS mount
	code, mount handilng, and mappinf of mounted FS's into the
	user visible hierarchy, it is very easy to see that one
	requirement for supporting locking is that the underlying
	FS's must also support locking.

	SOLOUTION

		Make all underlying FS's support locking by
		taking it out of the FS, and placing it at a
		higher layer.  Specifically, hang the lock
		list off the generic vnode, not off the FS
		specific inode.

	This is an obvious simplification that reaps many benefits.
	However, a we will discover in section 3, "Client side
	locking", we wil want to defer our modifications until we
	have a complete picture of the issues for *both* client
	and server requiriments.  Specifically, for VFS stacking
	to function correctly where an inferior VFS happens to
	be the NFS client VFS, we must preserve the VOP_ADVLOCK
	interface as a veto-based mechanism, where local media
	FS's never veto the operation (deferring to the upper level
	code that manages the lock off the vnode), whereas the
	NFS client code may, in fact, veto the operation (as could
	any other VFS that proxies operations, e.g., an SMBFS).

2.3.2.0	Requirements for rpc.lockd

	Once the FreeBSD interface issues have been addressed, it
	is necessary to address the rpc.lockd itself.  These
	issues are primarily algorithmic in nature.

2.3.2.1	When any request is made

	When a client makes a request, the first thing that the
	rpc.lockd must do is check the client handle hash list
	to determine if the rpc.lockd already has a descriptor
	open on that file *for that handle*.

	If a descriptor is not open for the handle, the rpc.lockd
	must convert the NFS file handle into a file descriptor.

	The rpc.lockd then fstats the descriptor to obtain the
	dev_t and ino_t fields.  This uniquely identifies the file
	to the FreeBSD system in a way that, for security reasons,
	the handle alone can not.

	Note: If the FreeBSD system chose to avoid some of the
	anti-hijack precations it takes, this step could be avoided,
	and the handle itself used as a unique identifier.

	The POSIX lock-release-on-close semantics are disabled via
	an fcntl using th F_NONPOSIX command.

	Given the unique identifier, a hash is computed to determine
	if some other client somewhere has the file open.  If so,
	the structure referencing the already open FD's reference
	count is incremented, and the FD is closed.  The client
	handle hash is updated so that subsequent operations in the
	same handle do not

	So there are two hash tables involved: the client handle
	hash, and the open file hash.

	Use of these hashes guarantees the minimum descriptor
	footprint possible for the rpc.lockd.  Since this is the
	most scarce resource on the server, this is what we must
	optimize.

	We note at this point what we noted earlier: we must have
	at least one descriptor per file in which locks are being
	asserted, since we are the process container for the locks.

2.3.2.2	F_RGETLK

	This is a straight-forward request.  The request is not
	a blocking request, so it is made, and the result is
	returned.  The rpc.lockd fills out the l_rpid and l_rsys
	as necessary to make the request.

2.3.2.3	F_RSETLK

	This is likewise non-blocking, and therefore likewise
	relatively trivial.

2.3.2.4 F_RSETLKW

	This operation is the tough one.  Because the operation
	would block, we have an implementation decision.

	To reduce overhead, we first try F_RSETLK; if it succeeds,
	we return success.  This is by far the most common outcome,
	given most lock contention mechanisms in most well written
	FS client software (note: FS, not NFS: programs are clients
	of FS services, even for local FS's).

	If this returns EAGAIN, then we must decide how to perform
	the operation.

	We can either fork, and have the forked process close all
	its copies of the descriptors, except the one of interest,
	and then implement F_RSETLKW as a blocking operation, or
	we can implement F_RSETLKW as a queued operation.  Finally,
	we could set up a time, and use F_RSETLK exclisively, until
	it succeeeds.  This last is unacceptable, since it does not
	guarantee order of grant equals order of enqueueing, and
	thus may break program expectations on semantics, resulting
	in deadly embrace deadlocks between processes.

	Given that FreeBSD supports the concepts of sharing a
	descriptor table between processes (via vfork(2)), the
	fork option is by far the most attractive, with the
	caveat that we use the vfork to get a copy of the
	descriptor table shared so as to not double the fd
	footprint, even for a short period of time.

	We can likewise enqueue state, and process SIGCLD to ensure
	that the parent rpc.lockd knows about all pending and
	successful requests (necessary for proper operation of the
	rpc.statd daemon).

2.3.2.5	Back to the general

	Now we can go back to discussing the general implementaiton.

	The rpc.lockd must decrement the reference count when locks
	held by a given process are removed.  It can either do this
	by maintaining a shadow, or, preferentially, by, after a
	lock is released, performing an F_RGETLK.

	This is part of the resource tracking for opn descriptors in
	the rpc.lockd.  If the request indicates that there are no
	more locks held by that l_rsys/l_rpid pair, then the fd
	reference count is decremented, and the per handle hash is
	removed from the list.  If the reference count goes to zero,
	then the descriptor is closed.

	DISCUSSION

		It is useful to implement late-bingding closes.
		Specifically, it is useful to not actually delete
		the reference immediately.

	SOLOUTION

		The handle refernces, instead of being deleted, are
		thrown ont a clock list.  If the handles are
		rereferenced within a tunable time frame, then they
		are removed from the list and placed back into use;
		otherwise, after sufficient time has elapsed, they
		are inactivated as above.

	This resolves the case of a single client generating a lot
	of unnecessary rpc.lockd activity by issuing lock-unlock
	pairs that would cause the references to bounce up and
	down, requiring a lot of system calls.  It preserves the
	NFS handle hash for a time after the operation nominally
	completes, in the expectation of future operations by that
	client.

3.0.0.0	Client side locking

	Client side locking is much harder than server side locking.

	Client side locking allows clients to request locks from
	remote NFS servers on behalf of local processes running on
	the client machine.

3.1.0.0	Theory of operation

	Client side locking is implemented by the client NFS code in
	the kernel making RPC requests against the server, much in
	the same way that NFS clients operate when making FS
	operation requests against NFS servers.

	It is simultaneously more difficult because of the code
	being located in the kernel, and less difficult, since
	there is a process context (the reqiesting process) to act
	as a conatiner ofr the operation until it is completed by
	the server.

	Server side locking is implemented by allowing the client
	to make RPC requests which are proxied to the server file
	space via one or more processes (generally, two: rpc.lockd
	and rpc.statd).

	Operations are proxied into the local collision domain,
	and enforced both against and by local locks, depending
	on order of operation.

3.1.1.0	Interface problems in FreeBSD

	FreeBSD has a number of interface problems that prevent
	implementation of a functional NFS client locking.

3.1.1.1	FreeBSD problem #1: VFS stacking and coelescence

	Locks, when asserted, are coelesced by l_pid.  If they
	are asserted by a hosted OS API, e.g., an NFS, AppleTalk,
	or SAMBA server, they are coelesced by l_rsys and l_rpid,
	as well; we can ignore all by l_pid in the general case,
	since exporting an exported FS is foolish and dangerous.

	When locks are asserted, then, the locks are coelesced if
	the lock is successful.  Thus, If a process had a file

		[FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF]

	Protected by the locks:

		[111111111]            [2222222222]
		[FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF]

	And asserted a third lock:

			[333333333333333333]
		[111111111]            [2222222222]
		[FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF]

	That lock would be coelesced:

		[111111111111111111111111111111111]
		[FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF]

	For a local media FS, this is not a problem, since the
	operation occurs locally, and is serialized by virtue
	of that fact.  But for an NFS client, the lock behaviour
	is less serialized.

	Consider the case of a VFS stacking layer that stacks
	two filesystems, and makes the files within them appear
	to be two extents of a single file.  We can imagine that
	this would be useful for combined log files for a cluster
	of machines, and for other reasons (there are many other
	examples; this is merely the simplest).  So we have:


		[ffffffffffffffff][FFFFFFFFFFFFFFFFFFFFFFFFFFF]

	Lets perform the same locks:

		[111111111]            [2222222222]
		[ffffffffffffffff][FFFFFFFFFFFFFFFFFFFFFFFFFFF]

	So far, so good.  Now the third lock:

			[333333333333333333]
		[111111111]            [2222222222]
		[ffffffffffffffff][FFFFFFFFFFFFFFFFFFFFFFFFFFF]

	Colesce, phase one:

			          [33333333]
		[1111111111111111]     [2222222222]
		[ffffffffffffffff][FFFFFFFFFFFFFFFFFFFFFFFFFFF]

	Oops!  The second phase fails because Some other client has
	the lock:

				  [XX]

	Now we need to back out the operation on the first FS:

			[33333333]          
		[111111111]       
		[ffffffffffffffff]

	Leaving:

		[1111111]         
		[ffffffffffffffff]

	Ut-oh: looks like we're screwed.

	SOLOUTION

		Delayed coelescing.  The locks are asserted, but
		they are not committed (coelesced) until all the
		operations have been deemed successful.

	By dividing the phases of asserting vs. committing, we can
	delay the coelesceing until we know that all locks are
	successfully asserted.

	How do we do this?  Very simply, we convert the VOP_ADVLOCK
	to be a veto mechanism, instead of the mechainsm by which
	the lock code is acutally called, and we move the locking
	operations to upper level (common) code.  At the same time,
	we make the OS more robust, since there is only one place,
	instead of many, where the code is called.

	For stacking layers that stack on more than one VFS, and for
	proxy layers, such as NFS, SMB, or AppleTalk client layers,
	the operation is a veto, where the operation is proxied, and
	if the proxy fails, then the operation is vetoed.

	So in general, VOP_ADVLOCK becomes a "return(1);" for most
	of the VFS layers, with specific exceptions for particular
	layer types, which *may* veto the operation by the upper
	level code.

	If the operation is not vetoed by the upper level code, then
	the upper level code commits the operation, and the lock
	ranges are coelesced.

3.1.1.2	FreeBSD problem #2: What if the NFS layer is first?

	If the NFS layer is first, and the operation is subsequently
	vetoed, how is the NFS coelesce backed out?

	SOLOUTION

		The shadow graph.  The NFS client, for each
		given vnode (nfsnode), must seperately maintain
		the locks agains the node on a per process basis.

	What this means is that when a process asserts a lock on an
	NFS accessed file, the NFS client lockign code must maintain
	an uncoelesced lock graph.

	This is because the lock graph *will* be coelesced on the
	server.

	In order to back out the operation:

			[33333333]
		[111111111]       
		[ffffffffffffffff]

			|
			v
			          
		[1111111111111111]
		[ffffffffffffffff]

	The client must keep knowledge of the fact that these locks
	are seperate.

	This implies that locks that result in type demotions are
	not type demoted to the server (i.e., locks against the
	server are only asserted in promote-only mode so that if
	they are backed out, there will not have been a demotion,
	for example, from write to read, on the server).

	There is currently code in SAMBA which models this, since
	SAMBA's consumtiopn of the host FS is similar to an NFS
	clients consumption of an NFS server's FS.

3.2.0.0	The client NFS VFS layer's RPC calls

	So far no one has implemented this.  In general, it is more
	important to be a server than it is to be a client, at this
	time.

	The amount of effort to implement this, if one has the ISO
	documents, or, more obliquely and therefore more difficult,
	the rpc.lockd code in the FreeBSD source tree, is pretty
	small.  This would make a good one quarter project for a
	Batcholer of Science in Computer Science independent study
	credit.

3.3.0.0	Discussion

	In general, all of the issues for an NFS client in FreeBSD
	apply equally to the idea of an AppleTalk or SMB client in
	FreeBSD.  It is likely that FreeBSD will want to support
	the ability to operate as a desktop (and therefore client)
	OS, even if this is not the primary niche into which it is
	currently being driven by the developers.


4.0.0.0 End Of Document
==========================================================================

To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-hackers" in the body of the message



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199812130011.RAA12494>