* Concurrent GC on Cygwin.
This commit is contained in:
parent
cc51f9c539
commit
dbf6d7e783
3 changed files with 146 additions and 73 deletions
|
@ -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(
|
||||||
|
@ -191,7 +209,19 @@ static void readTempRoots(PathSet & tempRoots, FDs & fds)
|
||||||
Path path = (format("%1%/%2%/%3%") % nixStateDir % tempRootsDir % *i).str();
|
Path path = (format("%1%/%2%/%3%") % nixStateDir % tempRootsDir % *i).str();
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue