diff mbox series

[v3] fzsync: revoke thread_b if parent hits an accidental break

Message ID 20190924105801.7616-1-liwang@redhat.com
State Changes Requested
Delegated to: Petr Vorel
Headers show
Series [v3] fzsync: revoke thread_b if parent hits an accidental break | expand

Commit Message

Li Wang Sept. 24, 2019, 10:58 a.m. UTC
We shouldn't rely entirely on the pair->exit flag in tst_fzsync_pair_cleanup()
since there is possible to call tst_brk() at anyplace of thread_a, that will
lead to timeout eventually because of thread_b(tst_fzsync_wait_b) fall into
an infinite(no explicit condition to exit) loop.

Thread_a path trace:
  tst_brk()
    cleanup()
      tst_fzsync_pair_cleanup()
        SAFE_PTHREAD_JOIN(pair->thread_b, NULL)
        #Or pthread_cancel(pair->thread_b)

We fix the problem via a way to kill thread_b with pthread_cancel.

Work-around: [commit 2e74d996: Check recvmmsg exists before entering fuzzy loop]
Signed-off-by: Li Wang <liwang@redhat.com>
Cc: Richard Palethorpe <rpalethorpe@suse.com>
Cc: Cyril Hrubis <chrubis@suse.cz>
---

Notes:
    Patch V2: http://lists.linux.it/pipermail/ltp/2019-January/010489.html
    Patch V1: http://lists.linux.it/pipermail/ltp/2019-January/010438.html

 include/tst_fuzzy_sync.h | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

Comments

Richard Palethorpe Sept. 24, 2019, 12:42 p.m. UTC | #1
Hello,

Li Wang <liwang@redhat.com> writes:

> We shouldn't rely entirely on the pair->exit flag in tst_fzsync_pair_cleanup()
> since there is possible to call tst_brk() at anyplace of thread_a, that will
> lead to timeout eventually because of thread_b(tst_fzsync_wait_b) fall into
> an infinite(no explicit condition to exit) loop.
>
> Thread_a path trace:
>   tst_brk()
>     cleanup()
>       tst_fzsync_pair_cleanup()
>         SAFE_PTHREAD_JOIN(pair->thread_b, NULL)
>         #Or pthread_cancel(pair->thread_b)
>
> We fix the problem via a way to kill thread_b with pthread_cancel.

Good idea, however not an easy one to implement.

>
> Work-around: [commit 2e74d996: Check recvmmsg exists before entering fuzzy loop]
> Signed-off-by: Li Wang <liwang@redhat.com>
> Cc: Richard Palethorpe <rpalethorpe@suse.com>
> Cc: Cyril Hrubis <chrubis@suse.cz>
> ---
>
> Notes:
>     Patch V2: http://lists.linux.it/pipermail/ltp/2019-January/010489.html
>     Patch V1: http://lists.linux.it/pipermail/ltp/2019-January/010438.html
>
>  include/tst_fuzzy_sync.h | 16 ++++++++++++----
>  1 file changed, 12 insertions(+), 4 deletions(-)
>
> diff --git a/include/tst_fuzzy_sync.h b/include/tst_fuzzy_sync.h
> index f9a1947c7..152f779cb 100644
> --- a/include/tst_fuzzy_sync.h
> +++ b/include/tst_fuzzy_sync.h
> @@ -63,6 +63,7 @@
>  #include <time.h>
>  #include <math.h>
>  #include <stdlib.h>
> +#include <pthread.h>
>  #include "tst_atomic.h"
>  #include "tst_timer.h"
>  #include "tst_safe_pthread.h"
> @@ -218,9 +219,13 @@ static void tst_fzsync_pair_init(struct tst_fzsync_pair *pair)
>  static void tst_fzsync_pair_cleanup(struct tst_fzsync_pair *pair)
>  {
>  	if (pair->thread_b) {
> -		tst_atomic_store(1, &pair->exit);
> -		SAFE_PTHREAD_JOIN(pair->thread_b, NULL);
> -		pair->thread_b = 0;
> +		if (pair->exit == 1) {

It can just be

if (!pair->exit) {
   ...
}

We want to join the thread and set the func pointer to zero regardless
of how we exit.

> +			SAFE_PTHREAD_JOIN(pair->thread_b, NULL);
> +			pair->thread_b = 0;
> +		} else {

I suggest still setting pair->exit here and maybe sleeping for
100ms. This gives thread B chance to exit gracefully. It is possible
that if thread B is in a spin loop then the thread won't be cancelled as
asynchronous cancellation is not guaranteed by POSIX.

> +			pthread_cancel(pair->thread_b);
> +			pair->thread_b = 0;
> +		}
>  	}
>  }
>
> @@ -271,8 +276,11 @@ static void tst_fzsync_pair_reset(struct tst_fzsync_pair *pair,
>  	pair->a_cntr = 0;
>  	pair->b_cntr = 0;
>  	pair->exit = 0;
> -	if (run_b)
> +	if (run_b) {
> +		pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
> +		pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

These need to go inside thread B unless I am mistaken. Which means you
must wrap the user supplied function. You can create a function which
accepts a pointer to some contiguous memory containing the user supplied function
pointer and the user supplied arg pointer.

This can then set the threads cancel type before calling the user func with
the arg.

>  		SAFE_PTHREAD_CREATE(&pair->thread_b, 0, run_b, 0);
> +	}
>
>  	pair->exec_time_start = (float)tst_timeout_remaining();
>  }

