
#include <pthread.h>

#include <stdio.h>
#include <unistd.h>
#include <my_assert.h>

int __dfwmalloc_get_n_arenas (void);
void __dfwmalloc_get_stat_adjust (struct dfwmalloc_stats *s);

static pthread_attr_t my_thread_attr;
static pthread_attr_t my_thread_attr_nonjoinable;

static int my_pthread_create_joinable (pthread_t * thread, int joinable, void *(*start_routine) (void *), void *arg)
{
    int r;
    if (joinable)
        r = pthread_create (thread, &my_thread_attr, start_routine, arg);
    else
        r = pthread_create (thread, &my_thread_attr_nonjoinable, start_routine, arg);
    return r;
}

static long get_blocks (void)
{
    long r;
    struct dfwmalloc_stats st_adjust;
    r = dfwmalloc_alloced_blocks ();
    __dfwmalloc_get_stat_adjust (&st_adjust);
#if defined(WITH_BTANDCHECK) && !defined(NULL_MODE)
    r += st_adjust.blocks_small + st_adjust.blocks;
#endif
    return r;
}

static char *some_var = NULL;

static int sync_flag = 0;

static int wait_before_exit = 0;

static void *simple_fn0 (void *dummy)
{
    assert (!strcmp ((const char *) dummy, "the param"));
    some_var = (char *) malloc (13);
    free (some_var);
    some_var = NULL;

#ifdef NO_PTHREAD_EXIT_OVERRIDE
#ifdef USE_TLS
    dfwmalloc_pthread_exit ();
#endif
#endif

    sync_flag = 1;

    if (wait_before_exit)
        usleep (10000);

    pthread_exit ((void *) "my result");
}

static void *simple_fn1 (void *dummy)
{
    assert (!strcmp ((const char *) dummy, "the param"));
    some_var = (char *) malloc (13);
    free (some_var);
    some_var = NULL;

#ifdef NO_PTHREAD_EXIT_OVERRIDE
#ifdef USE_TLS
    dfwmalloc_pthread_exit ();
#endif
#endif

    sync_flag = 1;

    if (wait_before_exit)
        usleep (10000);

    return (void *) "my result";
}

static void *simple_fn2 (void *dummy)
{
    assert (!strcmp ((const char *) dummy, "the param"));
    some_var = (char *) malloc (13);

#ifdef NO_PTHREAD_EXIT_OVERRIDE
#ifdef USE_TLS
    dfwmalloc_pthread_exit ();
#endif
#endif

    sync_flag = 1;

    if (wait_before_exit)
        usleep (10000);

    pthread_exit ((void *) "my result");
}

static void *simple_fn3 (void *dummy)
{
    assert (!strcmp ((const char *) dummy, "the param"));
    some_var = (char *) malloc (13);

#ifdef NO_PTHREAD_EXIT_OVERRIDE
#ifdef USE_TLS
    dfwmalloc_pthread_exit ();
#endif
#endif

    sync_flag = 1;

    if (wait_before_exit)
        usleep (10000);

    return (void *) "my result";
}


typedef void *(*simple_fn_t) (void *);

long start_leak = 0;

void run_it (void)
{
    simple_fn_t sf[4] = {simple_fn0, simple_fn1, simple_fn2, simple_fn3};
    int options;
    int i;

    printf ("try thread join+cancel combinations\n");
    fflush (stdout);
    for (options = 0; options < (1 << 5); options++) {
        for (i = 0; i < 4; i++) {
            int j;
            for (j = 0; j < 5; j++) {
                int wait_before_join, joinable, cancel, cancel2;
                int r;
                pthread_t thread;
                void *retval = NULL;
#if 0
                printf ("[%d,%d,%d,%d] ", options, j, i, __dfwmalloc_get_n_arenas ());
#else
                printf (".");
#endif
                assert (__dfwmalloc_get_n_arenas () < 4);
                fflush (stdout);
                sync_flag = 0;
                joinable =              (options >> 0) & 1;
                wait_before_exit =      (options >> 1) & 1;
                wait_before_join =      (options >> 2) & 1;
                cancel =                (options >> 3) & 1;
                cancel2 =               (options >> 4) & 1;
                my_pthread_create_joinable (&thread, joinable, sf[i], (void *) "the param");
                while (!sync_flag)
                    usleep (1000);
                if (cancel)
                    pthread_cancel (thread);
                if (wait_before_join)
                    usleep (10000);
                if (cancel2)
                    pthread_cancel (thread);
                r = pthread_join (thread, &retval);
                if (!joinable) {
                    assert (r);
                    assert (retval == NULL);
                } else if (((cancel2 && !wait_before_join) || cancel) && wait_before_exit) {
                    assert (retval == PTHREAD_CANCELED);
                } else if (cancel || cancel2) {
                    /* no guarantee here */
                } else {
                    assert (!strcmp ((char *) retval, "my result"));
                }
                if (some_var) {
                    free (some_var);
                    some_var = NULL;
                }
                if (wait_before_exit)
                    usleep(12000);
            }
        }
    }
    printf ("\n");
    fflush (stdout);
    dfwmalloc_truncate_heap ();
#ifdef WITH_BTANDCHECK
    dfwmalloc_log_leaks ();
    printf ("sf done leaks: %ld\n", (long) get_blocks () - start_leak);
    fflush (stdout);
#endif
}

void entry (void)
{
    int pr;
    int joinable = -1;

    pr = pthread_attr_init(&my_thread_attr);
    assert (!pr);
    pr = pthread_attr_setstacksize(&my_thread_attr, 16384);
    assert (!pr);

    pr = pthread_attr_init(&my_thread_attr_nonjoinable);
    assert (!pr);
    pr = pthread_attr_setstacksize(&my_thread_attr_nonjoinable, 16384);
    assert (!pr);
    pr = pthread_attr_setdetachstate(&my_thread_attr_nonjoinable, PTHREAD_CREATE_DETACHED);
    assert (!pr);
    pr = pthread_attr_getdetachstate(&my_thread_attr_nonjoinable, &joinable);
    assert (!pr);
    assert (joinable == PTHREAD_CREATE_DETACHED);

    run_it ();
#ifdef __FreeBSD__
    assert (get_blocks () <= 20);
#else
    assert (get_blocks () <= 12);
#endif

    dfwmalloc_enable_backtrace ();

    printf ("repeat with backtrace\n");

    start_leak = get_blocks ();
    (void) start_leak;

    run_it ();
    usleep(20000);
    assert (get_blocks () - start_leak == 0);
}

