mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-09-22 05:35:48 +00:00
Support using atomic-append logging for multiple file outputs
* The primary user is the main logfile itself, but it's useful for debugging purposes to be able to log to a file and still open it externally in a viewer without having the logging code take exclusive access to it.
This commit is contained in:
@@ -293,7 +293,7 @@ uint64_t Log2Floor(uint64_t value)
|
||||
#endif
|
||||
|
||||
static std::string logfile;
|
||||
static bool logfileOpened = false;
|
||||
static FileIO::LogFileHandle *logfileHandle = NULL;
|
||||
|
||||
const char *rdclog_getfilename()
|
||||
{
|
||||
@@ -308,21 +308,22 @@ void rdclog_filename(const char *filename)
|
||||
if(filename && filename[0])
|
||||
logfile = filename;
|
||||
|
||||
FileIO::logfile_close(NULL);
|
||||
FileIO::logfile_close(logfileHandle, NULL);
|
||||
|
||||
logfileOpened = false;
|
||||
logfileHandle = NULL;
|
||||
|
||||
if(!logfile.empty())
|
||||
{
|
||||
logfileOpened = FileIO::logfile_open(logfile.c_str());
|
||||
logfileHandle = FileIO::logfile_open(logfile.c_str());
|
||||
|
||||
if(logfileOpened && previous.c_str())
|
||||
if(logfileHandle && previous.c_str())
|
||||
{
|
||||
std::vector<unsigned char> previousContents;
|
||||
FileIO::slurp(previous.c_str(), previousContents);
|
||||
|
||||
if(!previousContents.empty())
|
||||
FileIO::logfile_append((const char *)&previousContents[0], previousContents.size());
|
||||
FileIO::logfile_append(logfileHandle, (const char *)&previousContents[0],
|
||||
previousContents.size());
|
||||
|
||||
FileIO::Delete(previous.c_str());
|
||||
}
|
||||
@@ -339,7 +340,7 @@ void rdclog_enableoutput()
|
||||
void rdclog_closelog(const char *filename)
|
||||
{
|
||||
log_output_enabled = false;
|
||||
FileIO::logfile_close(filename);
|
||||
FileIO::logfile_close(logfileHandle, filename);
|
||||
}
|
||||
|
||||
void rdclog_flush()
|
||||
@@ -366,10 +367,10 @@ void rdclogprint_int(LogType type, const char *fullMsg, const char *msg)
|
||||
OSUtility::WriteOutput(OSUtility::Output_StdErr, msg);
|
||||
#endif
|
||||
#if ENABLED(OUTPUT_LOG_TO_DISK)
|
||||
if(logfileOpened)
|
||||
if(logfileHandle)
|
||||
{
|
||||
// strlen used as byte length - str is UTF-8 so this is NOT number of characters
|
||||
FileIO::logfile_append(fullMsg, strlen(fullMsg));
|
||||
FileIO::logfile_append(logfileHandle, fullMsg, strlen(fullMsg));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -311,9 +311,10 @@ int fclose(FILE *f);
|
||||
|
||||
// functions for atomically appending to a log that may be in use in multiple
|
||||
// processes
|
||||
bool logfile_open(const char *filename);
|
||||
void logfile_append(const char *msg, size_t length);
|
||||
void logfile_close(const char *filename);
|
||||
struct LogFileHandle;
|
||||
LogFileHandle *logfile_open(const char *filename);
|
||||
void logfile_append(LogFileHandle *logHandle, const char *msg, size_t length);
|
||||
void logfile_close(LogFileHandle *logHandle, const char *deleteFilename);
|
||||
|
||||
// read the whole logfile into memory. This may race with processes writing, but it will read the
|
||||
// whole of the file at some point. Useful since normal file reading may fail on the shared logfile
|
||||
|
||||
@@ -471,15 +471,15 @@ bool exists(const char *filename)
|
||||
return (res == 0);
|
||||
}
|
||||
|
||||
static int logfileFD = -1;
|
||||
rdcarray<int> logfiles;
|
||||
|
||||
// this is used in posix_process.cpp, so that we can close the handle any time that we fork()
|
||||
void ReleaseFDAfterFork()
|
||||
{
|
||||
// we do NOT release the shared lock here, since the file descriptor is shared so we'd be
|
||||
// releasing the parent process's lock. Just close our file descriptor
|
||||
if(logfileFD >= 0)
|
||||
close(logfileFD);
|
||||
for(int log : logfiles)
|
||||
close(log);
|
||||
}
|
||||
|
||||
std::string logfile_readall(const char *filename)
|
||||
@@ -504,35 +504,49 @@ std::string logfile_readall(const char *filename)
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool logfile_open(const char *filename)
|
||||
LogFileHandle *logfile_open(const char *filename)
|
||||
{
|
||||
logfileFD = open(filename, O_APPEND | O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
||||
int fd = open(filename, O_APPEND | O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
||||
|
||||
if(fd < 0)
|
||||
{
|
||||
RDCWARN("Couldn't open logfile '%s': %d", filename, (int)errno);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
logfiles.push_back(fd);
|
||||
|
||||
// acquire a shared lock. Every process acquires a shared lock to the common logfile. Each time a
|
||||
// process shuts down and wants to close the logfile, it releases its shared lock and tries to
|
||||
// acquire an exclusive lock, to see if it can delete the file. See logfile_close.
|
||||
int err = flock(logfileFD, LOCK_SH | LOCK_NB);
|
||||
int err = flock(fd, LOCK_SH | LOCK_NB);
|
||||
|
||||
if(err < 0)
|
||||
RDCWARN("Couldn't acquire shared lock to %s: %d", filename, (int)errno);
|
||||
RDCWARN("Couldn't acquire shared lock to '%s': %d", filename, (int)errno);
|
||||
|
||||
return logfileFD >= 0;
|
||||
return (LogFileHandle *)(uintptr_t)fd;
|
||||
}
|
||||
|
||||
void logfile_append(const char *msg, size_t length)
|
||||
void logfile_append(LogFileHandle *logHandle, const char *msg, size_t length)
|
||||
{
|
||||
if(logfileFD >= 0)
|
||||
write(logfileFD, msg, (unsigned int)length);
|
||||
}
|
||||
|
||||
void logfile_close(const char *filename)
|
||||
{
|
||||
if(logfileFD >= 0)
|
||||
if(logHandle)
|
||||
{
|
||||
// release our shared lock
|
||||
int err = flock(logfileFD, LOCK_UN | LOCK_NB);
|
||||
int fd = int(uintptr_t(logHandle) & 0xffffffff);
|
||||
|
||||
if(err == 0 && filename)
|
||||
write(fd, msg, (unsigned int)length);
|
||||
}
|
||||
}
|
||||
|
||||
void logfile_close(LogFileHandle *logHandle, const char *deleteFilename)
|
||||
{
|
||||
if(logHandle)
|
||||
{
|
||||
int fd = int(uintptr_t(logHandle) & 0xffffffff);
|
||||
|
||||
// release our shared lock
|
||||
int err = flock(fd, LOCK_UN | LOCK_NB);
|
||||
|
||||
if(err == 0 && deleteFilename)
|
||||
{
|
||||
// now try to acquire an exclusive lock. If this succeeds, no other processes are using the
|
||||
// file (since no other shared locks exist), so we can delete it. If it fails, some other
|
||||
@@ -540,20 +554,20 @@ void logfile_close(const char *filename)
|
||||
// NOTE: there is a race here between acquiring the exclusive lock and unlinking, but we
|
||||
// aren't interested in this kind of race - we're interested in whether an application is
|
||||
// still running when the UI closes, or vice versa, or similar cases.
|
||||
err = flock(logfileFD, LOCK_EX | LOCK_NB);
|
||||
err = flock(fd, LOCK_EX | LOCK_NB);
|
||||
|
||||
if(err == 0)
|
||||
{
|
||||
// we got the exclusive lock. Now release it, close fd, and unlink the file
|
||||
err = flock(logfileFD, LOCK_UN | LOCK_NB);
|
||||
err = flock(fd, LOCK_UN | LOCK_NB);
|
||||
|
||||
// can't really error handle here apart from retrying
|
||||
if(err != 0)
|
||||
RDCWARN("Couldn't release exclusive lock to %s: %d", filename, (int)errno);
|
||||
RDCWARN("Couldn't release exclusive lock to '%s': %d", deleteFilename, (int)errno);
|
||||
|
||||
close(logfileFD);
|
||||
close(fd);
|
||||
|
||||
unlink(filename);
|
||||
unlink(deleteFilename);
|
||||
|
||||
// return immediately so we don't close again below.
|
||||
return;
|
||||
@@ -561,12 +575,14 @@ void logfile_close(const char *filename)
|
||||
}
|
||||
else
|
||||
{
|
||||
RDCWARN("Couldn't release shared lock to %s: %d", filename, (int)errno);
|
||||
RDCWARN("Couldn't release shared lock to '%s': %d", deleteFilename, (int)errno);
|
||||
// nothing to do, we won't try again, just exit. The log might lie around, but that's
|
||||
// relatively harmless.
|
||||
}
|
||||
|
||||
close(logfileFD);
|
||||
logfiles.removeOne(fd);
|
||||
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -662,15 +662,12 @@ int fclose(FILE *f)
|
||||
return ::fclose(f);
|
||||
}
|
||||
|
||||
static HANDLE logHandle = NULL;
|
||||
|
||||
bool logfile_open(const char *filename)
|
||||
LogFileHandle *logfile_open(const char *filename)
|
||||
{
|
||||
std::wstring wfn = StringFormat::UTF82Wide(std::string(filename));
|
||||
logHandle = CreateFileW(wfn.c_str(), FILE_APPEND_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
|
||||
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
|
||||
return logHandle != NULL;
|
||||
return (LogFileHandle *)CreateFileW(wfn.c_str(), FILE_APPEND_DATA,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
}
|
||||
|
||||
static std::string logfile_readall_fallback(const wchar_t *filename)
|
||||
@@ -747,25 +744,24 @@ std::string logfile_readall(const char *filename)
|
||||
return ret;
|
||||
}
|
||||
|
||||
void logfile_append(const char *msg, size_t length)
|
||||
void logfile_append(LogFileHandle *logHandle, const char *msg, size_t length)
|
||||
{
|
||||
if(logHandle)
|
||||
{
|
||||
DWORD bytesWritten = 0;
|
||||
WriteFile(logHandle, msg, (DWORD)length, &bytesWritten, NULL);
|
||||
WriteFile((HANDLE)logHandle, msg, (DWORD)length, &bytesWritten, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void logfile_close(const char *filename)
|
||||
void logfile_close(LogFileHandle *logHandle, const char *deleteFilename)
|
||||
{
|
||||
CloseHandle(logHandle);
|
||||
logHandle = NULL;
|
||||
CloseHandle((HANDLE)logHandle);
|
||||
|
||||
if(filename)
|
||||
if(deleteFilename)
|
||||
{
|
||||
// we can just try to delete the file. If it's open elsewhere in another process, the delete
|
||||
// will fail.
|
||||
std::wstring wpath = StringFormat::UTF82Wide(std::string(filename));
|
||||
std::wstring wpath = StringFormat::UTF82Wide(std::string(deleteFilename));
|
||||
::DeleteFileW(wpath.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user