Skip to content

Commit

Permalink
nbd: fix partial sending
Browse files Browse the repository at this point in the history
nbd driver sends request header and payload with multiple call of
sock_sendmsg, and partial sending can't be avoided. However, nbd driver
returns BLK_STS_RESOURCE to block core in this situation. This way causes
one issue: request->tag may change in the next run of nbd_queue_rq(), but
the original old tag has been sent as part of header cookie, this way
confuses nbd driver reply handling, since the real request can't be
retrieved any more with the obsolete old tag.

Fix it by retrying sending directly in per-socket work function,
meantime return BLK_STS_OK to block layer core.

Cc: vincent.chen@sifive.com
Cc: Leon Schuermann <leon@is.currently.online>
Cc: Bart Van Assche <bvanassche@acm.org>
Reported-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Ming Lei <ming.lei@redhat.com>
Tested-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Link: https://lore.kernel.org/r/20241029011941.153037-1-ming.lei@redhat.com
Signed-off-by: Jens Axboe <axboe@kernel.dk>
  • Loading branch information
Ming Lei authored and Jens Axboe committed Jan 13, 2025
1 parent 7c0be4e commit 8337b02
Showing 1 changed file with 85 additions and 10 deletions.
95 changes: 85 additions & 10 deletions drivers/block/nbd.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ struct nbd_sock {
bool dead;
int fallback_index;
int cookie;
struct work_struct work;
};

struct recv_thread_args {
Expand Down Expand Up @@ -141,6 +142,9 @@ struct nbd_device {
*/
#define NBD_CMD_INFLIGHT 2

/* Just part of request header or data payload is sent successfully */
#define NBD_CMD_PARTIAL_SEND 3

struct nbd_cmd {
struct nbd_device *nbd;
struct mutex lock;
Expand Down Expand Up @@ -453,6 +457,12 @@ static enum blk_eh_timer_return nbd_xmit_timeout(struct request *req)
if (!mutex_trylock(&cmd->lock))
return BLK_EH_RESET_TIMER;

/* partial send is handled in nbd_sock's work function */
if (test_bit(NBD_CMD_PARTIAL_SEND, &cmd->flags)) {
mutex_unlock(&cmd->lock);
return BLK_EH_RESET_TIMER;
}

if (!test_bit(NBD_CMD_INFLIGHT, &cmd->flags)) {
mutex_unlock(&cmd->lock);
return BLK_EH_DONE;
Expand Down Expand Up @@ -601,6 +611,30 @@ static inline int was_interrupted(int result)
return result == -ERESTARTSYS || result == -EINTR;
}

/*
* We've already sent header or part of data payload, have no choice but
* to set pending and schedule it in work.
*
* And we have to return BLK_STS_OK to block core, otherwise this same
* request may be re-dispatched with different tag, but our header has
* been sent out with old tag, and this way does confuse reply handling.
*/
static void nbd_sched_pending_work(struct nbd_device *nbd,
struct nbd_sock *nsock,
struct nbd_cmd *cmd, int sent)
{
struct request *req = blk_mq_rq_from_pdu(cmd);

/* pending work should be scheduled only once */
WARN_ON_ONCE(test_bit(NBD_CMD_PARTIAL_SEND, &cmd->flags));

nsock->pending = req;
nsock->sent = sent;
set_bit(NBD_CMD_PARTIAL_SEND, &cmd->flags);
refcount_inc(&nbd->config_refs);
schedule_work(&nsock->work);
}

/*
* Returns BLK_STS_RESOURCE if the caller should retry after a delay.
* Returns BLK_STS_IOERR if sending failed.
Expand Down Expand Up @@ -686,8 +720,8 @@ static blk_status_t nbd_send_cmd(struct nbd_device *nbd, struct nbd_cmd *cmd,
* completely done.
*/
if (sent) {
nsock->pending = req;
nsock->sent = sent;
nbd_sched_pending_work(nbd, nsock, cmd, sent);
return BLK_STS_OK;
}
set_bit(NBD_CMD_REQUEUED, &cmd->flags);
return BLK_STS_RESOURCE;
Expand Down Expand Up @@ -724,14 +758,8 @@ static blk_status_t nbd_send_cmd(struct nbd_device *nbd, struct nbd_cmd *cmd,
result = sock_xmit(nbd, index, 1, &from, flags, &sent);
if (result < 0) {
if (was_interrupted(result)) {
/* We've already sent the header, we
* have no choice but to set pending and
* return BUSY.
*/
nsock->pending = req;
nsock->sent = sent;
set_bit(NBD_CMD_REQUEUED, &cmd->flags);
return BLK_STS_RESOURCE;
nbd_sched_pending_work(nbd, nsock, cmd, sent);
return BLK_STS_OK;
}
dev_err(disk_to_dev(nbd->disk),
"Send data failed (result %d)\n",
Expand All @@ -757,6 +785,14 @@ static blk_status_t nbd_send_cmd(struct nbd_device *nbd, struct nbd_cmd *cmd,
return BLK_STS_OK;

requeue:
/*
* Can't requeue in case we are dealing with partial send
*
* We must run from pending work function.
* */
if (test_bit(NBD_CMD_PARTIAL_SEND, &cmd->flags))
return BLK_STS_OK;

/* retry on a different socket */
dev_err_ratelimited(disk_to_dev(nbd->disk),
"Request send failed, requeueing\n");
Expand All @@ -765,6 +801,44 @@ static blk_status_t nbd_send_cmd(struct nbd_device *nbd, struct nbd_cmd *cmd,
return BLK_STS_OK;
}

/* handle partial sending */
static void nbd_pending_cmd_work(struct work_struct *work)
{
struct nbd_sock *nsock = container_of(work, struct nbd_sock, work);
struct request *req = nsock->pending;
struct nbd_cmd *cmd = blk_mq_rq_to_pdu(req);
struct nbd_device *nbd = cmd->nbd;
unsigned long deadline = READ_ONCE(req->deadline);
unsigned int wait_ms = 2;

mutex_lock(&cmd->lock);

WARN_ON_ONCE(test_bit(NBD_CMD_REQUEUED, &cmd->flags));
if (WARN_ON_ONCE(!test_bit(NBD_CMD_PARTIAL_SEND, &cmd->flags)))
goto out;

mutex_lock(&nsock->tx_lock);
while (true) {
nbd_send_cmd(nbd, cmd, cmd->index);
if (!nsock->pending)
break;

/* don't bother timeout handler for partial sending */
if (READ_ONCE(jiffies) + msecs_to_jiffies(wait_ms) >= deadline) {
cmd->status = BLK_STS_IOERR;
blk_mq_complete_request(req);
break;
}
msleep(wait_ms);
wait_ms *= 2;
}
mutex_unlock(&nsock->tx_lock);
clear_bit(NBD_CMD_PARTIAL_SEND, &cmd->flags);
out:
mutex_unlock(&cmd->lock);
nbd_config_put(nbd);
}

static int nbd_read_reply(struct nbd_device *nbd, struct socket *sock,
struct nbd_reply *reply)
{
Expand Down Expand Up @@ -1211,6 +1285,7 @@ static int nbd_add_socket(struct nbd_device *nbd, unsigned long arg,
nsock->pending = NULL;
nsock->sent = 0;
nsock->cookie = 0;
INIT_WORK(&nsock->work, nbd_pending_cmd_work);
socks[config->num_connections++] = nsock;
atomic_inc(&config->live_connections);
blk_mq_unfreeze_queue(nbd->disk->queue);
Expand Down

0 comments on commit 8337b02

Please sign in to comment.