--
Thank you,
Richard.
Li Wang Sept. 25, 2019, 8:08 a.m. UTC | #2
Hi Richard,

On Tue, Sep 24, 2019 at 8:42 PM Richard Palethorpe <rpalethorpe@suse.de>
wrote:

> ...
> It can just be
>
> if (!pair->exit) {
>    ...
> }
>
> We want to join the thread and set the func pointer to zero regardless
> of how we exit.
>

OK.


>
> > +                     SAFE_PTHREAD_JOIN(pair->thread_b, NULL);
> > +                     pair->thread_b = 0;
> > +             } else {
>
> I suggest still setting pair->exit here and maybe sleeping for
> 100ms. This gives thread B chance to exit gracefully. It is possible
> that if thread B is in a spin loop then the thread won't be cancelled as
> asynchronous cancellation is not guaranteed by POSIX.
>

Good suggestion. That'd be better to give one more time for thread B
exiting gracefully.


> > +                     pthread_cancel(pair->thread_b);
> > +                     pair->thread_b = 0;
> > +             }
> >       }
> >  }
> >
> > @@ -271,8 +276,11 @@ static void tst_fzsync_pair_reset(struct
> tst_fzsync_pair *pair,
> >       pair->a_cntr = 0;
> >       pair->b_cntr = 0;
> >       pair->exit = 0;
> > -     if (run_b)
> > +     if (run_b) {
> > +             pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
> > +             pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
>
> These need to go inside thread B unless I am mistaken. Which means you
>

Right.


> must wrap the user supplied function. You can create a function which
> accepts a pointer to some contiguous memory containing the user supplied
> function
> pointer and the user supplied arg pointer.
>

Since you have fixed the function format of thread B as void *(*run_b)(void
*) in tst_fzsync_pair_reset(), which means we have no need to take care of
the function arg pointer anymore.

So just like what I did in V2, the wrapper function could steal the real
run_b address from pthread_create(..., wrap_run_b, run_b) parameter.
Richard Palethorpe Sept. 25, 2019, 8:53 a.m. UTC | #3
Hello,

Li Wang <liwang@redhat.com> writes:

> Hi Richard,
>
> On Tue, Sep 24, 2019 at 8:42 PM Richard Palethorpe <rpalethorpe@suse.de>
> wrote:
>
>> ...
>> It can just be
>>
>> if (!pair->exit) {
>>    ...
>> }
>>
>> We want to join the thread and set the func pointer to zero regardless
>> of how we exit.
>>
>
> OK.
>
>
>>
>> > +                     SAFE_PTHREAD_JOIN(pair->thread_b, NULL);
>> > +                     pair->thread_b = 0;
>> > +             } else {
>>
>> I suggest still setting pair->exit here and maybe sleeping for
>> 100ms. This gives thread B chance to exit gracefully. It is possible
>> that if thread B is in a spin loop then the thread won't be cancelled as
>> asynchronous cancellation is not guaranteed by POSIX.
>>
>
> Good suggestion. That'd be better to give one more time for thread B
> exiting gracefully.
>
>
>> > +                     pthread_cancel(pair->thread_b);
>> > +                     pair->thread_b = 0;
>> > +             }
>> >       }
>> >  }
>> >
>> > @@ -271,8 +276,11 @@ static void tst_fzsync_pair_reset(struct
>> tst_fzsync_pair *pair,
>> >       pair->a_cntr = 0;
>> >       pair->b_cntr = 0;
>> >       pair->exit = 0;
>> > -     if (run_b)
>> > +     if (run_b) {
>> > +             pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
>> > +             pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
>>
>> These need to go inside thread B unless I am mistaken. Which means you
>>
>
> Right.
>
>
>> must wrap the user supplied function. You can create a function which
>> accepts a pointer to some contiguous memory containing the user supplied
>> function
>> pointer and the user supplied arg pointer.
>>
>
> Since you have fixed the function format of thread B as void *(*run_b)(void
> *) in tst_fzsync_pair_reset(), which means we have no need to take care of
> the function arg pointer anymore.

I think the function pointer signature would be 'void *(*run_b)(void)'
not 'void *(*run_b)(void *)'.

I doubt any test would need the arg though, because we only use one
thread and can store parameters in global variables. So you could remove
it and update the tests.

The user might need that arg if they are starting many threads, but for
now we don't have explicit support for that in the library.

>
> So just like what I did in V2, the wrapper function could steal the real
> run_b address from pthread_create(..., wrap_run_b, run_b) parameter.


--
Thank you,
Richard.
Li Wang Sept. 25, 2019, 9:43 a.m. UTC | #4
Richard Palethorpe <rpalethorpe@suse.de> wrote:

>> must wrap the user supplied function. You can create a function which
> >> accepts a pointer to some contiguous memory containing the user supplied
> >> function
> >> pointer and the user supplied arg pointer.
> >>
> >
> > Since you have fixed the function format of thread B as void
> *(*run_b)(void
> > *) in tst_fzsync_pair_reset(), which means we have no need to take care
> of
> > the function arg pointer anymore.
>
> I think the function pointer signature would be 'void *(*run_b)(void)'
> not 'void *(*run_b)(void *)'.
>

Hmm, but at least we should respect the pthread_create()? It requires the
function prototype is ''void *(*func)(void *)'.

       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

We could unuse the arg in thread B, but declare the function prototype with
parameter "void *" is no harm.


> I doubt any test would need the arg though, because we only use one
> thread and can store parameters in global variables. So you could remove
> it and update the tests.
>
> The user might need that arg if they are starting many threads, but for
> now we don't have explicit support for that in the library.
>

Maybe we just need to note that in the lib document.


> >
> > So just like what I did in V2, the wrapper function could steal the real
> > run_b address from pthread_create(..., wrap_run_b, run_b) parameter.
>
>
> --
> Thank you,
> Richard.
>
Richard Palethorpe Sept. 25, 2019, 12:13 p.m. UTC | #5
Hello,

Li Wang <liwang@redhat.com> writes:

> Richard Palethorpe <rpalethorpe@suse.de> wrote:
>
>>> must wrap the user supplied function. You can create a function which
>> >> accepts a pointer to some contiguous memory containing the user supplied
>> >> function
>> >> pointer and the user supplied arg pointer.
>> >>
>> >
>> > Since you have fixed the function format of thread B as void
>> *(*run_b)(void
>> > *) in tst_fzsync_pair_reset(), which means we have no need to take care
>> of
>> > the function arg pointer anymore.
>>
>> I think the function pointer signature would be 'void *(*run_b)(void)'
>> not 'void *(*run_b)(void *)'.
>>
>
> Hmm, but at least we should respect the pthread_create()? It requires the
> function prototype is ''void *(*func)(void *)'.
>
>        int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
>                           void *(*start_routine) (void *), void *arg);
>
> We could unuse the arg in thread B, but declare the function prototype with
> parameter "void *" is no harm.

I'm not sure what you are saying. However you could do something like
this (I haven't tested it):

struct tst_fzsync_run_thread
{
        void *(*run)(void *);
        void *arg;
};

static void tst_fzsync_thread_wrapper(void *arg)
{
        struct tst_fzsync_run_thread t = *(struct tst_fzsync_run_thread *)arg;

        pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS);
        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE);

        t.run(t.arg);
}

