diff mbox series

[v2,1/2] scsi: Avoid that .queuecommand() gets called for a quiesced SCSI device

Message ID 20190402161624.148359-2-bvanassche@acm.org (mailing list archive)
State Changes Requested
Headers show
Series Avoid that .queuecommand() gets called for a quiesced SCSI device | expand

Commit Message

Bart Van Assche April 2, 2019, 4:16 p.m. UTC
Several SCSI transport and LLD drivers surround code that does not
tolerate concurrent calls of .queuecommand() with scsi_target_block() /
scsi_target_unblock(). These last two functions use
blk_mq_quiesce_queue() / blk_mq_unquiesce_queue() for scsi-mq request
queues to prevent concurrent .queuecommand() calls. However, that is
not sufficient to prevent .queuecommand() calls from scsi_send_eh_cmnd().
Hence surround the .queuecommand() call from the SCSI error handler with
code that avoids that .queuecommand() gets called in the quiesced state.

Note: converting the .queuecommand() call in scsi_send_eh_cmnd() into
code that calls blk_get_request() + blk_execute_rq() is not an option
since scsi_send_eh_cmnd() must be able to make forward progress even
if all requests have been allocated.

Cc: Christoph Hellwig <hch@lst.de>
Cc: Ming Lei <ming.lei@redhat.com>
Cc: Hannes Reinecke <hare@suse.de>
Cc: Johannes Thumshirn <jthumshirn@suse.de>
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 drivers/scsi/scsi_error.c | 26 ++++++++++++++++++++++++--
 1 file changed, 24 insertions(+), 2 deletions(-)

Comments

