home *** CD-ROM | disk | FTP | other *** search
/ ftp.uv.es / 2014.11.ftp.uv.es.tar / ftp.uv.es / pub / unix / pine4.10.tar.gz / pine4.10.tar / pine4.10 / imap / docs / locking.txt < prev    next >
Text File  |  1995-11-28  |  19KB  |  401 lines

  1.      UNIX Advisory File Locking Implications on c-client
  2.             Mark Crispin, 28 November 1995
  3.  
  4.  
  5.     THIS DOCUMENT HAS BEEN UPDATED TO REFLECT THE CODE IN THE
  6.     IMAP-4 TOOLKIT AS OF NOVEMBER 28, 1995.  SOME STATEMENTS
  7.     IN THIS DOCUMENT DO NOT APPLY TO EARLIER VERSIONS OF THE
  8.     IMAP TOOLKIT.
  9.  
  10. INTRODUCTION
  11.  
  12.      Advisory locking is a mechanism by which cooperating processes
  13. can signal to each other their usage of a resource and whether or not
  14. that usage is critical.  It is not a mechanism to protect against
  15. processes which do not cooperate in the locking.
  16.  
  17.      The most basic form of locking involves a counter.  This counter
  18. is -1 when the resource is available.  If a process wants the lock, it
  19. executes an atomic increment-and-test-if-zero.  If the value is zero,
  20. the process has the lock and can execute the critical code that needs
  21. exclusive usage of a resource.  When it is finished, it sets the lock
  22. back to -1.  In C terms:
  23.  
  24.   while (++lock)        /* try to get lock */
  25.     invoke_other_threads ();    /* failed, try again */
  26.    .
  27.    .    /* critical code  here */
  28.    .
  29.   lock = -1;            /* release lock */
  30.  
  31.      This particular form of locking appears most commonly in
  32. multi-threaded applications such as operating system kernels.  It
  33. makes several presumptions:
  34.  (1) it is alright to keep testing the lock (no overflow)
  35.  (2) the critical resource is single-access only
  36.  (3) there is shared writeable memory between the two threads
  37.  (4) the threads can be trusted to release the lock when finished
  38.  
  39.      In applications programming on multi-user systems, most commonly
  40. the other threads are in an entirely different process, which may even
  41. be logged in as a different user.  Few operating systems offer shared
  42. writeable memory between such processes.
  43.  
  44.      A means of communicating this is by use of a file with a mutually
  45. agreed upon name.  A binary semaphore can be passed by means of the
  46. existance or non-existance of that file, provided that there is an
  47. atomic means to create a file if and only if that file does not exist.
  48. In C terms:
  49.  
  50.                 /* try to get lock */
  51.   while ((fd = open ("lockfile",O_WRONLY|O_CREAT|O_EXCL,0666)) < 0)
  52.     sleep (1);            /* failed, try again */
  53.   close (fd);            /* got the lock */
  54.    .
  55.    .    /* critical code  here */
  56.    .
  57.   unlink ("lockfile");         /* release lock */
  58.  
  59.      This form of locking makes fewer presumptions, but it still is
  60. guilty of presumptions (2) and (4) above.  Presumption (2) limits the
  61. ability to have processes sharing a resource in a non-conflicting
  62. fashion (e.g. reading from a file).  Presumption (4) leads to
  63. deadlocks should the process crash while it has a resource locked.
  64.  
  65.      Most modern operating systems provide a resource locking system
  66. call that has none of these presumptions.  In particular, a mechanism
  67. is provided for identifying shared locks as opposed to exclusive
  68. locks.  A shared lock permits other processes to obtain a shared lock,
  69. but denies exclusive locks.  In other words:
  70.  
  71.     current state        want shared    want exclusive
  72.     -------------        -----------    --------------
  73.      unlocked         YES         YES
  74.      locked shared         YES         NO
  75.      locked exclusive     NO         NO
  76.  
  77.      Furthermore, the operating system automatically relinquishes all
  78. locks held by that process when it terminates.
  79.  
  80.      A useful operation is the ability to upgrade a shared lock to
  81. exclusive (provided there are no other shared users of the lock) and
  82. to downgrade an exclusive lock to shared.  It is important that at no
  83. time is the lock ever removed; a process upgrading to exclusive must
  84. not relenquish its shared lock.
  85.  
  86.      Most commonly, the resources being locked are files.  Shared
  87. locks are particularly important with files; multiple simultaneous
  88. processes can read from a file, but only one can safely write at a
  89. time.  Some writes may be safer than others; an append to the end of
  90. the file is safer than changing existing file data.  In turn, changing
  91. a file record in place is safer than rewriting the file with an
  92. entirely different structure.
  93.  
  94.  
  95. FILE LOCKING ON UNIX
  96.  
  97.      In the oldest versions of UNIX, the use of a semaphore lockfile
  98. was the only available form of locking.  Advisory locking system calls
  99. were not added to UNIX until after the BSD vs. System V split.  Both
  100. of these system calls deal with file resources only.
  101.  
  102.      Most systems only have one or the other form of locking.  AIX
  103. emulates the BSD form of locking as a jacket into the System V form.
  104. Ultrix and OSF/1 implement both forms.
  105.  
  106. BSD
  107.  
  108.      BSD added the flock() system call.  It offers capabilities to
  109. acquire shared lock, acquire exclusive lock, and unlock.  Optionally,
  110. the process can request an immediate error return instead of blocking
  111. when the lock is unavailable.
  112.  
  113.  
  114. FLOCK() BUGS
  115.  
  116.      flock() advertises that it permits upgrading of shared locks to
  117. exclusive and downgrading of exclusive locks to shared, but it does so
  118. by releasing the former lock and then trying to acquire the new lock.
  119. This creates a window of vulnerability in which another process can
  120. grab the exclusive lock.  Therefore, this capability is not useful,
  121. although many programmers have been deluded by incautious reading of
  122. the flock() man page to believe otherwise.  This problem can be
  123. programmed around, once the programmer is aware of it.
  124.  
  125.      flock() always returns as if it succeeded on NFS files, when in
  126. fact it is a no-op.  There is no way around this.
  127.  
  128.      Leaving aside these two problems, flock() works remarkably well,
  129. and has shown itself to be robust and trustworthy.
  130.  
  131. SYSTEM V/POSIX
  132.  
  133.      System V added new functions to the fnctl() system call, and a
  134. simple interface through the lockf() subroutine.  This was
  135. subsequently included in POSIX.  Both offer the facility to apply the
  136. lock to a particular region of the file instead of to the entire file.
  137. lockf() only supports exclusive locks, and calls fcntl() internally;
  138. hence it won't be discussed further.
  139.  
  140.      Functionally, fcntl() locking is a superset of flock(); it is
  141. possible to implement a flock() emulator using fcntl(), with one minor
  142. exception: it is not possible to acquire an exclusive lock if the file
  143. is not open for write.
  144.  
  145.      The fcntl() locking functions are: query lock station of a file
  146. region, lock/unlock a region, and lock/unlock a region and block until
  147. have the lock.  The locks may be shared or exclusive.  By means of the
  148. statd and lockd daemons, fcntl() locking is available on NFS files.
  149.  
  150.      When statd is started at system boot, it reads its /etc/state
  151. file (which contains the number of times it has been invoked) and
  152. /etc/sm directory (which contains a list of all remote sites which are
  153. client or server locking with this site), and notifies the statd on
  154. each of these systems that it has been restarted.  Each statd then
  155. notifies the local lockd of the restart of that system.
  156.  
  157.      lockd receives fcntl() requests for NFS files.  It communicates
  158. with the lockd at the server and requests it to apply the lock, and
  159. with the statd to request it for notification when the server goes
  160. down.  It blocks until all these requests are completed.
  161.  
  162.      There is quite a mythos about fcntl() locking.
  163.  
  164.      One religion holds that fcntl() locking is the best thing since
  165. sliced bread, and that programs which use flock() should be converted
  166. to fcntl() so that NFS locking will work.  However, as noted above,
  167. very few systems support both calls, so such an exercise is pointless
  168. except on Ultrix and OSF/1.
  169.  
  170.      Another religion, which I adhere to, has the opposite viewpoint.
  171.  
  172.  
  173. FCNTL() BUGS
  174.  
  175.      For all of the hairy code to do individual section locking of a
  176. file, it's clear that the designers of fcntl() locking never
  177. considered some very basic locking operations.  It's as if all they
  178. knew about locking they got out of some CS textbook with not
  179. investigation of real-world needs.
  180.  
  181.      It is not possible to acquire an exclusive lock unless the file
  182. is open for write.  You could have append with shared read, and thus
  183. you could have a case in which a read-only access may need to go
  184. exclusive.  This problem can be programmed around once the programmer
  185. is aware of it.
  186.  
  187.      If the file is opened on another file designator in the same
  188. process, the file is unlocked even if no attempt is made to do any
  189. form of locking on the second designator.  This is a very bad bug.  It
  190. means that an application must keep track of all the files that it has
  191. opened and locked.
  192.  
  193.      If there is no statd/lockd on the NFS server, fcntl() will hang
  194. forever waiting for them to appear.  This is a bad bug.  It means that
  195. any attempt to lock on a server that doesn't run these daemons will
  196. hang.  There is no way for an application to request flock() style
  197. ``try to lock, but no-op if the mechanism ain't there''.
  198.  
  199.      There is a rumor to the effect that fcntl() will hang forever on
  200. local files too if there is no local statd/lockd.  These daemons are
  201. running on mailer.u, although they appear not to have much CPU time.
  202. A useful experiment would be to kill them and see if imapd is affected
  203. in any way, but I decline to do so without an OK from UCS!  ;-) If
  204. killing statd/lockd can be done without breaking fcntl() on local
  205. files, this would become one of the primary means of dealing with this
  206. problem.
  207.  
  208.      The statd and lockd daemons have quite a reputation for extreme
  209. fragility.  There have been numerous reports about the locking
  210. mechanism being wedged on a systemwide or even clusterwide basis,
  211. requiring a reboot to clear.  It is rumored that this wedge, once it
  212. happens, also blocks local locking.  Presumably killing and restarting
  213. statd would suffice to clear the wedge, but I haven't verified this.
  214.  
  215.      There appears to be a limit to how many locks may be in use at a
  216. time on the system, although the documentation only mentions it in
  217. passing.  On some of their systems, UCS has increased lockd's ``size
  218. of the socket buffer'', whatever that means.
  219.  
  220. C-CLIENT USAGE
  221.  
  222.      c-client uses flock().  On System V systems, flock() is simulated
  223. by an emulator that calls fcntl().  This emulator is provided by some
  224. systems (e.g. AIX), or uses c-client's flock.c module.
  225.  
  226.  
  227. BEZERK AND MMDF
  228.  
  229.      Locking in the traditional UNIX formats was largely dictated by
  230. the status quo in other applications; however, additional protection
  231. is added against inadvertantly running multiple instances of a
  232. c-client application on the same mail file.
  233.  
  234.      (1) c-client attempts to create a .lock file (mail file name with
  235. ``.lock'' appended) whenever it reads from, or writes to, the mail
  236. file.  This is an exclusive lock, and is held only for short periods
  237. of time while c-client is actually doing the I/O.  There is a 5-minute
  238. timeout for this lock, after which it is broken on the presumption
  239. that it is a stale lock.  If it can not create the .lock file due to
  240. an EACCES (protection failure) error, it once silently proceeded
  241. without this lock; this was for systems which protect /usr/spool/mail
  242. from unprivileged processes creating files.  Today, c-client reports
  243. an error unless it is built otherwise.  The purpose of this lock is to
  244. prevent against unfavorable interactions with mail delivery.
  245.  
  246.      (2) c-client applies a shared flock() to the mail file whenever
  247. it reads from the mail file, and an exclusive flock() whenever it
  248. writes to the mail file.  This lock is freed as soon as it finishes
  249. reading.  The purpose of this lock is to prevent against unfavorable
  250. interactions with mail delivery.
  251.  
  252.      (3) c-client applies an exclusive flock() to a file on /tmp
  253. (whose name represents the device and inode number of the file) when
  254. it opens the mail file.  This lock is maintained throughout the
  255. session, although c-client has a feature (called ``kiss of death'')
  256. which permits c-client to forcibly and irreversibly seize the lock
  257. from a cooperating c-client application that surrenders the lock on
  258. demand.  The purpose of this lock is to prevent against unfavorable
  259. interactions with other instances of c-client (rewriting the mail
  260. file).
  261.  
  262.      Mail delivery daemons use lock (1), (2), or both.  Lock (1) works
  263. over NFS; lock (2) is the only one that works on sites that protect
  264. /usr/spool/mail against unprivileged file creation.  Prudent mail
  265. delivery daemons use both forms of locking, and of course so does
  266. c-client.
  267.  
  268.      If only lock (2) is used, then multiple processes can read from
  269. the mail file simultaneously, although in real life this doesn't
  270. really change things.  The normal state of locks (1) and (2) is
  271. unlocked except for very brief periods.
  272.  
  273.  
  274. TENEX AND MTX
  275.  
  276.      The design of the locking mechanism of these formats was
  277. motivated by a design to enable multiple simultaneous read/write
  278. access.  It is almost the reverse of how locking works with
  279. bezerk/mmdf.
  280.  
  281.      (1) c-client applies a shared flock() to the mail file when it
  282. opens the mail file.  It upgrades this lock to exclusive whenever it
  283. tries to expunge the mail file.  Because of the flock() bug that
  284. upgrading a lock actually releases it, it will not do so until it has
  285. acquired an exclusive lock (2) first.  The purpose of this lock is to
  286. prevent against expunge taking place while some other c-client has the
  287. mail file open (and thus knows where all the messages are).
  288.  
  289.      (2) c-client applies a shared flock() to a file on /tmp (whose
  290. name represents the device and inode number of the file) when it
  291. parses the mail file.  It applies an exclusive flock() to this file
  292. when it appends new mail to the mail file, as well as before it
  293. attempts to upgrade lock (1) to exclusive.  The purpose of this lock
  294. is to prevent against data being appended while some other c-client is
  295. parsing mail in the file (to prevent reading of incomplete messages).
  296. It also protects against the lock-releasing timing race on lock (1).
  297.  
  298. OBSERVATIONS
  299.  
  300.      In a perfect world, locking works.  You are protected against
  301. unfavorable interactions with the mailer and against your own mistake
  302. by running more than one instance of your mail reader.  In tenex/mtx
  303. formats, you have the additional benefit that multiple simultaneous
  304. read/write access works, with the sole restriction being that you
  305. can't expunge if there are any sharers of the mail file.
  306.  
  307.      If the mail file is NFS-mounted, then flock() locking is a silent
  308. no-op.  This is the way BSD implements flock(), and c-client's
  309. emulation of flock() through fcntl() tests for NFS files and
  310. duplicates this functionality.  There is no locking protection for
  311. tenex/mtx mail files at all, and only protection against the mailer
  312. for bezerk/mmdf mail files.  This has been the accepted state of
  313. affairs on UNIX for many sad years.
  314.  
  315.      If you can not create .lock files, it should not affect locking,
  316. since the flock() locks suffice for all protection.  This is, however,
  317. not true if the mailer does not check for flock() locking, or if the
  318. the mail file is NFS-mounted.
  319.  
  320.      What this means is that there is *no* locking protection at all
  321. in the case of a client using an NFS-mounted /usr/spool/mail that does
  322. not permit file creation by unprivileged programs.  It is impossible,
  323. under these circumstances, for an unprivileged program to do anything
  324. about it.  Worse, if EACCES errors on .lock file creation are no-op'ed
  325. , the user won't even know about it.  This is arguably a site
  326. configuration error.
  327.  
  328.      The problem with not being able to create .lock files exists on
  329. System V as well, but the failure modes for flock() -- which is
  330. implemented via fcntl() -- are different.
  331.  
  332.      On System V, if the mail file is NFS-mounted and either the
  333. client or the server lacks a functioning statd/lockd pair, then the
  334. lock attempt would have hung forever if it weren't for the fact that
  335. c-client tests for NFS and no-ops the flock() emulator in this case.
  336. Systemwide or clusterwide failures of statd/lockd have been known to
  337. occur which cause all locks in all processes to hang (including
  338. local?).  Without the special NFS test made by c-client, there would
  339. be no way to request BSD-style no-op behavior, nor is there any way to
  340. determine that this is happening other than the system being hung.
  341.  
  342.      The additional locking introduced by c-client was shown to cause
  343. much more stress on the System V locking mechanism than has
  344. traditionally been placed upon it.  If it was stressed too far, all
  345. hell broke loose.  Fortunately, this is now past history.
  346.  
  347. TRADEOFFS
  348.  
  349.      c-client based applications have a reasonable chance of winning
  350. as long as you don't use NFS for remote access to mail files.  That's
  351. what IMAP is for, after all.  It is, however, very important to
  352. realize that you can *not* use the lock-upgrade feature by itself
  353. because it releases the lock as an interim step -- you need to have
  354. lock-upgrading guarded by another lock.
  355.  
  356.      If you have the misfortune of using System V, you are likely to
  357. run into problems sooner or later having to do with statd/lockd.  You
  358. basically end up with one of three unsatisfactory choices:
  359.     1) Grit your teeth and live with it.
  360.     2) Try to make it work:
  361.        a) avoid NFS access so as not to stress statd/lockd.
  362.        b) try to understand the code in statd/lockd and hack it
  363.           to be more robust.
  364.        c) hunt out the system limit of locks, if there is one,
  365.           and increase it.  Figure on at least two locks per
  366.           simultaneous imapd process and four locks per Pine
  367.           process.  Better yet, make the limit be 10 times the
  368.           maximum number of processes.
  369.        d) increase the socket buffer (-S switch to lockd) if
  370.           it is offered.  I don't know what this actually does,
  371.           but giving lockd more resources to do its work can't
  372.           hurt.  Maybe.
  373.     3) Decide that it can't possibly work, and turn off the 
  374.        fcntl() calls in your program.
  375.     4) If nuking statd/lockd can be done without breaking local
  376.        locking, then do so.  This would make SVR4 have the same
  377.        limitations as BSD locking, with a couple of additional
  378.        bugs.
  379.     5) Check for NFS, and don't do the fcntl() in the NFS case.
  380.        This is what c-client does.
  381.  
  382.      Note that if you are going to use NFS to access files on a server
  383. which does not have statd/lockd running, your only choice is (3), (4),
  384. or (5).  Here again, IMAP can bail you out.
  385.  
  386.      These problems aren't unique to c-client applications; they have
  387. also been reported with Elm, Mediamail, and other email tools.
  388.  
  389.      Of the other two SVR4 locking bugs:
  390.  
  391.      Programmer awareness is necessary to deal with the bug that you
  392. can not get an exclusive lock unless the file is open for write.  I
  393. believe that c-client has fixed all of these cases.
  394.  
  395.      The problem about opening a second designator smashing any
  396. current locks on the file has not been addressed satisfactorily yet.
  397. This is not an easy problem to deal with, especially in c-client which
  398. really doesn't know what other files/streams may be open by Pine.
  399.  
  400.      Aren't you so happy that you bought an System V system?
  401.