OSDN Git Service

libparted: avoid race in informing the kernel of partition table changes
authorJim Meyering <meyering@redhat.com>
Fri, 30 Apr 2010 10:28:16 +0000 (12:28 +0200)
committerJim Meyering <meyering@redhat.com>
Fri, 30 Apr 2010 14:20:19 +0000 (16:20 +0200)
When sync'ing a partition table change using the latest
code, sometimes we'd get an unwarranted failure like this:

    Warning: Partition(s) 1 on /dev/sdd have been written, but we
    have been unable to inform the kernel of the change, probably because
    it/they are in use.  As a result, the old partition(s) will remain in
    use.  You should reboot now before making further changes.

To be precise, when running the partition-resizing root-only test
in a loop:

    for i in $(seq 240); do make -C tests check VERBOSE=yes \
    TESTS=t3000-resize-fs.sh >& log.$i && printf . || echo $i $?; done

I would typically see about 50% of them fail on a Fedora 13 system.
It was obvious that this was due to a race condition when I found that
modifying that tests' parted...resize invocation to go via strace changed
the timing enough to make the test pass every time.

The fix is to retry the partition-removal step upon any EBUSY failure,
currently for up to 1 second (retrying up to 100 times, sleeping 10ms
after each failure).

* libparted/arch/linux.c (_disk_sync_part_table): Allocate "ok" using
calloc, now that its initial values matter.
Retry each removal upon EBUSY-failure.
* bootstrap.conf (gnulib_modules): Use gnulib's usleep module.

bootstrap.conf
libparted/arch/linux.c

index 6c9287d..4ca51a7 100644 (file)
@@ -58,6 +58,7 @@ gnulib_modules="
        unlink
        update-copyright
        useless-if-before-free
+       usleep
        vc-list-files
        version-etc-fsf
        warnings
index 8116c76..73a8e6f 100644 (file)
@@ -2452,17 +2452,35 @@ _disk_sync_part_table (PedDisk* disk)
         if (lpn < 0)
                 return 0;
         int ret = 0;
-        int *ok = ped_malloc(sizeof(int) * lpn);
+        int *ok = calloc (lpn, sizeof *ok);
         if (!ok)
                 return 0;
         int *errnums = ped_malloc(sizeof(int) * lpn);
         if (!errnums)
                 goto cleanup;
-        int i;
 
-        for (i = 1; i <= lpn; i++) {
-                ok[i - 1] = _blkpg_remove_partition (disk, i);
-                errnums[i - 1] = errno;
+        /* Attempt to remove each and every partition, retrying for
+           up to max_sleep_seconds upon any failure due to EBUSY. */
+        unsigned int sleep_microseconds = 10000;
+        unsigned int max_sleep_seconds = 1;
+        unsigned int n_sleep = (max_sleep_seconds
+                                * 1000000 / sleep_microseconds);
+        int i;
+        for (i = 0; i < n_sleep; i++) {
+           if (i)
+               usleep (sleep_microseconds);
+            bool busy = false;
+            int j;
+            for (j = 0; j < lpn; j++) {
+                if (!ok[j]) {
+                    ok[j] = _blkpg_remove_partition (disk, j + 1);
+                    errnums[j] = errno;
+                    if (!ok[j] && errnums[j] == EBUSY)
+                        busy = true;
+                }
+            }
+            if (!busy)
+                break;
         }
 
         for (i = 1; i <= lpn; i++) {