[PATCH-v2 0/3] iscsi-target: Misc thread_set improvements + cleanups

14 views
Skip to first unread message

Nicholas A. Bellinger

unread,
Sep 10, 2013, 7:47:57 PM9/10/13
to target-devel, Nicholas Bellinger
From: Nicholas Bellinger <n...@linux-iscsi.org>

Hi folks,

This -v2 is a updated set of miscellaneous improvements to iscsi-target
thread_set code to reduce latency for handling 100's of simultaneous
logins, that goes along with iscsi-target login multi-plexing logic
in-flight for v3.12-rc1.

The first patch fixes a long-standing race between rx/tx pre handlers
use of flush_signals(), and iscsi_deallocate_extra_thread_sets() setting
of ISCSI_THREAD_SET_DIE before calling kthread_stop().

The second patch removes the iscsi_thread_set->[rx,tx]_post_start_comp
in favor of a single ->ts_activate_sem in order to save extra context
switches for each iscsi_activate_thread_set() call. It also refactors
duplicated thread_set dellocate logic into common code.

And the third patch removes an unnecessary wait_for_completion() within
iscsi_get_thread_set(), that originally would wait an extra second when
no thread_set pair was available.

Changes for v2:

- Add explicit complete(&ts->[rx,tx]_start_comp); before kthread_stop() in
iscsi_deallocate_extra_thread_sets()
- Drop left-over send_sig() calls in iscsi_deallocate_extra_thread_sets()
- Add kthread_should_stop() check in iscsi_signal_thread_pre_handler()
- Set ISCSI_THREAD_SET_ACTIVE before calling complete in
iscsi_activate_thread_set
- Protect ts->conn sanity checks with ->ts_state_lock in
RX/TX pre handlers
- Add ->ts_activate_sem to save extra context switches per
iscsi_activate_thread_set() call
- Refactor thread_set shutdown into iscsi_deallocate_thread_one()

Thanks,

--nab

Nicholas Bellinger (3):
iscsi-target: Fix race with thread_pre_handler flush_signals +
ISCSI_THREAD_SET_DIE
iscsi-target: Add thread_set->ts_activate_sem + use common deallocate
iscsi-target: Remove unnecessary wait_for_completion in
iscsi_get_thread_set

drivers/target/iscsi/iscsi_target_tq.c | 163 +++++++++++++-------------------
drivers/target/iscsi/iscsi_target_tq.h | 5 +-
2 files changed, 68 insertions(+), 100 deletions(-)

--
1.7.2.5

Nicholas A. Bellinger

unread,
Sep 10, 2013, 7:47:58 PM9/10/13
to target-devel, Nicholas Bellinger
From: Nicholas Bellinger <n...@linux-iscsi.org>

This patch addresses an long standing race in iscsi_[rx,tx]_thread_pre_handler()
use of flush_signals(), and between iscsi_deallocate_extra_thread_sets() setting
ISCSI_THREAD_SET_DIE before calling kthread_stop().

It addresses the issue by both holding ts_state_lock before calling send_sig()
in iscsi_deallocate_extra_thread_sets(), as well as only calling flush_signals()
when ts->status != ISCSI_THREAD_SET_DIE within iscsi_[rx,tx]_thread_pre_handler()
code.

v2 changes:
- Add explicit complete(&ts->[rx,tx]_start_comp); before kthread_stop() in
iscsi_deallocate_extra_thread_sets()
- Drop left-over send_sig() calls in iscsi_deallocate_extra_thread_sets()
- Add kthread_should_stop() check in iscsi_signal_thread_pre_handler()

Signed-off-by: Nicholas Bellinger <n...@linux-iscsi.org>
---
drivers/target/iscsi/iscsi_target_tq.c | 19 +++++++++++++------
1 files changed, 13 insertions(+), 6 deletions(-)

