selfboot / AnnotatedShadowSocks

Annotated shadowsocks(python version)
Other
3 stars 1 forks source link

Use lockf for record locking of pid file #26

Open selfboot opened 7 years ago

selfboot commented 7 years ago

Record locking was added to System V Release 3 through the fcntl function. The lockf function was built on top of this, providing a simplified interface. These functions allowed callers to lock arbitrary byte ranges in a file, ranging from the entire file down to a single byte within the file.

fcntl Record Locking

#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* struct flock *flockptr */ );
      // Returns: depends on cmd if OK (see following), −1 on error

For record locking, cmd is F_GETLK, F_SETLK, or F_SETLKW.

The third argument (which we’ll call flockptr) is a pointer to an flock structure.

image

This structure describes

The basic rule is that any number of processes can have a shared read lock on a given byte, but only one process can have an exclusive write lock on a given byte. Furthermore, if there are one or more read locks on a byte, there can’t be any write locks on that byte; if there is an exclusive write lock on a byte, there can’t be any read locks on that byte.

The compatibility rule applies to lock requests made from different processes, not to multiple lock requests made by a single process. If a process has an existing lock on a range of a file, a subsequent attempt to place a lock on the same range by the same process will replace the existing lock with the new one. Thus, if a process has a write lock on bytes 16 – 32 of a file and then tries to place a read lock on bytes 16 – 32, the request will succeed, and the write lock will be replaced by a read lock.

Implied Inheritance and Release of Locks

Three rules govern the automatic inheritance and release of record locks.

  1. Locks are associated with a process and a file. This has two implications. The first is obvious: when a process terminates, all its locks are released. The second is far from obvious: whenever a descriptor is closed, any locks on the file referenced by that descriptor for that process are released.
  2. Locks are never inherited by the child across a fork. This means that if a process obtains a lock and then calls fork, the child is considered another process with regard to the lock that was obtained by the parent.
  3. Locks are inherited by a new program across an exec. Note, however, that if the close-on-exec flag is set for a file descriptor, all locks for the underlying file are released when the descriptor is closed as part of an exec. (Read #25)

python lockf function

From python docs:

fcntl.lockf(fd, operation[, length[, start[, whence]]])

This is essentially a wrapper around the fcntl() locking calls. fd is the file descriptor of the file to lock or unlock, and operation is one of the following values:

  • LOCK_UN – unlock
  • LOCK_SH – acquire a shared lock
  • LOCK_EX – acquire an exclusive lock

When operation is LOCK_SH or LOCK_EX, it can also be bitwise ORed with LOCK_NB to avoid blocking on lock acquisition. If LOCK_NB is used and the lock cannot be acquired, an IOError will be raised and the exception will have an errno attribute set to EACCES or EAGAIN (depending on the operating system; for portability, check for both values). On at least some systems, LOCK_EX can only be used if the file descriptor refers to a file opened for writing.

length is the number of bytes to lock, start is the byte offset at which the lock starts, relative to whence, and whence is as with io.IOBase.seek(), specifically:

  • 0 – relative to the start of the file (os.SEEK_SET)
  • 1 – relative to the current buffer position (os.SEEK_CUR)
  • 2 – relative to the end of the file (os.SEEK_END)

The default for start is 0, which means to start at the beginning of the file. The default for length is 0 which means to lock to the end of the file. The default for whence is also 0.

In function shadowsocks.daemon.write_pid_file, we can see the code as follows:

try:
    fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB, 0, 0, os.SEEK_SET)
except IOError:
    r = os.read(fd, 32)
    if r:
        logging.error('already started at pid %s' % common.to_str(r))
    else:
        logging.error('already started')
    os.close(fd)
    return -1

Before write the process id to the pid file, use fcntl.lockf to acquire an exclusive lock. If there is already a process running, lockf function can't acquire the lock and raise an IOError, then we log some info and exit the process. On the other hand, if the lock is acquired, then write the process id into pid file.

Ref
《APUE 3th》 14.3 Record Locking
Chapter 7. File and Record Locking