iscsi-target: Fix missed wakeup race in TX thread
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> Cc: stable@vger.kernel.org Signed-off-by: Nicholas Bellinger <nab@linux-iscsi.org>
This commit is contained in:
parent
3e03989b58
commit
d5627acba9
|
@ -3719,7 +3719,9 @@ restart:
|
||||||
*/
|
*/
|
||||||
iscsit_thread_check_cpumask(conn, current, 1);
|
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) ||
|
if ((ts->status == ISCSI_THREAD_SET_RESET) ||
|
||||||
signal_pending(current))
|
signal_pending(current))
|
||||||
|
|
|
@ -486,6 +486,7 @@ struct iscsi_tmr_req {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct iscsi_conn {
|
struct iscsi_conn {
|
||||||
|
wait_queue_head_t queues_wq;
|
||||||
/* Authentication Successful for this connection */
|
/* Authentication Successful for this connection */
|
||||||
u8 auth_complete;
|
u8 auth_complete;
|
||||||
/* State connection is currently in */
|
/* State connection is currently in */
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
|
|
||||||
static int iscsi_login_init_conn(struct iscsi_conn *conn)
|
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_list);
|
||||||
INIT_LIST_HEAD(&conn->conn_cmd_list);
|
INIT_LIST_HEAD(&conn->conn_cmd_list);
|
||||||
INIT_LIST_HEAD(&conn->immed_queue_list);
|
INIT_LIST_HEAD(&conn->immed_queue_list);
|
||||||
|
|
|
@ -488,7 +488,7 @@ void iscsit_add_cmd_to_immediate_queue(
|
||||||
atomic_set(&conn->check_immediate_queue, 1);
|
atomic_set(&conn->check_immediate_queue, 1);
|
||||||
spin_unlock_bh(&conn->immed_queue_lock);
|
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)
|
struct iscsi_queue_req *iscsit_get_cmd_from_immediate_queue(struct iscsi_conn *conn)
|
||||||
|
@ -562,7 +562,7 @@ void iscsit_add_cmd_to_response_queue(
|
||||||
atomic_inc(&cmd->response_queue_count);
|
atomic_inc(&cmd->response_queue_count);
|
||||||
spin_unlock_bh(&conn->response_queue_lock);
|
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)
|
struct iscsi_queue_req *iscsit_get_cmd_from_response_queue(struct iscsi_conn *conn)
|
||||||
|
@ -616,6 +616,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)
|
void iscsit_free_queue_reqs_for_conn(struct iscsi_conn *conn)
|
||||||
{
|
{
|
||||||
struct iscsi_queue_req *qr, *qr_tmp;
|
struct iscsi_queue_req *qr, *qr_tmp;
|
||||||
|
|
|
@ -25,6 +25,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 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 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 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_free_queue_reqs_for_conn(struct iscsi_conn *);
|
||||||
extern void iscsit_release_cmd(struct iscsi_cmd *);
|
extern void iscsit_release_cmd(struct iscsi_cmd *);
|
||||||
extern void iscsit_free_cmd(struct iscsi_cmd *);
|
extern void iscsit_free_cmd(struct iscsi_cmd *);
|
||||||
|
|
Loading…
Reference in New Issue