diff options
author | Roland Dreier <roland@purestorage.com> | 2012-10-31 09:16:46 -0700 |
---|---|---|
committer | Ben Hutchings <ben@decadent.org.uk> | 2012-11-16 16:47:07 +0000 |
commit | 620cc1df7f260855bfa05f56483ced4be702d57b (patch) | |
tree | fbb17d6850329576ad7976eed77ef4ae61d6eb9e | |
parent | ca8b38a5d0489f39245404acea9a1524b29d1cbb (diff) | |
download | linux-stable-620cc1df7f260855bfa05f56483ced4be702d57b.tar.gz linux-stable-620cc1df7f260855bfa05f56483ced4be702d57b.tar.bz2 linux-stable-620cc1df7f260855bfa05f56483ced4be702d57b.zip |
iscsi-target: Fix missed wakeup race in TX thread
commit d5627acba9ae584cf4928af19f7ddf5f6837de32 upstream.
The sleeping code in iscsi_target_tx_thread() is susceptible to the classic
missed wakeup race:
- TX thread finishes handle_immediate_queue() and handle_response_queue(),
thinks both queues are empty.
- Another thread adds a queue entry and does wake_up_process(), which does
nothing because the TX thread is still awake.
- TX thread does schedule_timeout() and sleeps forever.
In practice this can kill an iSCSI connection if for example an initiator
does single-threaded writes and the target misses the wakeup window when
queueing an R2T; in this case the connection will be stuck until the
initiator loses patience and does some task management operation (or kills
the connection entirely).
Fix this by converting to wait_event_interruptible(), which does not
suffer from this sort of race.
Signed-off-by: Roland Dreier <roland@purestorage.com>
Cc: Andy Grover <agrover@redhat.com>
Cc: Hannes Reinecke <hare@suse.de>
Cc: Christoph Hellwig <hch@lst.de>
Signed-off-by: Nicholas Bellinger <nab@linux-iscsi.org>
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
-rw-r--r-- | drivers/target/iscsi/iscsi_target.c | 4 | ||||
-rw-r--r-- | drivers/target/iscsi/iscsi_target_core.h | 1 | ||||
-rw-r--r-- | drivers/target/iscsi/iscsi_target_login.c | 1 | ||||
-rw-r--r-- | drivers/target/iscsi/iscsi_target_util.c | 22 | ||||
-rw-r--r-- | drivers/target/iscsi/iscsi_target_util.h | 1 |
5 files changed, 26 insertions, 3 deletions
diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c index f35cb100764e..6fa72220bbea 100644 --- a/drivers/target/iscsi/iscsi_target.c +++ b/drivers/target/iscsi/iscsi_target.c @@ -3523,7 +3523,9 @@ restart: */ iscsit_thread_check_cpumask(conn, current, 1); - schedule_timeout_interruptible(MAX_SCHEDULE_TIMEOUT); + wait_event_interruptible(conn->queues_wq, + !iscsit_conn_all_queues_empty(conn) || + ts->status == ISCSI_THREAD_SET_RESET); if ((ts->status == ISCSI_THREAD_SET_RESET) || signal_pending(current)) diff --git a/drivers/target/iscsi/iscsi_target_core.h b/drivers/target/iscsi/iscsi_target_core.h index dae283fd37e2..bd8ce01712ab 100644 --- a/drivers/target/iscsi/iscsi_target_core.h +++ b/drivers/target/iscsi/iscsi_target_core.h @@ -491,6 +491,7 @@ struct iscsi_tmr_req { }; struct iscsi_conn { + wait_queue_head_t queues_wq; /* Authentication Successful for this connection */ u8 auth_complete; /* State connection is currently in */ diff --git a/drivers/target/iscsi/iscsi_target_login.c b/drivers/target/iscsi/iscsi_target_login.c index 2ec533938181..eb0c9feea786 100644 --- a/drivers/target/iscsi/iscsi_target_login.c +++ b/drivers/target/iscsi/iscsi_target_login.c @@ -44,6 +44,7 @@ extern spinlock_t sess_idr_lock; static int iscsi_login_init_conn(struct iscsi_conn *conn) { + init_waitqueue_head(&conn->queues_wq); INIT_LIST_HEAD(&conn->conn_list); INIT_LIST_HEAD(&conn->conn_cmd_list); INIT_LIST_HEAD(&conn->immed_queue_list); diff --git a/drivers/target/iscsi/iscsi_target_util.c b/drivers/target/iscsi/iscsi_target_util.c index 99f2af3cf5a7..e6127227be44 100644 --- a/drivers/target/iscsi/iscsi_target_util.c +++ b/drivers/target/iscsi/iscsi_target_util.c @@ -659,7 +659,7 @@ void iscsit_add_cmd_to_immediate_queue( atomic_set(&conn->check_immediate_queue, 1); spin_unlock_bh(&conn->immed_queue_lock); - wake_up_process(conn->thread_set->tx_thread); + wake_up(&conn->queues_wq); } struct iscsi_queue_req *iscsit_get_cmd_from_immediate_queue(struct iscsi_conn *conn) @@ -733,7 +733,7 @@ void iscsit_add_cmd_to_response_queue( atomic_inc(&cmd->response_queue_count); spin_unlock_bh(&conn->response_queue_lock); - wake_up_process(conn->thread_set->tx_thread); + wake_up(&conn->queues_wq); } struct iscsi_queue_req *iscsit_get_cmd_from_response_queue(struct iscsi_conn *conn) @@ -787,6 +787,24 @@ static void iscsit_remove_cmd_from_response_queue( } } +bool iscsit_conn_all_queues_empty(struct iscsi_conn *conn) +{ + bool empty; + + spin_lock_bh(&conn->immed_queue_lock); + empty = list_empty(&conn->immed_queue_list); + spin_unlock_bh(&conn->immed_queue_lock); + + if (!empty) + return empty; + + spin_lock_bh(&conn->response_queue_lock); + empty = list_empty(&conn->response_queue_list); + spin_unlock_bh(&conn->response_queue_lock); + + return empty; +} + void iscsit_free_queue_reqs_for_conn(struct iscsi_conn *conn) { struct iscsi_queue_req *qr, *qr_tmp; diff --git a/drivers/target/iscsi/iscsi_target_util.h b/drivers/target/iscsi/iscsi_target_util.h index 835bf7de0281..cfac698fb3cb 100644 --- a/drivers/target/iscsi/iscsi_target_util.h +++ b/drivers/target/iscsi/iscsi_target_util.h @@ -28,6 +28,7 @@ extern struct iscsi_queue_req *iscsit_get_cmd_from_immediate_queue(struct iscsi_ extern void iscsit_add_cmd_to_response_queue(struct iscsi_cmd *, struct iscsi_conn *, u8); extern struct iscsi_queue_req *iscsit_get_cmd_from_response_queue(struct iscsi_conn *); extern void iscsit_remove_cmd_from_tx_queues(struct iscsi_cmd *, struct iscsi_conn *); +extern bool iscsit_conn_all_queues_empty(struct iscsi_conn *); extern void iscsit_free_queue_reqs_for_conn(struct iscsi_conn *); extern void iscsit_release_cmd(struct iscsi_cmd *); extern void iscsit_free_cmd(struct iscsi_cmd *); |