static void tst_fzsync_pair_reset(..., struct tst_fzsync_run_thread *run_b)
{
        ...

        if (run_b)
           SAFE_PTHREAD_fCREATE(..., tst_fzsync_thread_wrapper, (void *)run_b);

        ...
}

Note that in any case you can't reliably cast a function pointer to a
void pointer without some magic. I am guessing wrapping it in a struct
is the clearest way to do it.

You can remove the arg altogether, but I kept it because we have a
struct anyway to wrap the function pointer.

>
>
>> I doubt any test would need the arg though, because we only use one
>> thread and can store parameters in global variables. So you could remove
>> it and update the tests.
>>
>> The user might need that arg if they are starting many threads, but for
>> now we don't have explicit support for that in the library.
>>
>
> Maybe we just need to note that in the lib document.
>
>
>> >
>> > So just like what I did in V2, the wrapper function could steal the real
>> > run_b address from pthread_create(..., wrap_run_b, run_b) parameter.
>>
>>
>> --
>> Thank you,
>> Richard.
>>


--
Thank you,
Richard.
Li Wang Sept. 26, 2019, 5:48 a.m. UTC | #6
On Wed, Sep 25, 2019 at 8:13 PM Richard Palethorpe <rpalethorpe@suse.de>
wrote:

> ...
> I'm not sure what you are saying. However you could do something like
> this (I haven't tested it):
>

I misunderstood your words in the last mail, sorry about that.


>
> struct tst_fzsync_run_thread
> {
>         void *(*run)(void *);
>         void *arg;
> };
>
> static void tst_fzsync_thread_wrapper(void *arg)
> {
>         struct tst_fzsync_run_thread t = *(struct tst_fzsync_run_thread
> *)arg;
>
>         pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS);
>         pthread_setcancelstate(PTHREAD_CANCEL_ENABLE);
>
>         t.run(t.arg);
> }
>
> static void tst_fzsync_pair_reset(..., struct tst_fzsync_run_thread *run_b)
>

