* Concurrent GC on Cygwin.

This commit is contained in:
Eelco Dolstra 2006-06-20 17:48:10 +00:00
parent cc51f9c539
commit dbf6d7e783
3 changed files with 146 additions and 73 deletions

View file

@ -11,6 +11,11 @@
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#ifdef __CYGWIN__
#include <windows.h>
#include <sys/cygwin.h>
#endif
static string gcLockName = "gc.lock"; static string gcLockName = "gc.lock";
static string tempRootsDir = "temproots"; static string tempRootsDir = "temproots";
@ -108,10 +113,6 @@ static AutoCloseFD fdTempRoots;
void addTempRoot(const Path & path) void addTempRoot(const Path & path)
{ {
#ifdef __CYGWIN__
return;
#endif
/* Create the temporary roots file for this process. */ /* Create the temporary roots file for this process. */
if (fdTempRoots == -1) { if (fdTempRoots == -1) {
@ -124,12 +125,24 @@ void addTempRoot(const Path & path)
AutoCloseFD fdGCLock = openGCLock(ltRead); AutoCloseFD fdGCLock = openGCLock(ltRead);
fdTempRoots = open(fnTempRoots.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0600); if (pathExists(fnTempRoots))
if (fdTempRoots == -1) /* It *must* be stale, since there can be no two
throw SysError(format("opening temporary roots file `%1%'") % fnTempRoots); processes with the same pid. */
deletePath(fnTempRoots);
fdTempRoots = openLockFile(fnTempRoots, true);
fdGCLock.close(); fdGCLock.close();
/* Note that on Cygwin a lot of the following complexity
is unnecessary, since we cannot delete open lock
files. If we have the lock file open, then it's valid;
if we can delete it, then it wasn't in use any more.
Also note that on Cygwin we cannot "upgrade" a lock
from a read lock to a write lock. */
#ifndef __CYGWIN__
debug(format("acquiring read lock on `%1%'") % fnTempRoots); debug(format("acquiring read lock on `%1%'") % fnTempRoots);
lockFile(fdTempRoots, ltRead, true); lockFile(fdTempRoots, ltRead, true);
@ -143,6 +156,10 @@ void addTempRoot(const Path & path)
/* The garbage collector deleted this file before we could /* The garbage collector deleted this file before we could
get a lock. (It won't delete the file after we get a get a lock. (It won't delete the file after we get a
lock.) Try again. */ lock.) Try again. */
#else
break;
#endif
} }
} }
@ -155,9 +172,14 @@ void addTempRoot(const Path & path)
string s = path + '\0'; string s = path + '\0';
writeFull(fdTempRoots, (const unsigned char *) s.c_str(), s.size()); writeFull(fdTempRoots, (const unsigned char *) s.c_str(), s.size());
#ifndef __CYGWIN__
/* Downgrade to a read lock. */ /* Downgrade to a read lock. */
debug(format("downgrading to read lock on `%1%'") % fnTempRoots); debug(format("downgrading to read lock on `%1%'") % fnTempRoots);
lockFile(fdTempRoots, ltRead, true); lockFile(fdTempRoots, ltRead, true);
#else
debug(format("releasing write lock on `%1%'") % fnTempRoots);
lockFile(fdTempRoots, ltNone, true);
#endif
} }
@ -176,10 +198,6 @@ typedef list<FDPtr> FDs;
static void readTempRoots(PathSet & tempRoots, FDs & fds) static void readTempRoots(PathSet & tempRoots, FDs & fds)
{ {
#ifdef __CYGWIN__
return;
#endif
/* Read the `temproots' directory for per-process temporary root /* Read the `temproots' directory for per-process temporary root
files. */ files. */
Strings tempRootFiles = readDirectory( Strings tempRootFiles = readDirectory(
@ -192,6 +210,18 @@ static void readTempRoots(PathSet & tempRoots, FDs & fds)
debug(format("reading temporary root file `%1%'") % path); debug(format("reading temporary root file `%1%'") % path);
#ifdef __CYGWIN__
/* On Cygwin we just try to delete the lock file. */
char win32Path[MAX_PATH];
cygwin_conv_to_full_win32_path(path.c_str(), win32Path);
if (DeleteFile(win32Path)) {
printMsg(lvlError, format("removed stale temporary roots file `%1%'")
% path);
continue;
} else
debug(format("delete of `%1%' failed: %2%") % path % GetLastError());
#endif
FDPtr fd(new AutoCloseFD(open(path.c_str(), O_RDWR, 0666))); FDPtr fd(new AutoCloseFD(open(path.c_str(), O_RDWR, 0666)));
if (*fd == -1) { if (*fd == -1) {
/* It's okay if the file has disappeared. */ /* It's okay if the file has disappeared. */
@ -199,6 +229,11 @@ static void readTempRoots(PathSet & tempRoots, FDs & fds)
throw SysError(format("opening temporary roots file `%1%'") % path); throw SysError(format("opening temporary roots file `%1%'") % path);
} }
/* This should work, but doesn't, for some reason. */
//FDPtr fd(new AutoCloseFD(openLockFile(path, false)));
//if (*fd == -1) continue;
#ifndef __CYGWIN__
/* Try to acquire a write lock without blocking. This can /* Try to acquire a write lock without blocking. This can
only succeed if the owning process has died. In that case only succeed if the owning process has died. In that case
we don't care about its temporary roots. */ we don't care about its temporary roots. */
@ -209,6 +244,7 @@ static void readTempRoots(PathSet & tempRoots, FDs & fds)
writeFull(*fd, (const unsigned char *) "d", 1); writeFull(*fd, (const unsigned char *) "d", 1);
continue; continue;
} }
#endif
/* Acquire a read lock. This will prevent the owning process /* Acquire a read lock. This will prevent the owning process
from upgrading to a write lock, therefore it will block in from upgrading to a write lock, therefore it will block in
@ -448,28 +484,24 @@ void collectGarbage(GCAction action, const PathSet & pathsToDelete,
debug(format("dead path `%1%'") % *i); debug(format("dead path `%1%'") % *i);
result.insert(*i); result.insert(*i);
AutoCloseFD fdLock;
if (action == gcDeleteDead || action == gcDeleteSpecific) { if (action == gcDeleteDead || action == gcDeleteSpecific) {
#ifndef __CYGWIN__
AutoCloseFD fdLock;
/* Only delete a lock file if we can acquire a write lock /* Only delete a lock file if we can acquire a write lock
on it. That means that it's either stale, or the on it. That means that it's either stale, or the
process that created it hasn't locked it yet. In the process that created it hasn't locked it yet. In the
latter case the other process will detect that we latter case the other process will detect that we
deleted the lock, and retry (see pathlocks.cc). */ deleted the lock, and retry (see pathlocks.cc). */
if (i->size() >= 5 && string(*i, i->size() - 5) == ".lock") { if (i->size() >= 5 && string(*i, i->size() - 5) == ".lock") {
fdLock = openLockFile(*i, false);
fdLock = open(i->c_str(), O_RDWR); if (fdLock != -1 && !lockFile(fdLock, ltWrite, false)) {
if (fdLock == -1) {
if (errno == ENOENT) continue;
throw SysError(format("opening lock file `%1%'") % *i);
}
if (!lockFile(fdLock, ltWrite, false)) {
debug(format("skipping active lock `%1%'") % *i); debug(format("skipping active lock `%1%'") % *i);
continue; continue;
} }
} }
#endif
printMsg(lvlInfo, format("deleting `%1%'") % *i); printMsg(lvlInfo, format("deleting `%1%'") % *i);
@ -478,9 +510,11 @@ void collectGarbage(GCAction action, const PathSet & pathsToDelete,
deleteFromStore(*i, freed); deleteFromStore(*i, freed);
bytesFreed += freed; bytesFreed += freed;
#ifndef __CYGWIN__
if (fdLock != -1) if (fdLock != -1)
/* Write token to stale (deleted) lock file. */ /* Write token to stale (deleted) lock file. */
writeFull(fdLock, (const unsigned char *) "d", 1); writeFull(fdLock, (const unsigned char *) "d", 1);
#endif
} }
} }
} }

