Skip to contentSkip to conversation

block completion races

PATCH Andrew Morton 3 participants 4 messages Jan 16, 2002 Block layer

lore
Share & tools
lore
#0 lore raw

void end_that_request_last(struct request *req)

{
        if (req->waiting != NULL)
                complete(req->waiting);

        blkdev_release_request(req);
}

I think a bug. Sometimes (eg, cdrom_queue_packet_command()) the request is allocated on a task's kernel stack. As soon as we call complete(), that task can wake and release the request while blkdev_release_request() is diddling it on this CPU.

Do you see any problem with releasing the request before running complete()?. Also I think it's best to uninline blkdev_release_request(). It's 104 bytes long, and we have four copies of it in ll_rw_blk.c. A patch is here.

Also, there is this code in ide_do_drive_cmd():

        if (action == ide_wait) {
                wait_for_completion(&wait);     /* wait for it to be serviced */
                return rq->errors ? -EIO : 0;   /* return -EIO if errors */
        }

Is it safe to use `rq' here? It has just been recycled in end_that_request_last() and we don't own it any more.

I think the simplest approach to this one is to make the error code a part of the completion structure, so:

struct blkdev_completion {
	struct completion completion;
	int errcode;
};

If you agree, I'll do the patch.

drivers/block/ll_rw_blk.c +4 -3
@@ -546,7 +546,7 @@ static inline void add_request(request_q
 /*
  * Must be called with io_request_lock held and interrupts disabled
  */
-inline void blkdev_release_request(struct request *req)
+void blkdev_release_request(struct request *req)
 {
 	request_queue_t *q = req->q;
 	int rw = req->cmd;
@@ -1084,10 +1084,11 @@ int end_that_request_first (struct reque
 
 void end_that_request_last(struct request *req)
 {
-	if (req->waiting != NULL)
-		complete(req->waiting);
+	struct completion *waiting = req->waiting;
 
 	blkdev_release_request(req);
+	if (waiting != NULL)
+		complete(waiting);
 }
 
 #define MB(kb)	((kb) << 10)
#1 lore raw

We have a more interesting problem!
This does not show up in 2.5.1 which is patch base for 2.5.3-pre1. It does show up in 2.5.2-pre10 but I have not walked the patch through 2.5.2preX series.

We have a very bad queue race that is PIO specific but really the whole darn driver before the patch was applied. ACB only tighten the driver's alignment to the NCITS standards. One should note the direct access via the ioctl does not lock the driver. Only coming down from BLOCK will this event occur.

Repeatable test "hdparm -d0 -t /dev/hdx"

If you apply the acb-io patch to 2.5.1 this does not happen.

In the introduction of BIO, there were no "q->queue_lock" applied to protecting the queue.

        /*
         * Is meant to protect the queue in the future instead of
         * io_request_lock
         */
        spinlock_t              queue_lock;

Well we pulled "io_request_lock" but did we forgot to insert or add q->queue_lock spinlocks?

It is going to be a LONG LONG NIGHT :-(

Andre Hedrick
Linux Disk Certification Project Linux ATA Development

Quoted 72 lines · Andrew Morton

void end_that_request_last(struct request *req)

{
        if (req->waiting != NULL)
                complete(req->waiting);

        blkdev_release_request(req);
}

I think a bug. Sometimes (eg, cdrom_queue_packet_command()) the request is allocated on a task's kernel stack. As soon as we call complete(), that task can wake and release the request while blkdev_release_request() is diddling it on this CPU.

Do you see any problem with releasing the request before running complete()?. Also I think it's best to uninline blkdev_release_request(). It's 104 bytes long, and we have four copies of it in ll_rw_blk.c. A patch is here.

Also, there is this code in ide_do_drive_cmd():

        if (action == ide_wait) {
                wait_for_completion(&wait);     /* wait for it to be serviced */
                return rq->errors ? -EIO : 0;   /* return -EIO if errors */
        }

Is it safe to use `rq' here? It has just been recycled in end_that_request_last() and we don't own it any more.

I think the simplest approach to this one is to make the error code a part of the completion structure, so:

struct blkdev_completion {
	struct completion completion;
	int errcode;
};

If you agree, I'll do the patch.