diff --git a/drivers/target/iscsi/iscsi_target_tq.c b/drivers/target/iscsi/iscsi_target_tq.c
index 8128952..1a5bbec 100644
--- a/drivers/target/iscsi/iscsi_target_tq.c
+++ b/drivers/target/iscsi/iscsi_target_tq.c
@@ -189,16 +189,20 @@ static void iscsi_deallocate_extra_thread_sets(void)

spin_lock_bh(&ts->ts_state_lock);
ts->status = ISCSI_THREAD_SET_DIE;
- spin_unlock_bh(&ts->ts_state_lock);

if (ts->rx_thread) {
- send_sig(SIGINT, ts->rx_thread, 1);
+ complete(&ts->rx_start_comp);
+ spin_unlock_bh(&ts->ts_state_lock);
kthread_stop(ts->rx_thread);
+ spin_lock_bh(&ts->ts_state_lock);
}
if (ts->tx_thread) {
- send_sig(SIGINT, ts->tx_thread, 1);
+ complete(&ts->tx_start_comp);
+ spin_unlock_bh(&ts->ts_state_lock);
kthread_stop(ts->tx_thread);
+ spin_lock_bh(&ts->ts_state_lock);
}
+ spin_unlock_bh(&ts->ts_state_lock);
/*
* Release this thread_id in the thread_set_bitmap
*/
@@ -400,7 +404,8 @@ static void iscsi_check_to_add_additional_sets(void)
static int iscsi_signal_thread_pre_handler(struct iscsi_thread_set *ts)
{
spin_lock_bh(&ts->ts_state_lock);
- if ((ts->status == ISCSI_THREAD_SET_DIE) || signal_pending(current)) {
+ if (ts->status == ISCSI_THREAD_SET_DIE || kthread_should_stop() ||
+ signal_pending(current)) {
spin_unlock_bh(&ts->ts_state_lock);
return -1;
}
@@ -419,7 +424,8 @@ struct iscsi_conn *iscsi_rx_thread_pre_handler(struct iscsi_thread_set *ts)
goto sleep;
}

- flush_signals(current);
+ if (ts->status != ISCSI_THREAD_SET_DIE)
+ flush_signals(current);

if (ts->delay_inactive && (--ts->thread_count == 0)) {
spin_unlock_bh(&ts->ts_state_lock);
@@ -472,7 +478,8 @@ struct iscsi_conn *iscsi_tx_thread_pre_handler(struct iscsi_thread_set *ts)
goto sleep;
}

- flush_signals(current);
+ if (ts->status != ISCSI_THREAD_SET_DIE)
+ flush_signals(current);

if (ts->delay_inactive && (--ts->thread_count == 0)) {
spin_unlock_bh(&ts->ts_state_lock);
--
1.7.2.5

Nicholas A. Bellinger

unread,
Sep 10, 2013, 7:47:59 PM9/10/13
to target-devel, Nicholas Bellinger
From: Nicholas Bellinger <n...@linux-iscsi.org>

This patch removes the iscsi_thread_set->[rx,tx]_post_start_comp that
was originally used synchronize startup between rx and tx threads within
a single thread_set.

Instead, use a single ->ts_activate_sem in iscsi_activate_thread_set()
to wait for both processes to awake in the RX/TX pre handlers.

Also, go ahead and refactor thread_set deallocate code into a common
iscsi_deallocate_thread_one(), and update iscsi_deallocate_thread_sets()
and iscsi_deallocate_extra_thread_sets() use this code

v2 changes:
- Set ISCSI_THREAD_SET_ACTIVE before calling complete in
iscsi_activate_thread_set
- Protect ts->conn sanity checks with ->ts_state_lock in
RX/TX pre handlers
- Add ->ts_activate_sem to save extra context switches per
iscsi_activate_thread_set() call.
- Refactor thread_set shutdown into iscsi_deallocate_thread_one()

Signed-off-by: Nicholas Bellinger <n...@linux-iscsi.org>
---
drivers/target/iscsi/iscsi_target_tq.c | 139 +++++++++++++------------------
drivers/target/iscsi/iscsi_target_tq.h | 5 +-
2 files changed, 59 insertions(+), 85 deletions(-)

diff --git a/drivers/target/iscsi/iscsi_target_tq.c b/drivers/target/iscsi/iscsi_target_tq.c
index 1a5bbec..09aae3d 100644
--- a/drivers/target/iscsi/iscsi_target_tq.c
+++ b/drivers/target/iscsi/iscsi_target_tq.c
@@ -105,12 +105,11 @@ int iscsi_allocate_thread_sets(u32 thread_pair_count)
ts->status = ISCSI_THREAD_SET_FREE;
INIT_LIST_HEAD(&ts->ts_list);
spin_lock_init(&ts->ts_state_lock);
- init_completion(&ts->rx_post_start_comp);
- init_completion(&ts->tx_post_start_comp);
init_completion(&ts->rx_restart_comp);
init_completion(&ts->tx_restart_comp);
init_completion(&ts->rx_start_comp);
init_completion(&ts->tx_start_comp);
+ sema_init(&ts->ts_activate_sem, 0);

ts->create_threads = 1;
ts->tx_thread = kthread_run(iscsi_target_tx_thread, ts, "%s",
@@ -139,35 +138,44 @@ int iscsi_allocate_thread_sets(u32 thread_pair_count)
return allocated_thread_pair_count;
}

-void iscsi_deallocate_thread_sets(void)
+void iscsi_deallocate_thread_one(struct iscsi_thread_set *ts)
{
- u32 released_count = 0;
- struct iscsi_thread_set *ts = NULL;
-
- while ((ts = iscsi_get_ts_from_inactive_list())) {
+ spin_lock_bh(&ts->ts_state_lock);
+ ts->status = ISCSI_THREAD_SET_DIE;

+ if (ts->rx_thread) {
+ complete(&ts->rx_start_comp);
+ spin_unlock_bh(&ts->ts_state_lock);
+ kthread_stop(ts->rx_thread);
spin_lock_bh(&ts->ts_state_lock);
- ts->status = ISCSI_THREAD_SET_DIE;
+ }
+ if (ts->tx_thread) {
+ complete(&ts->tx_start_comp);
spin_unlock_bh(&ts->ts_state_lock);
+ kthread_stop(ts->tx_thread);
+ spin_lock_bh(&ts->ts_state_lock);
+ }
+ spin_unlock_bh(&ts->ts_state_lock);
+ /*
+ * Release this thread_id in the thread_set_bitmap
+ */
+ spin_lock(&ts_bitmap_lock);
+ bitmap_release_region(iscsit_global->ts_bitmap,
+ ts->thread_id, get_order(1));
+ spin_unlock(&ts_bitmap_lock);

- if (ts->rx_thread) {
- send_sig(SIGINT, ts->rx_thread, 1);
- kthread_stop(ts->rx_thread);
- }
- if (ts->tx_thread) {
- send_sig(SIGINT, ts->tx_thread, 1);
- kthread_stop(ts->tx_thread);
- }
- /*
- * Release this thread_id in the thread_set_bitmap
- */
- spin_lock(&ts_bitmap_lock);
- bitmap_release_region(iscsit_global->ts_bitmap,
- ts->thread_id, get_order(1));
- spin_unlock(&ts_bitmap_lock);
+ kfree(ts);
+}
+
+void iscsi_deallocate_thread_sets(void)
+{
+ struct iscsi_thread_set *ts = NULL;
+ u32 released_count = 0;

+ while ((ts = iscsi_get_ts_from_inactive_list())) {
+
+ iscsi_deallocate_thread_one(ts);
released_count++;
- kfree(ts);
}

if (released_count)
@@ -187,38 +195,13 @@ static void iscsi_deallocate_extra_thread_sets(void)
if (!ts)
break;

- spin_lock_bh(&ts->ts_state_lock);
- ts->status = ISCSI_THREAD_SET_DIE;
-
- if (ts->rx_thread) {
- complete(&ts->rx_start_comp);
- spin_unlock_bh(&ts->ts_state_lock);
- kthread_stop(ts->rx_thread);
- spin_lock_bh(&ts->ts_state_lock);
- }
- if (ts->tx_thread) {
- complete(&ts->tx_start_comp);
- spin_unlock_bh(&ts->ts_state_lock);
- kthread_stop(ts->tx_thread);
- spin_lock_bh(&ts->ts_state_lock);
- }
- spin_unlock_bh(&ts->ts_state_lock);
- /*
- * Release this thread_id in the thread_set_bitmap
- */
- spin_lock(&ts_bitmap_lock);
- bitmap_release_region(iscsit_global->ts_bitmap,
- ts->thread_id, get_order(1));
- spin_unlock(&ts_bitmap_lock);
-
+ iscsi_deallocate_thread_one(ts);
released_count++;
- kfree(ts);
}

- if (released_count) {
+ if (released_count)
pr_debug("Stopped %d thread set(s) (%d total threads)."
"\n", released_count, released_count * 2);
- }
}

void iscsi_activate_thread_set(struct iscsi_conn *conn, struct iscsi_thread_set *ts)
@@ -228,14 +211,13 @@ void iscsi_activate_thread_set(struct iscsi_conn *conn, struct iscsi_thread_set
spin_lock_bh(&ts->ts_state_lock);
conn->thread_set = ts;
ts->conn = conn;
+ ts->status = ISCSI_THREAD_SET_ACTIVE;
spin_unlock_bh(&ts->ts_state_lock);
- /*
- * Start up the RX thread and wait on rx_post_start_comp. The RX
- * Thread will then do the same for the TX Thread in
- * iscsi_rx_thread_pre_handler().
- */
+
complete(&ts->rx_start_comp);
- wait_for_completion(&ts->rx_post_start_comp);
+ complete(&ts->tx_start_comp);
+
+ down(&ts->ts_activate_sem);
}

struct iscsi_thread_set *iscsi_get_thread_set(void)
@@ -267,6 +249,7 @@ get_set:
ts->thread_count = 2;
init_completion(&ts->rx_restart_comp);
init_completion(&ts->tx_restart_comp);
+ sema_init(&ts->ts_activate_sem, 0);

return ts;
}
@@ -452,18 +435,19 @@ sleep:
if (iscsi_signal_thread_pre_handler(ts) < 0)
return NULL;

+ iscsi_check_to_add_additional_sets();
+
+ spin_lock_bh(&ts->ts_state_lock);
if (!ts->conn) {
pr_err("struct iscsi_thread_set->conn is NULL for"
- " thread_id: %d, going back to sleep\n", ts->thread_id);
- goto sleep;
+ " RX thread_id: %s/%d\n", current->comm, current->pid);
+ spin_unlock_bh(&ts->ts_state_lock);
+ return NULL;
}
- iscsi_check_to_add_additional_sets();
- /*
- * The RX Thread starts up the TX Thread and sleeps.
- */
ts->thread_clear |= ISCSI_CLEAR_RX_THREAD;
- complete(&ts->tx_start_comp);
- wait_for_completion(&ts->tx_post_start_comp);
+ spin_unlock_bh(&ts->ts_state_lock);
+
+ up(&ts->ts_activate_sem);

