
#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);
void malloc_stats (void);

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 pthread_attr_t my_thread_attr;
static pthread_attr_t my_thread_attr_nonjoinable;

static int my_pthread_create (pthread_t * thread, void *(*start_routine) (void *), void *arg)
{
    int r;
    r = pthread_create (thread, &my_thread_attr, start_routine, arg);
    return r;
}

static int state1 = -1;
static int state2 = -1;

#define SET_STATE(s, x) \
    s = (x)

#define WAIT_FOR_STATE(s, x) \
    __sync_synchronize (); \
    while ((s) != (x)) \
        usleep (10000)

#define N_MALLOCS       150000
static char *shared_p[N_MALLOCS];

static void *thread1_fn (void *arg_)
{
    int i;
    (void) arg_;

    WAIT_FOR_STATE (state1, 0);

    for (i = 0; i < N_MALLOCS; i += 2) {
        shared_p[i] = (char *) realloc (NULL, 20);
        assert (shared_p[i] != NULL);
    }

    SET_STATE (state1, 1);
    WAIT_FOR_STATE (state1, 2);

    assert (shared_p[0] == NULL);
    for (i = 0; i < N_MALLOCS; i += 2) {
        shared_p[i] = (char *) realloc (NULL, 20);
        assert (shared_p[i] != NULL);
    }

    SET_STATE (state1, 3);
    WAIT_FOR_STATE (state1, 4);

    assert (shared_p[0] == NULL);

    {
        char *x;
        x = malloc (1);
        free (x);
    }

    SET_STATE (state1, 5);

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

    return NULL;
}


static void *thread2_fn (void *arg_)
{
    int i;
    (void) arg_;

    WAIT_FOR_STATE (state2, 0);

    for (i = 1; i < N_MALLOCS; i += 2) {
        shared_p[i] = (char *) realloc (NULL, 20);
        assert (shared_p[i] != NULL);
    }

    SET_STATE (state2, 1);
    WAIT_FOR_STATE (state2, 2);

    assert (shared_p[1] == NULL);
    for (i = 1; i < N_MALLOCS; i += 2) {
        shared_p[i] = (char *) realloc (NULL, 20);
        assert (shared_p[i] != NULL);
    }

    SET_STATE (state2, 3);
    WAIT_FOR_STATE (state2, 4);

    assert (shared_p[1] == NULL);

    {
        char *x;
        x = malloc (1);
        free (x);
    }

    SET_STATE (state2, 5);

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

    return NULL;
}


void *threadfree1_fn (void *dummy)
{
    int i;
    (void) dummy;

    SET_STATE (state1, 0);

    WAIT_FOR_STATE (state1, 1);
    assert (shared_p[0] != NULL);
    for (i = 0; i < N_MALLOCS; i += 2)
        free (shared_p[i]);
    shared_p[0] = NULL;
    SET_STATE (state1, 2);

    WAIT_FOR_STATE (state1, 3);
    assert (shared_p[0] != NULL);
    for (i = 0; i < N_MALLOCS; i += 2)
        free (shared_p[i]);
    shared_p[0] = NULL;
    SET_STATE (state1, 4);

    WAIT_FOR_STATE (state1, 5);

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

    return NULL;
}

void *threadfree2_fn (void *dummy)
{
    int i;
    (void) dummy;

    SET_STATE (state2, 0);

    WAIT_FOR_STATE (state2, 1);
    assert (shared_p[1] != NULL);
    for (i = 1; i < N_MALLOCS; i += 2)
        free (shared_p[i]);
    shared_p[1] = NULL;
    SET_STATE (state2, 2);

    WAIT_FOR_STATE (state2, 3);
    assert (shared_p[1] != NULL);
    for (i = 1; i < N_MALLOCS; i += 2)
        free (shared_p[i]);
    shared_p[1] = NULL;
    SET_STATE (state2, 4);

    WAIT_FOR_STATE (state2, 5);

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

    return NULL;
}



