I'm writing unit tests for a function that may lock a file (using fcntl(fd, F_SETLK, ...)) under some conditions.
I want my unit test to be able to EXPECT that the file is or is not locked at certain points. But I can't find any way to test that. I've tried using F_GETLK, but it will only tell you if the lock would not be able to be placed. Since a given process can re-lock the same file as often as it wants, F_GETLK is returning F_UNLCK, indicating the file is unlocked.
For instance, if I run the following little program:
int main(int argc, char** argv) {
int fd = open("/tmp/my_test_file", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (fd < 0) {
return EXIT_FAILURE;
}
// Initial lock
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0; // Lock entire file.
if (fcntl(fd, F_SETLK, &lock) < 0) {
return EXIT_FAILURE;
}
// Test lock:
lock.l_type = F_WRLCK;
lock.l_pid = 0;
if (fcntl(fd, F_GETLK, &lock) < 0) {
return EXIT_FAILURE;
}
switch (lock.l_type) {
case F_WRLCK:
std::cout << lock.l_pid << " is holding the lock\n";
break;
case F_UNLCK:
std::cout << "File is unlocked\n";
break;
default:
std::cout << "Unexpected " << lock.l_type << "\n";
break;
}
return EXIT_SUCCESS;
}
It will print:
File is unlocked
So, is there a way for a process to test if it is holding an fcntl file lock?
Also, are there other kinds of (Linux-portable!) file locks I could be using that would solve my problem?
Well I am not aware of any "already available library” for this, but on implementation level I would suggest you to have a log file which keeps track of that.
You can simply create a file named “log”, mmap(2) it as MAP_SHARED into each process accessing the file and whenever a file lock succeeds, write the current process’ pid at the end of that log file maintaining offset to the end with CAS. That will help you analyse order of locks.
Maybe simply by opening file in append more and writing to the end, the pid of current process.
Or maybe a quicker way to do so such a test is to create a fifo file by mkfifo(2) and write to that file.
Related
I am using this really simple code to try to create a mutex
int main(){
HANDLE hMutex = ::CreateMutex(nullptr, FALSE, L"SingleInstanceMutex");
if(!hMutex){
wchar_t buff[1000];
_snwprintf(buff, sizeof(buff), L"Failed to create mutex (Error: %d)", ::GetLastError());
::MessageBox(nullptr, buff, L"Single Instance", MB_OK);
return 0x1;
} else {
::MessageBox(nullptr, L"Mutex Created", L"Single Instance", MB_OK);
}
return 0x0;
}
And I get the message "Mutex Created" like if it is correctly created, but when I try to search it using the tool WinObj of SysInternals I can't find it.
Also if I restart the program many times while another instance is running I always get the message "Mutex Created" and never an error because the mutex already exists.
I'm trying it on a Windows 7 VM.
What I'm doing wrong?
Ah I'm compiling on Linux using:
i686-w64-mingw32-g++ -static-libgcc -static-libstdc++ Mutex.cpp
Thank you!
In order to use a Windows mutex (whether a named one like yours or an unnamed one), you need to use the following Win APIs:
CreateMutex - to obtain a handle to the mutex Windows kernel object. In case of a named mutex (like yours) multiple processes should succeed to get this handle. The first one will cause the OS to create a new named mutex, and the others will get a handle referring to that same mutex.
In case the function succeeds and you get a valid handle to the named mutex, you can determine whether the mutex already existed (i.e. that another process already created the mutex) by checking if GetLastError returns ERROR_ALREADY_EXISTS.
WaitForSingleObject - to lock the mutex for exclusive access. This function is actually not specific to mutex and is used for many kernel objects. See the link above for more info about Windows kernel objects.
ReleaseMutex - to unlock the mutex.
CloseHandle - to release the acquired mutex handle (as usual with Windows handles). The OS will automatically close the handle when the process exists, but it is good practice to do it explicitly.
A complete example:
#include <Windows.h>
#include <iostream>
int main()
{
// Create the mutex handle:
HANDLE hMutex = ::CreateMutex(nullptr, FALSE, L"SingleInstanceMutex");
if (!hMutex)
{
std::cout << "Failed to create mutex handle." << std::endl;
// Handle error: ...
return 1;
}
bool bAlreadyExisted = (GetLastError() == ERROR_ALREADY_EXISTS);
std::cout << "Succeeded to create mutex handle. Already existed: " << (bAlreadyExisted ? "YES" : "NO") << std::endl;
// Lock the mutex:
std::cout << "Atempting to lock ..." << std::endl;
DWORD dwRes = ::WaitForSingleObject(hMutex, INFINITE);
if (dwRes != WAIT_OBJECT_0)
{
std::cout << "Failed to lock the mutex" << std::endl;
// Handle error: ...
return 1;
}
std::cout << "Locked." << std::endl;
// Do something that required the lock: ...
std::cout << "Press ENTER to unlock." << std::endl;
std::getchar();
// Unlock the mutex:
if (!::ReleaseMutex(hMutex))
{
std::cout << "Failed to unlock the mutex" << std::endl;
// Handle error: ...
return 1;
}
std::cout << "Unlocked." << std::endl;
// Free the handle:
if (!CloseHandle(hMutex))
{
std::cout << "Failed to close the mutex handle" << std::endl;
// Handle error: ...
return 1;
}
return 0;
}
Error handling:
As you can see in the documentation links above, when CreateMutex,ReleaseMutex and CloseHandle fail, you should call GetLastError to get more info about the error. WaitForSingleObject will return a specific return value upon error (see the documentation link above). This should be done as a part of the // Handle error: ... sections.
Note:
Using a named mutex for IPC (interprocess communication) might be the only good use case for native Windows mutexes.
For a regular unnamed mutex it's better to use one of the available standard library types of mutexes: std::mutex,std::recursive_mutex,std::recursive_timed_mutex (the last 2 support repeated lock by a thread, similarly to Windows mutex).
I have a C++ script, which checks whether any action has to be done and if so it starts the right processor C++ script. However, since it runs every x minutes it also checks whether the processor isn't still running using lock files.
I use the following function to acquire the lock:
int LockFile(string FileNameToLock) {
FileNameToLock += ".lock";
int fd = open(FileNameToLock.c_str(), O_RDWR | O_CREAT, 0666);
int rc = flock(fd, LOCK_EX | LOCK_NB);
if (rc || rc == -1) {
cout << errno << endl;
cout << strerror(errno) << endl;
return -1;
}
return fd;
}
The code that is being executed:
[...]
if (LockFile(ExecuteFileName, Extra) == -1) {
cout << "Already running!" << endl; //MAIN IS ALREADY RUNNING
//RETURNS ME Resource temporarily unavailable when processor is running from an earlier run
exit(EXIT_SUCCESS);
}
if (StartProcessor) { //PSEUDO
int LockFileProcessor = LockFile("Processor");
if (LockFileProcessor != -1) {
string Command = "nohup setsid ./Processor"; //NOHUP CREATES ZOMBIE?
Command += IntToString(result->getInt("Klantnummer"));
Command += " > /dev/null 2>&1 &"; //DISCARD OUTPUT
system(Command.c_str());
//STARTS PROCESSOR (AS ZOMBIE?)
}
}
The first run works well, however when the main script runs again, the lock file returns -1, which means an error occurred (only when the processor is still running). The errno is 11 which results in the error message: Resource temporarily unavailable. Note that this only happens when the (zombie) processor is still running. (However, the main script has already terminated, which should close the file handle?)
For some reason, the zombie keeps the file handle to the lock file of the main script open???
I have no idea where to look for this problem.
SOLVED:
see my answer
No, 11 is EAGAIN/EWOULDBLOCK which simply means that you cannot acquire the lock because the resource is already locked (see the documentation). You received that error (instead of blocking behaviour) due to LOCK_NB flag.
EDIT: After some discussion it seems that the problem is due to flock() locks being preserved when subprocessing. To avoid this issue I recommend not using flock() for the lifetime but instead touch-and-delete-at-exit strategy:
If file.lock exists then exit
Otherwise create file.lock and start processing
Delete file.lock at exit.
Of course there's a race condition here. In order to make it safe you would need another file with flock:
flock(common.flock)
If file.lock exists then exit
Otherwise create file.lock
Unlock flock(common.flock)
Start processing
Delete file.lock at exit
But this only matters if you expect simultaneous calls to main. If you don't (you said that a cron starts the process every 10min, no race here) then stick to the first version.
Side note: here's a simple implementation of such (non-synchronized) file lock:
#include <string>
#include <fstream>
#include <stdexcept>
#include <cstdio>
// for sleep only
#include <chrono>
#include <thread>
class FileLock {
public:
FileLock(const std::string& path) : path { path } {
if (std::ifstream{ path }) {
// You may want to use a custom exception here
throw std::runtime_error("Already locked.");
}
std::ofstream file{ path };
};
~FileLock() {
std::remove(path.c_str());
};
private:
std::string path;
};
int main() {
// This will throw std::runtime_error if test.xxx exists
FileLock fl { "./test.xxx" };
std::this_thread::sleep_for(std::chrono::seconds { 5 });
// RAII: no need to delete anything here
return 0;
};
Requires C++11. Note that this implementation is not race-condition-safe, i.e. you generally need to flock() the constructor but in this situation it probably be fine (i.e. when you don't start main in parallel).
I solved this issue, since the system and fork commands seem to pass on the flock, by saving the command to execute in a vector. Unlock the lock file right before looping the Commands vector and execute each command. This leaves the main with a very tiny gap of running while not locked, but for now it seems to work great. This also supports ungraceful terminations.
[...]
if (LockFile(ExecuteFileName, Extra) == -1) {
cout << "Already running!" << endl; //MAIN IS ALREADY RUNNING
//RETURNS ME Resource temporarily unavailable when processor is running from an earlier run
exit(EXIT_SUCCESS);
}
vector<string> Commands;
if (StartProcessor) { //PSEUDO
int LockFileProcessor = LockFile("Processor");
if (LockFileProcessor != -1) {
string Command = "nohup setsid ./Processor"; //NOHUP CREATES ZOMBIE
Command += IntToString(result->getInt("Klantnummer"));
Command += " > /dev/null 2>&1 &"; //DISCARD OUTPUT
Commands.push_back(Command);
}
}
//UNLOCK MAIN
if (UnlockFile(LockFileMain)) {
for(auto const& Command: Commands) {
system(Command.c_str());
}
}
I am currently working on a program that does IPC via Posix Message Queues. I now need a function that removes every message of that queue. The problem is: My code deadlocks. Currently I am trying the following:
void clear_mq(std::string queue_name)
{
struct mq_attr mq_attrs = {0, 10, sizeof(uint8_t), 0};
mqd_t mq = ::mq_open(queue_name.c_str(), O_WRONLY | O_CREAT, 00644, &mq_attrs);
if (mq < 0)
{
std::cout << "Error opening Queue" << std::endl;
exit(-1);
}
struct mq_attr num_messages;
if (mq_getattr(mq, &num_messages) == -1)
{
std::cout << "Error!" << std::endl;
exit(-1);
}
while (num_messages.mq_curmsgs > 0)
{
uint8_t buf;
mq_receive(mq, (char *)&buf, sizeof(uint8_t), NULL);
if (mq_getattr(mq, &num_messages) == -1)
{
std::cout << "Error!" << std::endl;
exit(-1);
}
}
mq_close(mq);
}
Can anyone point out what I am doing wrong? I do not understand why the receive is blocking... At that moment when I call clear_mq noone else is in the receive block...
Could be that mq_receive() fails and you end up in a endless loop.
mq_receive() can fail for various reasons, e.g. the buffer provided must at least have the size of the mq-maxsize.
You should check the return value of mq_receive() and exit the loop if it fails.
You IMHO have no deadlock. However, the mq_receive blocks until it receives a message (man mq_receive) because the queue is not open with O_NONBLOCK parameter while mq_open.
Please also ensure you do not neglect the return value of the mq_receive in the loop.
In case someone else has the problem.
When printing the errno I get error 9 (Bad file descriptor), which makes sense cause the message queue is only opened for write but you are trying to read from it. When you open the queue with O_RDWR see mq_open it should work.
A tip for debugging use mq_timedreceive so that you can check the error.
I have a small problem with my code in the following. I call it in my class from within a state machine this->write_file(this->d_filename);. The case in the loop gets hit a couple of times, however I only have one line of entries in the CSV file I want to produce.
I'm not sure why this is. I open the file with this->open(filename) in my write function. It returns the file-descriptor. The file is opened with O_TRUNK, and if ((d_new_fp = fdopen(fd, d_is_binary ? "wba" : "w")) == NULL). While the aba refers to write, binary and append. Therefore I expect more than one line.
The fprintf statement writes my data. It also has a \n.
fprintf(d_new_fp, "%s, %d %d\n", this->d_packet, this->d_lqi, this->d_lqi_sample_count);
I simply can't figure out why my file doesn't grow.
Best,
Marius
inline bool
cogra_ieee_802_15_4_sink::open(const char *filename)
{
gruel::scoped_lock guard(d_mutex); // hold mutex for duration of this function
// we use the open system call to get access to the O_LARGEFILE flag.
int fd;
if ((fd = ::open(filename, O_WRONLY | O_CREAT | O_TRUNC | OUR_O_LARGEFILE,
0664)) < 0)
{
perror(filename);
return false;
}
if (d_new_fp)
{ // if we've already got a new one open, close it
fclose(d_new_fp);
d_new_fp = 0;
}
if ((d_new_fp = fdopen(fd, d_is_binary ? "wba" : "w")) == NULL)
{
perror(filename);
::close(fd);
}
d_updated = true;
return d_new_fp != 0;
}
inline void
cogra_ieee_802_15_4_sink::close()
{
gruel::scoped_lock guard(d_mutex); // hold mutex for duration of this function
if (d_new_fp)
{
fclose(d_new_fp);
d_new_fp = 0;
}
d_updated = true;
}
inline void
cogra_ieee_802_15_4_sink::write_file(const char* filename)
{
if (this->open(filename))
{
fprintf(d_new_fp, "%s, %d %d\n", this->d_packet, this->d_lqi,
this->d_lqi_sample_count);
if (true)
{
fprintf(stderr, "Writing file %x\n", this->d_packet);
}
}
}
Description for O_TRUNC from man open:
If the file already exists and is a regular file and the open mode allows writing (i.e., is O_RDWR or O_WRONLY) it will be truncated to length 0. If the file is a FIFO or terminal device file, the O_TRUNC flag is ignored. Otherwise the effect of O_TRUNC is unspecified.
The file is opened in each call to write_file(), removing anything that was previously written. Replace O_TRUNC with O_APPEND.
I wrote a UNIX daemon (targeting Debian, but it shouldn't matter) and I wanted to provide some way of creating a ".pid" file, (a file which contains the process identifier of the daemon).
I searched for a way of opening a file only if it doesn't exist, but couldn't find one.
Basically, I could do something like:
if (fileexists())
{
//fail...
}
else
{
//create it with fopen() or similar
}
But as it stands, this code does not perform the task in a atomic fashion, and doing so would be dangerous, because another process might create the file during my test, and the file creation.
Do you guys have any idea on how to do that?
Thank you.
P.S: Bonus point for a solution which only involves std::streams.
man 2 open:
O_EXCL Ensure that this call creates the file: if this flag is specified in conjunction with O_CREAT, and pathname already exists, then open()
will fail. The behavior of O_EXCL is undefined if O_CREAT is not specified.
so, you could call fd = open(name, O_CREAT | O_EXCL, 0644); /* Open() is atomic. (for a reason) */
UPDATE: and you should of course OR one of the O_RDONLY, O_WRONLY, or O_RDWR flags into the flags argument.
I learned about proper daemonizing here (back in the day):
http://www.enderunix.org/docs/eng/daemon.php
It is a good read. I have since improved the locking code to eliminate race conditions on platforms that allow advisory file locking with specific regions specified.
Here is a relevant snippet from a project that I was involved in:
static int zfsfuse_do_locking(int in_child)
{
/* Ignores errors since the directory might already exist */
mkdir(LOCKDIR, 0700);
if (!in_child)
{
ASSERT(lock_fd == -1);
/*
* before the fork, we create the file, truncating it, and locking the
* first byte
*/
lock_fd = creat(LOCKFILE, S_IRUSR | S_IWUSR);
if(lock_fd == -1)
return -1;
/*
* only if we /could/ lock all of the file,
* we shall lock just the first byte; this way
* we can let the daemon child process lock the
* remainder of the file after forking
*/
if (0==lockf(lock_fd, F_TEST, 0))
return lockf(lock_fd, F_TLOCK, 1);
else
return -1;
} else
{
ASSERT(lock_fd != -1);
/*
* after the fork, we instead try to lock only the region /after/ the
* first byte; the file /must/ already exist. Only in this way can we
* prevent races with locking before or after the daemonization
*/
lock_fd = open(LOCKFILE, O_WRONLY);
if(lock_fd == -1)
return -1;
ASSERT(-1 == lockf(lock_fd, F_TEST, 0)); /* assert that parent still has the lock on the first byte */
if (-1 == lseek(lock_fd, 1, SEEK_SET))
{
perror("lseek");
return -1;
}
return lockf(lock_fd, F_TLOCK, 0);
}
}
void do_daemon(const char *pidfile)
{
chdir("/");
if (pidfile) {
struct stat dummy;
if (0 == stat(pidfile, &dummy)) {
cmn_err(CE_WARN, "%s already exists; aborting.", pidfile);
exit(1);
}
}
/*
* info gleaned from the web, notably
* http://www.enderunix.org/docs/eng/daemon.php
*
* and
*
* http://sourceware.org/git/?p=glibc.git;a=blob;f=misc/daemon.c;h=7597ce9996d5fde1c4ba622e7881cf6e821a12b4;hb=HEAD
*/
{
int forkres, devnull;
if(getppid()==1)
return; /* already a daemon */
forkres=fork();
if (forkres<0)
{ /* fork error */
cmn_err(CE_WARN, "Cannot fork (%s)", strerror(errno));
exit(1);
}
if (forkres>0)
{
int i;
/* parent */
for (i=getdtablesize();i>=0;--i)
if ((lock_fd!=i) && (ioctl_fd!=i)) /* except for the lockfile and the comm socket */
close(i); /* close all descriptors */
/* allow for airtight lockfile semantics... */
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 200000; /* 0.2 seconds */
select(0, NULL, NULL, NULL, &tv);
VERIFY(0 == close(lock_fd));
lock_fd == -1;
exit(0);
}
/* child (daemon) continues */
setsid(); /* obtain a new process group */
VERIFY(0 == chdir("/")); /* change working directory */
umask(027); /* set newly created file permissions */
devnull=open("/dev/null",O_RDWR); /* handle standard I/O */
ASSERT(-1 != devnull);
dup2(devnull, 0); /* stdin */
dup2(devnull, 1); /* stdout */
dup2(devnull, 2); /* stderr */
if (devnull>2)
close(devnull);
/*
* contrary to recommendation, do _not_ ignore SIGCHLD:
* it will break exec-ing subprocesses, e.g. for kstat mount and
* (presumably) nfs sharing!
*
* this will lead to really bad performance too
*/
signal(SIGTSTP,SIG_IGN); /* ignore tty signals */
signal(SIGTTOU,SIG_IGN);
signal(SIGTTIN,SIG_IGN);
}
if (0 != zfsfuse_do_locking(1))
{
cmn_err(CE_WARN, "Unexpected locking conflict (%s: %s)", strerror(errno), LOCKFILE);
exit(1);
}
if (pidfile) {
FILE *f = fopen(pidfile, "w");
if (!f) {
cmn_err(CE_WARN, "Error opening %s.", pidfile);
exit(1);
}
if (fprintf(f, "%d\n", getpid()) < 0) {
unlink(pidfile);
exit(1);
}
if (fclose(f) != 0) {
unlink(pidfile);
exit(1);
}
}
}
See also http://gitweb.zfs-fuse.net/?p=sehe;a=blob;f=src/zfs-fuse/util.c;h=7c9816cc895db4f65b94592eebf96d05cd2c369a;hb=refs/heads/maint
The only way I can think of is to use system level locks. See this: C++ how to check if file is in use - multi-threaded multi-process system
One way to approach this problem is to open the file for appending. If the function succeeds and the position is at 0 then you can be fairly certain this is a new file. Could still be an empty file but that scenario may not be important.
FILE* pFile = fopen(theFilePath, "a+");
if (pFile && gfetpos(pFile) == 0) {
// Either file didn't previously exist or it did and was empty
} else if (pFile) {
fclose(pFile);
}
It would appear that there's no way to do it strictly using streams.
You can, instead, use open (as mentioned above by wildplasser) and if that succeeds, proceed to open the same file as a stream. Of course, if all you're writing to the file is a PID, it is unclear why you wouldn't just write it using C-style write().
O_EXCL only excludes other processes that are attempting to open the same file using O_EXCL. This, of course, means that you never have a perfect guarantee, but if the file name/location is somewhere nobody else is likely to be opening (other than folks you know are using O_EXCL) you should be OK.