drivers/block/ll_rw_blk.c +4 -4
@@ -546,7 +546,7 @@ static inline void add_request(request_q
 /*
  * Must be called with io_request_lock held and interrupts disabled
  */
-inline void blkdev_release_request(struct request *req)
+void blkdev_release_request(struct request *req)
 {
 	request_queue_t *q = req->q;
 	int rw = req->cmd;
@@ -1084,10 +1084,11 @@ int end_that_request_first (struct reque
 
 void end_that_request_last(struct request *req)
 {
-	if (req->waiting != NULL)
-		complete(req->waiting);
+	struct completion *waiting = req->waiting;
 
 	blkdev_release_request(req);
+	if (waiting != NULL)
+		complete(waiting);
 }
 
 #define MB(kb)	((kb) << 10)
-

To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/

#2 lore raw
In reply to Andrew Morton
Quoted 14 lines · Andrew Morton

void end_that_request_last(struct request *req)

{
        if (req->waiting != NULL)
                complete(req->waiting);

        blkdev_release_request(req);
}

I think a bug. Sometimes (eg, cdrom_queue_packet_command()) the request is allocated on a task's kernel stack. As soon as we call complete(), that task can wake and release the request while blkdev_release_request() is diddling it on this CPU.

Quoted 4 lines

Do you see any problem with releasing the request before running complete()?. Also I think it's best to uninline blkdev_release_request(). It's 104 bytes long, and we have four copies of it in ll_rw_blk.c. A patch is here.

Agreed, patch is fine with me.

Quoted 9 lines

Also, there is this code in ide_do_drive_cmd():

        if (action == ide_wait) {
                wait_for_completion(&wait);     /* wait for it to be serviced */
                return rq->errors ? -EIO : 0;   /* return -EIO if errors */
        }

Is it safe to use `rq' here? It has just been recycled in end_that_request_last() and we don't own it any more.

Not really, I guess it would be easiest to make end_that_request_last return errors status.

Quoted 15 lines
drivers/block/ll_rw_blk.c +1 -1
@@ -546,7 +546,7 @@ static inline void add_request(request_q
 /*
  * Must be called with io_request_lock held and interrupts disabled
  */
-inline void blkdev_release_request(struct request *req)
+void blkdev_release_request(struct request *req)
 {
 	request_queue_t *q = req->q;
 	int rw = req->cmd;
@@ -1084,10 +1084,11 @@ int end_that_request_first (struct reque
 
 void end_that_request_last(struct request *req)
 {

drivers/block/ll_rw_blk.c int end_that_request_first (struct reque · line 1094

Quoted 10 lines
drivers/block/ll_rw_blk.c +3 -2
-	if (req->waiting != NULL)
-		complete(req->waiting);
+	struct completion *waiting = req->waiting;
 
 	blkdev_release_request(req);
+	if (waiting != NULL)
+		complete(waiting);
 }
 
 #define MB(kb)	((kb) << 10)

I've applied this.

Signature · 1 line
Jens Axboe
#3 lore raw
In reply to Andrew Morton
Quoted 25 lines · Andre Hedrick

We have a more interesting problem!
This does not show up in 2.5.1 which is patch base for 2.5.3-pre1. It does show up in 2.5.2-pre10 but I have not walked the patch through 2.5.2preX series.

We have a very bad queue race that is PIO specific but really the whole darn driver before the patch was applied. ACB only tighten the driver's alignment to the NCITS standards. One should note the direct access via the ioctl does not lock the driver. Only coming down from BLOCK will this event occur.

Repeatable test "hdparm -d0 -t /dev/hdx"

If you apply the acb-io patch to 2.5.1 this does not happen.

In the introduction of BIO, there were no "q->queue_lock" applied to protecting the queue.

        /*
         * Is meant to protect the queue in the future instead of
         * io_request_lock
         */
        spinlock_t              queue_lock;
Quoted 4 lines

Well we pulled "io_request_lock" but did we forgot to insert or add q->queue_lock spinlocks?

It is going to be a LONG LONG NIGHT :-(

ide_lock == q->queue_lock

ide-probe.c:ide_init_queue(): blk_init_queue(q, do_ide_request, &ide_lock);

Signature · 1 line
Jens Axboe