void *top_thread_fn (void *dummy)
{
    void *ret = NULL;
    int i;
    pthread_t thread1, thread2;
    (void) dummy;

    my_pthread_create (&thread1, thread1_fn, NULL);
    my_pthread_create (&thread2, thread2_fn, NULL);

    SET_STATE (state1, 0);
    SET_STATE (state2, 0);

    WAIT_FOR_STATE (state1, 1);
    WAIT_FOR_STATE (state2, 1);
    assert (shared_p[0] != NULL);
    assert (shared_p[1] != NULL);
    for (i = 0; i < N_MALLOCS; i++)
        free (shared_p[i]);
    shared_p[0] = NULL;
    shared_p[1] = NULL;
    SET_STATE (state1, 2);
    SET_STATE (state2, 2);

    WAIT_FOR_STATE (state1, 3);
    WAIT_FOR_STATE (state2, 3);
    assert (shared_p[0] != NULL);
    assert (shared_p[1] != NULL);
    for (i = 0; i < N_MALLOCS; i++)
        free (shared_p[i]);
    shared_p[0] = NULL;
    shared_p[1] = NULL;
    SET_STATE (state1, 4);
    SET_STATE (state2, 4);

    WAIT_FOR_STATE (state1, 5);
    WAIT_FOR_STATE (state2, 5);

    pthread_join (thread1, &ret);
    pthread_join (thread2, &ret);


/* ========================================================= */
    state1 = -1;
    state2 = -1;

    my_pthread_create (&thread1, threadfree1_fn, NULL);
    my_pthread_create (&thread2, threadfree2_fn, NULL);


    WAIT_FOR_STATE (state1, 0);
    WAIT_FOR_STATE (state2, 0);

    for (i = 0; i < N_MALLOCS; i++) {
        shared_p[i] = (char *) realloc (NULL, 20);
        assert (shared_p[i] != NULL);
    }

    SET_STATE (state1, 1);
    SET_STATE (state2, 1);
    WAIT_FOR_STATE (state1, 2);
    WAIT_FOR_STATE (state2, 2);

    assert (shared_p[0] == NULL);
    assert (shared_p[1] == NULL);
    for (i = 0; i < N_MALLOCS; i++) {
        shared_p[i] = (char *) realloc (NULL, 20);
        assert (shared_p[i] != NULL);
    }

    SET_STATE (state1, 3);
    SET_STATE (state2, 3);
    WAIT_FOR_STATE (state1, 4);
    WAIT_FOR_STATE (state2, 4);

    assert (shared_p[0] == NULL);
    assert (shared_p[1] == NULL);

    {
        char *x;
        x = malloc (1);
        free (x);
    }

    SET_STATE (state1, 5);
    SET_STATE (state2, 5);

    pthread_join (thread1, &ret);
    pthread_join (thread2, &ret);

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

    return NULL;
}


void thrash_alloc_in_one_thread_free_in_another (void)
{
    void *ret = NULL;
    pthread_t thread;

    my_pthread_create (&thread, top_thread_fn, NULL);

    WAIT_FOR_STATE (state1, 5);
    WAIT_FOR_STATE (state2, 5);

    pthread_join (thread, &ret);

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


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

    malloc_stats ();

    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);

    for (i = 0; i < 10; i++) {
        printf ("blocksA%d=%d,%d\n", i, leaks, (int) get_blocks ());
        thrash_alloc_in_one_thread_free_in_another ();
        if (leaks != -1)
            assert (leaks == (int) get_blocks ());
        leaks = get_blocks ();
    }

    dfwmalloc_enable_backtrace ();

    leaks = get_blocks ();
    printf ("blocks4=%d\n", (int) leaks);

    thrash_alloc_in_one_thread_free_in_another ();

    printf ("blocks5 => %d - %d = %d\n", (int) get_blocks (), leaks, (int) get_blocks () - leaks);
    assert (get_blocks () - leaks == 0);

    dfwmalloc_log_leaks ();

    printf ("test-lfv success\n");
}

