Pls sanity check my semtimedop(2) implementation
Michael B Allen
ioplex at gmail.com
Thu Jul 31 04:12:37 UTC 2008
On Fri, Jul 18, 2008 at 11:58 AM, Jilles Tjoelker <jilles at stack.nl> wrote:
> On Sat, Jul 12, 2008 at 07:11:26PM -0400, Michael B Allen wrote:
>> Below is a semtimedop(2) implementation that I'm using for FreeBSD. I
>> was hoping someone could look it over and tell me if they think the
>> implementation is sound.
>
>> [snip semtimedop implementation that uses SIGALRM and relies on EINTR]
>
>> The code seems to work ok but when stressing the FreeBSD build of my app
>> I have managed to provoke errors related to concurrency (usually when a
>> SIGALRM goes off). The Linux build works flawlessesly so I'm wondering
>> about this one critical function that is different.
>
> In your implementation, the SIGALRM signal may happen before you even
> call semop(2). If so, most likely the semop(2) will hang arbitrarily
> long.
Indeed. And I reconnoiter that condition is likely to happen if called
with a sufficiently small timeout value.
> Another dirty fix is to try non-blocking semop(2) several times with
> sleeps in between.
Actually that seems to work pretty well.
If I force the nanosleep codepath to be used always, the application
actually works pretty well under load. I wonder if the overhead of
managing the signal and timer is worth the trouble.
For posterity, below is the current implementation of semtimedop(2)
for FreeBSD. Further ideas are welcome.
void
_timeval_diff(struct timeval *tv1, struct timeval *tv2, struct timeval *tvd)
{
tvd->tv_sec = tv1->tv_sec - tv2->tv_sec;
tvd->tv_usec = tv1->tv_usec - tv2->tv_usec;
if (tvd->tv_usec < 0) {
tvd->tv_usec = 1000000 - tvd->tv_usec;
tvd->tv_sec--;
}
}
void
signal_ignore(int s, siginfo_t *si, void *ctx)
{
}
int
_semtimedop(int semid, struct sembuf *array, size_t nops, struct
timespec *_timeout)
{
struct timeval timeout, before, after;
struct itimerval value, ovalue;
struct sigaction sa, osa;
int ret;
if (_timeout) {
timeout.tv_sec = _timeout->tv_sec;
timeout.tv_usec = _timeout->tv_nsec / 1000;
if (gettimeofday(&before, NULL) == -1) {
errno = EFAULT;
return -1;
}
if (timeout.tv_sec == 0 && timeout.tv_usec < 5000) {
struct timeval tsleep, tend;
struct sembuf wait;
wait = *array;
wait.sem_flg |= IPC_NOWAIT;
tsleep.tv_sec = 0;
tsleep.tv_usec = 1;
timeradd(&before, &timeout, &tend);
for ( ;; ) {
struct timeval tnow, tleft;
struct timespec ts;
ret = semop(semid, &wait, nops);
if (ret == 0) {
return 0;
} else if (errno != EAGAIN) {
break;
}
if (gettimeofday(&tnow, NULL) == -1) {
errno = EFAULT;
break;
}
if (timercmp(&tnow, &tend, >=)) {
errno = EAGAIN;
break;
}
timersub(&tend, &tnow, &tleft);
if (tsleep.tv_usec > tleft.tv_usec)
tsleep.tv_usec = tleft.tv_usec;
ts.tv_sec = 0;
ts.tv_nsec = tsleep.tv_usec * 1000;
nanosleep(&ts, NULL);
tsleep.tv_usec *= 10;
}
return -1;
}
memset(&value, 0, sizeof value);
value.it_value = timeout;
memset(&sa, 0, sizeof sa);
sa.sa_sigaction = signal_ignore;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sigaction(SIGALRM, &sa, &osa);
if (setitimer(ITIMER_REAL, &value, &ovalue) == -1) {
sigaction(SIGALRM, &osa, NULL);
return -1;
}
}
ret = semop(semid, array, nops);
if (_timeout) {
ret = setitimer(ITIMER_REAL, &ovalue, NULL);
if (ret < 0)
errno = EFAULT;
sigaction(SIGALRM, &osa, NULL);
}
if (ret == -1) {
if (_timeout) {
struct timeval elapsed;
if (gettimeofday(&after, NULL) == -1) {
} else {
_timeval_diff(&after, &before, &elapsed);
if (timercmp(&elapsed, &timeout, >=))
errno = EAGAIN;
}
}
return -1;
}
return 0;
}
More information about the freebsd-hackers
mailing list