return ts->conn;
}
@@ -505,27 +489,20 @@ sleep:
if (iscsi_signal_thread_pre_handler(ts) < 0)
return NULL;

- if (!ts->conn) {
- pr_err("struct iscsi_thread_set->conn is NULL for "
- " thread_id: %d, going back to sleep\n",
- ts->thread_id);
- goto sleep;
- }
-
iscsi_check_to_add_additional_sets();
- /*
- * From the TX thread, up the tx_post_start_comp that the RX Thread is
- * sleeping on in iscsi_rx_thread_pre_handler(), then up the
- * rx_post_start_comp that iscsi_activate_thread_set() is sleeping on.
- */
- ts->thread_clear |= ISCSI_CLEAR_TX_THREAD;
- complete(&ts->tx_post_start_comp);
- complete(&ts->rx_post_start_comp);

spin_lock_bh(&ts->ts_state_lock);
- ts->status = ISCSI_THREAD_SET_ACTIVE;
+ if (!ts->conn) {
+ pr_err("struct iscsi_thread_set->conn is NULL for"
+ " TX thread_id: %s/%d\n", current->comm, current->pid);
+ spin_unlock_bh(&ts->ts_state_lock);
+ return NULL;
+ }
+ ts->thread_clear |= ISCSI_CLEAR_TX_THREAD;
spin_unlock_bh(&ts->ts_state_lock);

