comparison tests/nap.h @ 17427:afd09cd62311

tests/nap.h: use an adaptive delay to avoid ctime update issues The recent change in nap.h (5191133e) decreased the probability of lost races to about a third, however such problems could still be observed in virtual machines and openSUSE's OBS. Instead of calulating the nap() time once and using it (together with a small correction multiplier), avoid the race alltogether by verifying on a reference file whether a timestamp difference has happened. Before, nap() detected the needed time once empirically and then used that delay (together with a small correction multiplier) in further calls. This problem has been reported and discussed several times, including guesses about possible kernel issues: https://lists.gnu.org/archive/html/bug-gnulib/2013-04/msg00071.html http://lists.gnu.org/archive/html/coreutils/2012-03/msg00088.html https://lists.gnu.org/archive/html/bug-gnulib/2011-11/msg00226.html http://bugs.gnu.org/12820 https://lists.gnu.org/archive/html/bug-gnulib/2010-11/msg00113.html https://lists.gnu.org/archive/html/bug-gnulib/2009-11/msg00007.html Now, nap() avoids the race alltogether by verifying on a reference file whether a timestamp difference has happened. * tests/nap.h (nap_fd): Define file descriptor variable for the witness file. (nap_works): Change return value to bool. Change passing the old file's status by value instead of by reference as this function does no longer update that timestamp; rename the function argument from st to old_st. Remove the local variables cdiff and mdiff because that function now returns true/false instead of the precise delay. (guess_delay): Remove function. (clear_tmp_file): Add new function to close and unlink the witness file. (nap): Instead of re-using the delay which has been calculated during the first call, avoid the race by actually verifying that a timestamp difference can be observed on the current file system. Use an adaptive approach for the delay to minimize execution time. Assert that the maximum delay is <= ~2 seconds, more precisely sum(2^n) from 0 to 30 = 2^31 - 1 = 2.1s. Use atexit to call clear_tmp_file when the process terminates.
author Bernhard Voelker <mail@bernhard-voelker.de>
date Wed, 05 Jun 2013 09:20:15 +0200
parents 3c592b4deb04
children
comparison
equal deleted inserted replaced
17426:90f3d53e01f5 17427:afd09cd62311
18 18
19 #ifndef GLTEST_NAP_H 19 #ifndef GLTEST_NAP_H
20 # define GLTEST_NAP_H 20 # define GLTEST_NAP_H
21 21
22 # include <limits.h> 22 # include <limits.h>
23 # include <stdbool.h>
24
25 /* File descriptor used for the witness file. */
26 static int nap_fd = -1;
23 27
24 /* Return A - B, in ns. 28 /* Return A - B, in ns.
25 Return 0 if the true result would be negative. 29 Return 0 if the true result would be negative.
26 Return INT_MAX if the true result would be greater than INT_MAX. */ 30 Return INT_MAX if the true result would be greater than INT_MAX. */
27 static int 31 static int
51 ASSERT (write (fd, "\n", 1) == 1); 55 ASSERT (write (fd, "\n", 1) == 1);
52 ASSERT (fstat (fd, st) == 0); 56 ASSERT (fstat (fd, st) == 0);
53 } 57 }
54 58
55 /* Given a file whose descriptor is FD, see whether delaying by DELAY 59 /* Given a file whose descriptor is FD, see whether delaying by DELAY
56 nanoseconds causes a change in a file's time stamp. *ST is the 60 nanoseconds causes a change in a file's ctime and mtime.
57 file's status, recently gotten. Update *ST to reflect the latest 61 OLD_ST is the file's status, recently gotten. */
58 status gotten. If successful, return the needed delay, in 62 static bool
59 nanoseconds as determined by the observed time stamps; this may be 63 nap_works (int fd, int delay, struct stat old_st)
60 greater than DELAY if we crossed a quantization boundary. If
61 unsuccessful, return 0. */
62 static int
63 nap_works (int fd, int delay, struct stat *st)
64 { 64 {
65 struct stat old_st = *st; 65 struct stat st;
66 struct timespec delay_spec; 66 struct timespec delay_spec;
67 int cdiff, mdiff;
68 delay_spec.tv_sec = delay / 1000000000; 67 delay_spec.tv_sec = delay / 1000000000;
69 delay_spec.tv_nsec = delay % 1000000000; 68 delay_spec.tv_nsec = delay % 1000000000;
70 ASSERT (nanosleep (&delay_spec, 0) == 0); 69 ASSERT (nanosleep (&delay_spec, 0) == 0);
71 get_stat (fd, st, 1); 70 get_stat (fd, &st, 1);
72 71
73 /* Return the greater of the ctime and the mtime differences, or 72 if ( diff_timespec (get_stat_ctime (&st), get_stat_ctime (&old_st))
74 zero if it cannot be determined, or INT_MAX if either overflows. */ 73 && diff_timespec (get_stat_mtime (&st), get_stat_mtime (&old_st)))
75 cdiff = diff_timespec (get_stat_ctime (st), get_stat_ctime (&old_st)); 74 return true;
76 if (cdiff != 0) 75
77 { 76 return false;
78 mdiff = diff_timespec (get_stat_mtime (st), get_stat_mtime (&old_st));
79 if (mdiff != 0)
80 return cdiff < mdiff ? mdiff : cdiff;
81 }
82 return 0;
83 } 77 }
84 78
85 static int 79 #define TEMPFILE BASE "nap.tmp"
86 guess_delay (void) 80
81 static void
82 clear_temp_file (void)
87 { 83 {
88 /* Try a 1-ns sleep first, for speed. If that doesn't work, try 100 84 if (0 <= nap_fd)
89 ns, 1 microsecond, 1 ms, etc. xfs has a quantization of about 10
90 milliseconds, even though it has a granularity of 1 nanosecond,
91 and NTFS has a default quantization of 15.25 milliseconds, even
92 though it has a granularity of 100 nanoseconds, so 15.25 ms is a
93 good quantization to try. If that doesn't work, try 1 second.
94 The worst case is 2 seconds, needed for FAT. */
95 static int const delaytab[] = {1, 1000, 1000000, 15250000, 1000000000 };
96 int fd = creat (BASE "tmp", 0600);
97 int i;
98 int delay = 2000000000;
99 struct stat st;
100 ASSERT (0 <= fd);
101 get_stat (fd, &st, 0);
102 for (i = 0; i < sizeof delaytab / sizeof delaytab[0]; i++)
103 { 85 {
104 int d = nap_works (fd, delaytab[i], &st); 86 ASSERT (close (nap_fd) != -1);
105 if (d != 0) 87 ASSERT (unlink (TEMPFILE) != -1);
106 {
107 delay = d;
108 break;
109 }
110 } 88 }
111 ASSERT (close (fd) == 0);
112 ASSERT (unlink (BASE "tmp") == 0);
113 return delay;
114 } 89 }
115 90
116 /* Sleep long enough to notice a timestamp difference on the file 91 /* Sleep long enough to notice a timestamp difference on the file
117 system in the current directory. Assumes that BASE is defined, 92 system in the current directory. Use an adaptive approach, trying
118 and requires that the test module depends on nanosleep. */ 93 to find the smallest delay which works on the current file system
94 to make the timestamp difference appear. Assert a maximum delay of
95 ~2 seconds, more precisely sum(2^n) from 0 to 30 = 2^31 - 1 = 2.1s.
96 Assumes that BASE is defined, and requires that the test module
97 depends on nanosleep. */
119 static void 98 static void
120 nap (void) 99 nap (void)
121 { 100 {
122 static struct timespec delay; 101 struct stat old_st;
123 if (!delay.tv_sec && !delay.tv_nsec) 102 static int delay = 1;
103
104 if (-1 == nap_fd)
124 { 105 {
125 int d = guess_delay (); 106 atexit (clear_temp_file);
107 ASSERT ((nap_fd = creat (TEMPFILE, 0600)) != -1);
108 get_stat (nap_fd, &old_st, 0);
109 }
110 else
111 {
112 ASSERT (0 <= nap_fd);
113 get_stat (nap_fd, &old_st, 1);
114 }
126 115
127 /* Multiply by 1.125 (rounding up), to avoid problems if the 116 if (1 < delay)
128 file system's clock is a bit slower than nanosleep's. 117 delay = delay / 2; /* Try half of the previous delay. */
129 Ceiling it at INT_MAX, though. */ 118 ASSERT (0 < delay);
130 int delta = (d >> 3) + ((d & 7) != 0); 119
131 d = delta < INT_MAX - d ? d + delta : INT_MAX; 120 for ( ; delay <= 2147483647; delay = delay * 2)
132 delay.tv_sec = d / 1000000000; 121 if (nap_works (nap_fd, delay, old_st))
133 delay.tv_nsec = d % 1000000000; 122 return;
134 } 123
135 ASSERT (nanosleep (&delay, 0) == 0); 124 /* Bummer: even the highest nap delay didn't work. */
125 ASSERT (0);
136 } 126 }
137 127
138 #endif /* GLTEST_NAP_H */ 128 #endif /* GLTEST_NAP_H */