Reputation: 23
I have a process with two threads. One thread, thread A, will set up timerfd timers and the other thread, thread B, will perform 'select' on those timers. Once a timer expires thread B will indicate this to thread A.
To add timers, thread A will create a new timer and then it will wake up thread B to include this timer in its call to select. I'm trying to wake up thread B by using a file descriptor just for that purpose. Thread B would then call select on that FD and all the FD's returned by calls to timerfd.
The problem is that I cannot manage to create a FD that I can control in a way so that it will cause select to block, or return, when I want.
I have tried to use shm_open with calls to fcntl, and i have tried to just use open on a file, but none of them will cause select to block. All my attempts causes select to return immediately. Is there any way to create a FD that will cause select to block until I update that FD somehow?
Try 1 - Create a FD with shm_open and use fcntl to set read lock:
Create the FD from thread A.
if((wakeUpFd = shm_open("/wakeup", O_RDWR|O_CREAT|O_TRUNC, 0)) == -1)
printf("Failed to open /wakeup, Errno = %d\n", errno);
else
{
fcntl(wakeUpFd, F_SETLK, F_RDLCK);
}
Add timer from thread A.
#create a timer and add it to a list
/* wake up timer thread */
fcntl(wakeUpFd, F_SETLK, ~F_RDLCK);
Wake up thread B
#when select returns
if(FD_ISSET(wakeUpFd, &timerSet))
{
fcntl(wakeUpFd, F_SETLK, F_RDLCK);
}
#check all other timer FD's
Try 2 - Use shm_open and read/write data to it:
Create the FD from thread A.
if((wakeUpFd = shm_open("/wakeup", O_RDWR|O_CREAT|O_TRUNC, 0)) == -1)
printf("Failed to open /wakeup, Errno = %d\n", errno);
else
{
if(ftruncate(wakeUpFd, 2) == -1)
{
printf("Failed with ftruncate, Errno = %d\n", errno);
}
}
Add timer from thread A.
#create a timer and add it to a list
/* wake up timer thread */
if(write(wakeUpFd, wakeUpStr, 1) != 1)
printf("Failed to write to wakeUpFd\n");
Wake up thread B
#when select returns
if(FD_ISSET(wakeUpFd, &timerSet))
{
read(wakeUpFd, wakeUpBuf, 10);
}
#check all other timer FD's
Try 3 - Pretty much the same as Try 2 but use open instead of shm_open.
Try 4 - Same as Try 1 but with fcntl(wakeUpFd, F_SETFL, ~O_NONBLOCK) instead of the fcntl(wakeUpFd, F_SETLK, ~F_RDLCK)
Upvotes: 2
Views: 1423
Reputation: 39416
Use an Unix domain socket pair, for example socketpair(AF_UNIX, SOCK_DGRAM, 0, commsd)
for the communications. When thread A creates a new timerfd, it simply writes the new descriptor, an int
, to the communications socket commsd[0]
.
When thread B notices that the communications socket commsd[1]
is readable, it reads one or more int
s from it. Each int
is obviously a new timerd descriptor, so thread B has to add each one to the set it select()
s on.
In thread B, I recommend using nonblocking reads, bytes = recv(commfd, ptr, len, MSG_DONTWAIT)
in a loop to read from the communications socket:
char buffer[8 * sizeof(int)];
size_t head = 0, tail = 0;
ssize_t bytes;
int new_timerfd;
while (1) {
if (head >= tail)
head = tail = 0;
else
if (head > 0) {
memmove(buffer, buffer + head, tail - head);
tail -= head;
head = 0;
}
if (tail < sizeof buffer) {
bytes = recv(commsd[1], buffer + head, sizeof buffer - tail, MSG_DONTWAIT);
if (bytes > (ssize_t)0)
head += bytes;
}
if (head >= tail)
break;
while (head + sizeof (int) <= tail) {
/* Unaligned version of new_timerfd = *(int *)(buffer + head); */
memmove(&new_timerfd, buffer + head, sizeof new_timerfd);
head += sizeof (int);
/*
* Add new_timerfd to select()ed set
*/
}
}
The above loop does do an extra nonblocking recv()
call to detect it has read all that is immediately pending, but this way it is extremely robust. It does not even assume you can always read a full int
. (Since _POSIX_PIPE_BUF
is always a multiple of sizeof int
, you could assume you always read full int
.)
The above loop does a nonblocking receive from the socket for as long as there is data available, and the loop body will extract the descriptors to new_timerfd
one by one. I omitted the code that adds it to the select()
ed sets.
Finally, this approach works in the general case, too, when you transfer a larger structure to a thread that does select()
in a loop. Just make sure the buffer is large enough for at least two structures, and you're set; the memmove()
s handle any buffer packing and structure alignment issues you might otherwise have.
Upvotes: 1
Reputation: 754790
Read the select()
specification, especially the bit where it says:
File descriptors associated with regular files shall always select true for ready to read, ready to write, and error conditions.
You can't make select()
block on file descriptors for regular files. You'd have to have a pipe, or socket, or something along those lines, as the file you select()
on.
Upvotes: 4