+ up(&ts->ts_activate_sem);
+
return ts->conn;
}

diff --git a/drivers/target/iscsi/iscsi_target_tq.h b/drivers/target/iscsi/iscsi_target_tq.h
index 547d118..cc1eede 100644
--- a/drivers/target/iscsi/iscsi_target_tq.h
+++ b/drivers/target/iscsi/iscsi_target_tq.h
@@ -64,10 +64,6 @@ struct iscsi_thread_set {
struct iscsi_conn *conn;
/* used for controlling ts state accesses */
spinlock_t ts_state_lock;
- /* Used for rx side post startup */
- struct completion rx_post_start_comp;
- /* Used for tx side post startup */
- struct completion tx_post_start_comp;
/* used for restarting thread queue */
struct completion rx_restart_comp;
/* used for restarting thread queue */
@@ -82,6 +78,7 @@ struct iscsi_thread_set {
struct task_struct *tx_thread;
/* struct iscsi_thread_set in list list head*/
struct list_head ts_list;
+ struct semaphore ts_activate_sem;
};

#endif /*** ISCSI_THREAD_QUEUE_H ***/
--
1.7.2.5

Nicholas A. Bellinger

unread,
Sep 10, 2013, 7:48:00 PM9/10/13
to target-devel, Nicholas Bellinger
From: Nicholas Bellinger <n...@linux-iscsi.org>

This patch removes an unnecessary wait_for_completion within
iscsi_get_thread_set(), that would wait for 1 second before
trying to obtain an inactive struct iscsi_thread_set from
iscsi_get_ts_from_inactive_list().

Since iscsi_allocate_thread_sets() will already be adding the
newly allocated iscsi_thread_set to the inactive list directly,
there is no need to wait here.

Signed-off-by: Nicholas Bellinger <n...@linux-iscsi.org>
---
drivers/target/iscsi/iscsi_target_tq.c | 19 +++----------------
1 files changed, 3 insertions(+), 16 deletions(-)

diff --git a/drivers/target/iscsi/iscsi_target_tq.c b/drivers/target/iscsi/iscsi_target_tq.c
index 09aae3d..5430dea 100644
--- a/drivers/target/iscsi/iscsi_target_tq.c
+++ b/drivers/target/iscsi/iscsi_target_tq.c
@@ -222,25 +222,12 @@ void iscsi_activate_thread_set(struct iscsi_conn *conn, struct iscsi_thread_set

struct iscsi_thread_set *iscsi_get_thread_set(void)
{
- int allocate_ts = 0;
- struct completion comp;
- struct iscsi_thread_set *ts = NULL;
- /*
- * If no inactive thread set is available on the first call to
- * iscsi_get_ts_from_inactive_list(), sleep for a second and
- * try again. If still none are available after two attempts,
- * allocate a set ourselves.
- */
+ struct iscsi_thread_set *ts;
+
get_set:
ts = iscsi_get_ts_from_inactive_list();
if (!ts) {
- if (allocate_ts == 2)
- iscsi_allocate_thread_sets(1);
-
- init_completion(&comp);
- wait_for_completion_timeout(&comp, 1 * HZ);
-
- allocate_ts++;
+ iscsi_allocate_thread_sets(1);
goto get_set;
}

--
1.7.2.5

Reply all
Reply to author
Forward
0 new messages