common: Improve lock strategy for dotlock.

* common/dotlock.c (next_wait_interval): New.
(dotlock_take_unix): Use new function.
(dotlock_take_w32): Ditto.
--

In particular when using a dotlock file for protecting the spawning
and several processes try to spawn the agent or another component, we
often run into long delays.  The solution is to is to exponential
backoff and also to reduce the initial delay from 50ms to 4ms.  We
further limit the maximum wait period to about 2 seconds and then
repeat at intervals of 512, 1024 and 2048ms.  In the wait-forever case
we add a small random value to have different intervals per process.

GnuPG-bug-id: 3380

For testing this code snippet in the spawning function might be
useful:

          const char *s;
          if ((s=getenv("hold_gpg_file")))
            while (!gnupg_access (s, F_OK))
              gnupg_sleep (1);
This commit is contained in:
Werner Koch 2023-10-02 12:53:41 +02:00
parent d7a1577a25
commit 9a3e41c151
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
1 changed files with 52 additions and 38 deletions

View File

@ -1011,6 +1011,48 @@ dotlock_destroy (dotlock_t h)
}
/* Return true if H has been taken. */
int
dotlock_is_locked (dotlock_t h)
{
return h && !!h->locked;
}
/* Return the next interval to wait. WTIME and TIMEOUT are pointers
* to the current state and are updated by this function. The
* returned value might be different from the value of WTIME. */
static int
next_wait_interval (int *wtime, long *timeout)
{
int result;
/* Wait until lock has been released. We use retry intervals of 4,
* 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 512, 1024, 2048ms, and
* so on. If wait-forever was requested we add a small random value
* to have different timeouts per process. */
if (!*wtime)
*wtime = 4;
else if (*wtime < 2048)
*wtime *= 2;
else
*wtime = 512;
result = *wtime;
if (*wtime > 8 && *timeout < 0)
result += ((unsigned int)getpid() % 37);
if (*timeout > 0)
{
if (result > *timeout)
result = *timeout;
*timeout -= result;
}
return result;
}
#ifdef HAVE_POSIX_SYSTEM
/* Unix specific code of make_dotlock. Returns 0 on success and -1 on
@ -1170,27 +1212,14 @@ dotlock_take_unix (dotlock_t h, long timeout)
if (timeout)
{
struct timeval tv;
int wtimereal;
/* Wait until lock has been released. We use increasing retry
intervals of 50ms, 100ms, 200ms, 400ms, 800ms, 2s, 4s and 8s
but reset it if the lock owner meanwhile changed. */
if (!wtime || ownerchanged)
wtime = 50;
else if (wtime < 800)
wtime *= 2;
else if (wtime == 800)
wtime = 2000;
else if (wtime < 8000)
wtime *= 2;
if (ownerchanged)
wtime = 0; /* Reset because owner chnaged. */
if (timeout > 0)
{
if (wtime > timeout)
wtime = timeout;
timeout -= wtime;
}
wtimereal = next_wait_interval (&wtime, &timeout);
sumtime += wtime;
sumtime += wtimereal;
if (sumtime >= 1500)
{
sumtime = 0;
@ -1198,9 +1227,8 @@ dotlock_take_unix (dotlock_t h, long timeout)
pid, maybe_dead, maybe_deadlock(h)? _("(deadlock?) "):"");
}
tv.tv_sec = wtime / 1000;
tv.tv_usec = (wtime % 1000) * 1000;
tv.tv_sec = wtimereal / 1000;
tv.tv_usec = (wtimereal % 1000) * 1000;
select (0, NULL, NULL, NULL, &tv);
goto again;
}
@ -1242,28 +1270,14 @@ dotlock_take_w32 (dotlock_t h, long timeout)
if (timeout)
{
/* Wait until lock has been released. We use retry intervals of
50ms, 100ms, 200ms, 400ms, 800ms, 2s, 4s and 8s. */
if (!wtime)
wtime = 50;
else if (wtime < 800)
wtime *= 2;
else if (wtime == 800)
wtime = 2000;
else if (wtime < 8000)
wtime *= 2;
int wtimereal;
if (timeout > 0)
{
if (wtime > timeout)
wtime = timeout;
timeout -= wtime;
}
wtimereal = next_wait_interval (&wtime, &timeout);
if (wtime >= 800)
my_info_1 (_("waiting for lock %s...\n"), h->lockname);
Sleep (wtime);
Sleep (wtimereal);
goto again;
}