I'd like to keep the tst_fzync_pair_reset() API no change to user.

The patch v4.1 like below, is there any improper place in usage?

@@ -218,12 +219,36 @@ static void tst_fzsync_pair_init(struct
tst_fzsync_pair *pair)
 static void tst_fzsync_pair_cleanup(struct tst_fzsync_pair *pair)
 {
        if (pair->thread_b) {
-               tst_atomic_store(1, &pair->exit);
+               /* Revoke thread B if parent hits accidental break */
+               if (!pair->exit) {
+                       tst_atomic_store(1, &pair->exit);
+                       usleep(100000);
+                       pthread_cancel(pair->thread_b);
+               }
                SAFE_PTHREAD_JOIN(pair->thread_b, NULL);
                pair->thread_b = 0;
        }
 }

+/** To store the run_b pointer and pass to tst_fzsync_thread_wrapper */
+struct tst_fzsync_run_thread {
+       void *(*func)(void *);
+       void *arg;
+};
+
+/**
+ * Wrap run_b for tst_fzsync_pair_reset to enable pthread cancel
+ * at the start of the thread B.
+ */
+static void *tst_fzsync_thread_wrapper(void *run_thread)
+{
+       struct tst_fzsync_run_thread t = *(struct tst_fzsync_run_thread
*)run_thread;
+
+       pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+       pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+       return t.func(t.arg);
+}
+
 /**
  * Zero some stat fields
  *
@@ -271,8 +296,10 @@ static void tst_fzsync_pair_reset(struct
tst_fzsync_pair *pair,
        pair->a_cntr = 0;
        pair->b_cntr = 0;
        pair->exit = 0;
-       if (run_b)
-               SAFE_PTHREAD_CREATE(&pair->thread_b, 0, run_b, 0);
+       if (run_b) {
+               struct tst_fzsync_run_thread wrap_run_b = {.func = run_b,
.arg = NULL};
+               SAFE_PTHREAD_CREATE(&pair->thread_b, 0,
tst_fzsync_thread_wrapper, &wrap_run_b);
+       }

        pair->exec_time_start = (float)tst_timeout_remaining();
 }


> Note that in any case you can't reliably cast a function pointer to a
> void pointer without some magic. I am guessing wrapping it in a struct
> is the clearest way to do it.
>

Good to know this, I searched on google and confirmed that the C standard
does not allow to cast function pointer to void*, thanks!


>
> You can remove the arg altogether, but I kept it because we have a
> struct anyway to wrap the function pointer.
>

Yes, to keep it make the wrapper struct is clear to understand.
Richard Palethorpe Sept. 26, 2019, 9:25 a.m. UTC | #7
Li Wang <liwang@redhat.com> writes:

> On Wed, Sep 25, 2019 at 8:13 PM Richard Palethorpe <rpalethorpe@suse.de>
> wrote:
>
>> ...
>> I'm not sure what you are saying. However you could do something like
>> this (I haven't tested it):
>>
>
> I misunderstood your words in the last mail, sorry about that.

No problem. This is also my fault.

>
>
>>
>> struct tst_fzsync_run_thread
>> {
>>         void *(*run)(void *);
>>         void *arg;
>> };
>>
>> static void tst_fzsync_thread_wrapper(void *arg)
>> {
>>         struct tst_fzsync_run_thread t = *(struct tst_fzsync_run_thread
>> *)arg;
>>
>>         pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS);
>>         pthread_setcancelstate(PTHREAD_CANCEL_ENABLE);
>>
>>         t.run(t.arg);
>> }
>>
>> static void tst_fzsync_pair_reset(..., struct tst_fzsync_run_thread *run_b)
>>
>
> I'd like to keep the tst_fzync_pair_reset() API no change to user.
>
> The patch v4.1 like below, is there any improper place in usage?

LGTM! 

>
> @@ -218,12 +219,36 @@ static void tst_fzsync_pair_init(struct
> tst_fzsync_pair *pair)
>  static void tst_fzsync_pair_cleanup(struct tst_fzsync_pair *pair)
>  {
>         if (pair->thread_b) {
> -               tst_atomic_store(1, &pair->exit);
> +               /* Revoke thread B if parent hits accidental break */
> +               if (!pair->exit) {
> +                       tst_atomic_store(1, &pair->exit);
> +                       usleep(100000);
> +                       pthread_cancel(pair->thread_b);
> +               }
>                 SAFE_PTHREAD_JOIN(pair->thread_b, NULL);
>                 pair->thread_b = 0;
>         }
>  }
>
> +/** To store the run_b pointer and pass to tst_fzsync_thread_wrapper */
> +struct tst_fzsync_run_thread {
> +       void *(*func)(void *);
> +       void *arg;
> +};
> +
> +/**
> + * Wrap run_b for tst_fzsync_pair_reset to enable pthread cancel
> + * at the start of the thread B.
> + */
> +static void *tst_fzsync_thread_wrapper(void *run_thread)
> +{
> +       struct tst_fzsync_run_thread t = *(struct tst_fzsync_run_thread
> *)run_thread;
> +
> +       pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
> +       pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
> +       return t.func(t.arg);
> +}
> +
>  /**
>   * Zero some stat fields
>   *
> @@ -271,8 +296,10 @@ static void tst_fzsync_pair_reset(struct
> tst_fzsync_pair *pair,
>         pair->a_cntr = 0;
>         pair->b_cntr = 0;
>         pair->exit = 0;
> -       if (run_b)
> -               SAFE_PTHREAD_CREATE(&pair->thread_b, 0, run_b, 0);
> +       if (run_b) {
> +               struct tst_fzsync_run_thread wrap_run_b = {.func = run_b,
> .arg = NULL};
> +               SAFE_PTHREAD_CREATE(&pair->thread_b, 0,
> tst_fzsync_thread_wrapper, &wrap_run_b);
> +       }
>
>         pair->exec_time_start = (float)tst_timeout_remaining();
>  }
>
>
>> Note that in any case you can't reliably cast a function pointer to a
>> void pointer without some magic. I am guessing wrapping it in a struct
>> is the clearest way to do it.
>>
>
> Good to know this, I searched on google and confirmed that the C standard
> does not allow to cast function pointer to void*, thanks!
>
>
>>
>> You can remove the arg altogether, but I kept it because we have a
>> struct anyway to wrap the function pointer.
>>
>
> Yes, to keep it make the wrapper struct is clear to understand.
diff mbox series

Patch

diff --git a/include/tst_fuzzy_sync.h b/include/tst_fuzzy_sync.h
index f9a1947c7..152f779cb 100644
--- a/include/tst_fuzzy_sync.h
+++ b/include/tst_fuzzy_sync.h
@@ -63,6 +63,7 @@ 
 #include <time.h>
 #include <math.h>
 #include <stdlib.h>
+#include <pthread.h>
 #include "tst_atomic.h"
 #include "tst_timer.h"
 #include "tst_safe_pthread.h"
@@ -218,9 +219,13 @@  static void tst_fzsync_pair_init(struct tst_fzsync_pair *pair)
 static void tst_fzsync_pair_cleanup(struct tst_fzsync_pair *pair)
 {
 	if (pair->thread_b) {
-		tst_atomic_store(1, &pair->exit);
-		SAFE_PTHREAD_JOIN(pair->thread_b, NULL);
-		pair->thread_b = 0;
+		if (pair->exit == 1) {
+			SAFE_PTHREAD_JOIN(pair->thread_b, NULL);
+			pair->thread_b = 0;
+		} else {
+			pthread_cancel(pair->thread_b);
+			pair->thread_b = 0;
+		}
 	}
 }
 
@@ -271,8 +276,11 @@  static void tst_fzsync_pair_reset(struct tst_fzsync_pair *pair,
 	pair->a_cntr = 0;
 	pair->b_cntr = 0;
 	pair->exit = 0;
-	if (run_b)
+	if (run_b) {
+		pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+		pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
 		SAFE_PTHREAD_CREATE(&pair->thread_b, 0, run_b, 0);
+	}
 
 	pair->exec_time_start = (float)tst_timeout_remaining();
 }