Ming Lei April 3, 2019, 4:15 a.m. UTC | #1
On Tue, Apr 02, 2019 at 09:16:23AM -0700, Bart Van Assche wrote:
> Several SCSI transport and LLD drivers surround code that does not
> tolerate concurrent calls of .queuecommand() with scsi_target_block() /
> scsi_target_unblock(). These last two functions use
> blk_mq_quiesce_queue() / blk_mq_unquiesce_queue() for scsi-mq request
> queues to prevent concurrent .queuecommand() calls. However, that is
> not sufficient to prevent .queuecommand() calls from scsi_send_eh_cmnd().
> Hence surround the .queuecommand() call from the SCSI error handler with
> code that avoids that .queuecommand() gets called in the quiesced state.
> 
> Note: converting the .queuecommand() call in scsi_send_eh_cmnd() into
> code that calls blk_get_request() + blk_execute_rq() is not an option
> since scsi_send_eh_cmnd() must be able to make forward progress even
> if all requests have been allocated.
> 
> Cc: Christoph Hellwig <hch@lst.de>
> Cc: Ming Lei <ming.lei@redhat.com>
> Cc: Hannes Reinecke <hare@suse.de>
> Cc: Johannes Thumshirn <jthumshirn@suse.de>
> Signed-off-by: Bart Van Assche <bvanassche@acm.org>
> ---
>  drivers/scsi/scsi_error.c | 26 ++++++++++++++++++++++++--
>  1 file changed, 24 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c
> index 8e9680572b9f..d516dd1b824d 100644
> --- a/drivers/scsi/scsi_error.c
> +++ b/drivers/scsi/scsi_error.c
> @@ -1054,7 +1054,7 @@ static int scsi_send_eh_cmnd(struct scsi_cmnd *scmd, unsigned char *cmnd,
>  	struct scsi_device *sdev = scmd->device;
>  	struct Scsi_Host *shost = sdev->host;
>  	DECLARE_COMPLETION_ONSTACK(done);
> -	unsigned long timeleft = timeout;
> +	unsigned long timeleft = timeout, delay;
>  	struct scsi_eh_save ses;
>  	const unsigned long stall_for = msecs_to_jiffies(100);
>  	int rtn;
> @@ -1065,7 +1065,29 @@ static int scsi_send_eh_cmnd(struct scsi_cmnd *scmd, unsigned char *cmnd,
>  
>  	scsi_log_send(scmd);
>  	scmd->scsi_done = scsi_eh_done;
> -	rtn = shost->hostt->queuecommand(shost, scmd);
> +
> +	/*
> +	 * Lock sdev->state_mutex to avoid that scsi_device_quiesce() can
> +	 * change the SCSI device state after we have examined it and before
> +	 * .queuecommand() is called.
> +	 */
> +	mutex_lock(&sdev->state_mutex);
> +	while (sdev->sdev_state == SDEV_BLOCK && timeleft > 0) {
> +		mutex_unlock(&sdev->state_mutex);
> +		SCSI_LOG_ERROR_RECOVERY(5, sdev_printk(KERN_DEBUG, sdev,
> +			"%s: state %d <> %d\n", __func__, sdev->sdev_state,
> +			SDEV_BLOCK));
> +		delay = min(timeleft, stall_for);
> +		timeleft -= delay;
> +		msleep(jiffies_to_msecs(delay));
> +		mutex_lock(&sdev->state_mutex);
> +	}
> +	if (sdev->sdev_state != SDEV_BLOCK)
> +		rtn = shost->hostt->queuecommand(shost, scmd);
> +	else
> +		rtn = SCSI_MLQUEUE_DEVICE_BUSY;
> +	mutex_unlock(&sdev->state_mutex);
> +
>  	if (rtn) {
>  		if (timeleft > stall_for) {
>  			scsi_eh_restore_cmnd(scmd, &ses);
> -- 
> 2.21.0.196.g041f5ea1cf98
> 

Still not sure if it is safe to do that in case of SDEV_BLOCK.

SDEV_BLOCK can be set via sysfs interface, then what if there are
in-flight IOs which need EH to retry just after someone sets 'blocked'?

People may complain this patch causes data loss on above test case.


Thanks,
Ming
Bart Van Assche April 3, 2019, 4:06 p.m. UTC | #2
On Wed, 2019-04-03 at 12:15 +0800, Ming Lei wrote:
> Still not sure if it is safe to do that in case of SDEV_BLOCK.
> 
> SDEV_BLOCK can be set via sysfs interface, then what if there are
> in-flight IOs which need EH to retry just after someone sets 'blocked'?
> 
> People may complain this patch causes data loss on above test case.

Hi Ming,

scsi_send_eh_cmnd() is only used to request sense data, to submit a TUR or
to submit a START UNIT. None of these commands modify the data stored on
the SCSI device so there is no risk of data loss.

The ability to modify the SCSI device state was introduced by commit
638127e579a4 ("[PATCH] Fix error handler offline behaviour"; v2.6.12). That
same commit introduced the following device states:

       { SDEV_CREATED, "created" },
       { SDEV_RUNNING, "running" },
       { SDEV_CANCEL,  "cancel"  },
       { SDEV_DEL,     "deleted" },
       { SDEV_QUIESCE, "quiesce" },
       { SDEV_OFFLINE, "offline" },

The SDEV_BLOCK state was introduced later to avoid that an FC cable pull
would immediately result in an I/O error (commit 1094e682310e; "[PATCH]
suspending I/Os to a device"; v2.6.12). I'm not sure whether the ability to
set the SDEV_BLOCK state from user space was introduced on purpose or
accidentally.

I think there are three alternatives:
(1) Accept that some error handling steps are skipped if a user sets the
    device state to "blocked".
(2) Prevent users to change the device state to "blocked".
(3) Split SDEV_BLOCK into SDEV_BLOCKED_BY_USER and SDEV_BLOCKED_BY_TRANSPORT
    and only skip sending EH commands to the device in state
    SDEV_BLOCKED_BY_TRANSPORT.

Thanks,

Bart.
Bart Van Assche April 9, 2019, 5:27 p.m. UTC | #3
On Wed, 2019-04-03 at 09:06 -0700, Bart Van Assche wrote:
> scsi_send_eh_cmnd() is only used to request sense data, to submit a TUR or
> to submit a START UNIT. None of these commands modify the data stored on
> the SCSI device so there is no risk of data loss.
> 
> The ability to modify the SCSI device state was introduced by commit
> 638127e579a4 ("[PATCH] Fix error handler offline behaviour"; v2.6.12). That
> same commit introduced the following device states:
> 
>        { SDEV_CREATED, "created" },
>        { SDEV_RUNNING, "running" },
>        { SDEV_CANCEL,  "cancel"  },
>        { SDEV_DEL,     "deleted" },
>        { SDEV_QUIESCE, "quiesce" },
>        { SDEV_OFFLINE, "offline" },
> 
> The SDEV_BLOCK state was introduced later to avoid that an FC cable pull
> would immediately result in an I/O error (commit 1094e682310e; "[PATCH]
> suspending I/Os to a device"; v2.6.12). I'm not sure whether the ability to
> set the SDEV_BLOCK state from user space was introduced on purpose or
> accidentally.
> 
> I think there are three alternatives:
> (1) Accept that some error handling steps are skipped if a user sets the
>     device state to "blocked".
> (2) Prevent users to change the device state to "blocked".
> (3) Split SDEV_BLOCK into SDEV_BLOCKED_BY_USER and SDEV_BLOCKED_BY_TRANSPORT
>     and only skip sending EH commands to the device in state
>     SDEV_BLOCKED_BY_TRANSPORT.

(repyling to my own e-mail)

Does anyone want to share an opinion about the above?

Thanks,

Bart.
Martin K. Petersen April 10, 2019, 2:14 a.m. UTC | #4
Bart,

>> (1) Accept that some error handling steps are skipped if a user sets the
>>     device state to "blocked".
>> (2) Prevent users to change the device state to "blocked".

We can't really remove an exposed interface. So at the very least the
string "blocked" would have to trigger something semantically
close. Quiesce, maybe?

>> (3) Split SDEV_BLOCK into SDEV_BLOCKED_BY_USER and
>> SDEV_BLOCKED_BY_TRANSPORT and only skip sending EH commands to the
>> device in state SDEV_BLOCKED_BY_TRANSPORT.
diff mbox series

Patch

diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c
index 8e9680572b9f..d516dd1b824d 100644
--- a/drivers/scsi/scsi_error.c
+++ b/drivers/scsi/scsi_error.c
@@ -1054,7 +1054,7 @@  static int scsi_send_eh_cmnd(struct scsi_cmnd *scmd, unsigned char *cmnd,
 	struct scsi_device *sdev = scmd->device;
 	struct Scsi_Host *shost = sdev->host;
 	DECLARE_COMPLETION_ONSTACK(done);
-	unsigned long timeleft = timeout;
+	unsigned long timeleft = timeout, delay;
 	struct scsi_eh_save ses;
 	const unsigned long stall_for = msecs_to_jiffies(100);
 	int rtn;
@@ -1065,7 +1065,29 @@  static int scsi_send_eh_cmnd(struct scsi_cmnd *scmd, unsigned char *cmnd,
 
 	scsi_log_send(scmd);
 	scmd->scsi_done = scsi_eh_done;
-	rtn = shost->hostt->queuecommand(shost, scmd);
+
+	/*
+	 * Lock sdev->state_mutex to avoid that scsi_device_quiesce() can
+	 * change the SCSI device state after we have examined it and before
+	 * .queuecommand() is called.
+	 */
+	mutex_lock(&sdev->state_mutex);
+	while (sdev->sdev_state == SDEV_BLOCK && timeleft > 0) {
+		mutex_unlock(&sdev->state_mutex);
+		SCSI_LOG_ERROR_RECOVERY(5, sdev_printk(KERN_DEBUG, sdev,
+			"%s: state %d <> %d\n", __func__, sdev->sdev_state,
+			SDEV_BLOCK));
+		delay = min(timeleft, stall_for);
+		timeleft -= delay;
+		msleep(jiffies_to_msecs(delay));
+		mutex_lock(&sdev->state_mutex);
+	}
+	if (sdev->sdev_state != SDEV_BLOCK)
+		rtn = shost->hostt->queuecommand(shost, scmd);
+	else
+		rtn = SCSI_MLQUEUE_DEVICE_BUSY;
+	mutex_unlock(&sdev->state_mutex);
+
 	if (rtn) {
 		if (timeleft > stall_for) {
 			scsi_eh_restore_cmnd(scmd, &ses);