View file

@ -12,6 +12,79 @@
#endif #endif
int openLockFile(const Path & path, bool create)
{
AutoCloseFD fd;
#ifdef __CYGWIN__
/* On Cygwin we have to open the lock file without "DELETE"
sharing mode; otherwise Windows will allow open lock files to
be deleted (which is almost but not quite what Unix does). */
char win32Path[MAX_PATH + 1];
cygwin_conv_to_full_win32_path(path.c_str(), win32Path);
SECURITY_ATTRIBUTES sa; /* required, otherwise inexplicably bad shit happens */
sa.nLength = sizeof sa;
sa.lpSecurityDescriptor = 0;
sa.bInheritHandle = TRUE;
HANDLE h = CreateFile(win32Path, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, &sa,
(create ? OPEN_ALWAYS : OPEN_EXISTING),
FILE_ATTRIBUTE_NORMAL, 0);
if (h == INVALID_HANDLE_VALUE) {
if (create || GetLastError() != ERROR_FILE_NOT_FOUND)
throw Error(format("opening lock file `%1%'") % path);
fd = -1;
}
else
fd = cygwin_attach_handle_to_fd((char *) path.c_str(), -1, h, 1, O_RDWR);
#else
fd = open(path.c_str(), O_RDWR | (create ? O_CREAT : 0), 0666);
if (fd == -1 && (create || errno != ENOENT))
throw SysError(format("opening lock file `%1%'") % path);
#endif
return fd.borrow();
}
void deleteLockFilePreClose(const Path & path, int fd)
{
#ifndef __CYGWIN__
/* Get rid of the lock file. Have to be careful not to introduce
races. */
/* On Unix, write a (meaningless) token to the file to indicate to
other processes waiting on this lock that the lock is stale
(deleted). */
unlink(path.c_str());
writeFull(fd, (const unsigned char *) "d", 1);
/* Note that the result of unlink() is ignored; removing the lock
file is an optimisation, not a necessity. */
#endif
}
void deleteLockFilePostClose(const Path & path)
{
#ifdef __CYGWIN__
/* On Windows, just try to delete the lock file. This will fail
if anybody still has the file open. We cannot use unlink()
here, because Cygwin emulates Unix semantics of allowing an
open file to be deleted (but fakes it - the file isn't actually
deleted until later, so a file with the same name cannot be
created in the meantime). */
char win32Path[MAX_PATH + 1];
cygwin_conv_to_full_win32_path(path.c_str(), win32Path);
if (DeleteFile(win32Path))
debug(format("delete of `%1%' succeeded") % path.c_str());
else
/* Not an error: probably means that the lock is still opened
by someone else. */
debug(format("delete of `%1%' failed: %2%") % path.c_str() % GetLastError());
#endif
}
bool lockFile(int fd, LockType lockType, bool wait) bool lockFile(int fd, LockType lockType, bool wait)
{ {
struct flock lock; struct flock lock;
@ -94,26 +167,7 @@ void PathLocks::lockPaths(const PathSet & _paths, const string & waitMsg)
while (1) { while (1) {
/* Open/create the lock file. */ /* Open/create the lock file. */
#ifdef __CYGWIN__ fd = openLockFile(lockPath, true);
char win32Path[MAX_PATH];
cygwin_conv_to_full_win32_path(lockPath.c_str(), win32Path);
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof sa;
sa.lpSecurityDescriptor = 0;
sa.bInheritHandle = TRUE;
HANDLE h = CreateFile(win32Path, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL, 0);
if (h == INVALID_HANDLE_VALUE)
throw Error(format("opening lock file `%1%'") % lockPath);
fd = cygwin_attach_handle_to_fd((char *) lockPath.c_str(), -1, h, 1, O_RDWR);
#else
fd = open(lockPath.c_str(), O_WRONLY | O_CREAT, 0666);
if (fd == -1)
throw SysError(format("opening lock file `%1%'") % lockPath);
#endif
/* Acquire an exclusive lock. */ /* Acquire an exclusive lock. */
if (!lockFile(fd, ltWrite, false)) { if (!lockFile(fd, ltWrite, false)) {
@ -148,40 +202,15 @@ void PathLocks::lockPaths(const PathSet & _paths, const string & waitMsg)
PathLocks::~PathLocks() PathLocks::~PathLocks()
{ {
for (list<FDPair>::iterator i = fds.begin(); i != fds.end(); i++) { for (list<FDPair>::iterator i = fds.begin(); i != fds.end(); i++) {
#ifndef __CYGWIN__ if (deletePaths) deleteLockFilePreClose(i->second, i->first);
if (deletePaths) {
/* Get rid of the lock file. Have to be careful not to
introduce races. */
/* On Unix, write a (meaningless) token to the file to
indicate to other processes waiting on this lock that
the lock is stale (deleted). */
unlink(i->second.c_str());
writeFull(i->first, (const unsigned char *) "d", 1);
/* Note that the result of unlink() is ignored; removing
the lock file is an optimisation, not a necessity. */
}
#endif
lockedPaths.erase(i->second); lockedPaths.erase(i->second);
if (close(i->first) == -1) if (close(i->first) == -1)
printMsg(lvlError, printMsg(lvlError,
format("error (ignored): cannot close lock file on `%1%'") % i->second); format("error (ignored): cannot close lock file on `%1%'") % i->second);
#ifdef __CYGWIN__
if (deletePaths) { if (deletePaths) deleteLockFilePostClose(i->second);
/* On Windows, just try to delete the lock file. This
will fail if anybody still has the file open. We
cannot use unlink() here, because Cygwin emulates Unix
semantics of allowing an open file to be deleted (but
fakes it - the file isn't actually deleted until later,
so a file with the same name cannot be created in the
meantime). */
char win32Path[MAX_PATH];
cygwin_conv_to_full_win32_path(i->second.c_str(), win32Path);
if (DeleteFile(win32Path))
debug(format("delete of `%1%' succeeded") % i->second.c_str());
else
debug(format("delete of `%1%' failed: %2%") % i->second.c_str() % GetLastError());
}
#endif
debug(format("lock released on `%1%'") % i->second); debug(format("lock released on `%1%'") % i->second);
} }
} }

View file

@ -4,6 +4,16 @@
#include "util.hh" #include "util.hh"
/* Open (possibly create) a lock file and return the file descriptor.
-1 is returned if create is false and the lock could not be opened
because it doesn't exist. Any other error throws an exception. */
int openLockFile(const Path & path, bool create);
/* Delete an open lock file. Both must be called to be fully portable
between Unix and Windows. */
void deleteLockFilePreClose(const Path & path, int fd);
void deleteLockFilePostClose(const Path & path);
typedef enum LockType { ltRead, ltWrite, ltNone }; typedef enum LockType { ltRead, ltWrite, ltNone };
bool lockFile(int fd, LockType lockType, bool wait); bool lockFile(int fd, LockType lockType, bool wait);