dm snapshot: fix race during exception creation
authorMikulas Patocka <mpatocka@redhat.com>
Mon, 21 Jul 2008 11:00:34 +0000 (12:00 +0100)
committerAlasdair G Kergon <agk@redhat.com>
Mon, 21 Jul 2008 11:00:34 +0000 (12:00 +0100)
Fix a race condition that returns incorrect data when a write causes an
exception to be allocated whilst a read is still in flight.

The race condition happens as follows:
* A read to non-reallocated sector in the snapshot is submitted so that the
  read is routed to the original device.
* A write to the original device is submitted. The write causes an exception
  that reallocates the block.  The write proceeds.
* The original read is dequeued and reads the wrong data.

This race can be triggered with CFQ scheduler and one thread writing and
multiple threads reading simultaneously.

(This patch relies upon the earlier dm-kcopyd-per-device.patch to avoid a
deadlock.)

Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
Signed-off-by: Alasdair G Kergon <agk@redhat.com>
drivers/md/dm-snap.c

index de302702ab3e23b9dc95b0dee56b786d74a3365e..f4fd0cee9c3d43f1c06ec288af0d7476bf4f4538 100644 (file)
@@ -134,6 +134,27 @@ static void stop_tracking_chunk(struct dm_snapshot *s,
        mempool_free(c, s->tracked_chunk_pool);
 }
 
+static int __chunk_is_tracked(struct dm_snapshot *s, chunk_t chunk)
+{
+       struct dm_snap_tracked_chunk *c;
+       struct hlist_node *hn;
+       int found = 0;
+
+       spin_lock_irq(&s->tracked_chunk_lock);
+
+       hlist_for_each_entry(c, hn,
+           &s->tracked_chunk_hash[DM_TRACKED_CHUNK_HASH(chunk)], node) {
+               if (c->chunk == chunk) {
+                       found = 1;
+                       break;
+               }
+       }
+
+       spin_unlock_irq(&s->tracked_chunk_lock);
+
+       return found;
+}
+
 /*
  * One of these per registered origin, held in the snapshot_origins hash
  */
@@ -839,6 +860,13 @@ static void pending_complete(struct dm_snap_pending_exception *pe, int success)
                goto out;
        }
 
+       /*
+        * Check for conflicting reads. This is extremely improbable,
+        * so yield() is sufficient and there is no need for a wait queue.
+        */
+       while (__chunk_is_tracked(s, pe->e.old_chunk))
+               yield();
+
        /*
         * Add a proper exception, and remove the
         * in-flight exception from the list.