/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Copyright by The HDF Group.                                               *
 * All rights reserved.                                                      *
 *                                                                           *
 * This file is part of HDF5.  The full HDF5 copyright notice, including     *
 * terms governing use, modification, and redistribution, is contained in    *
 * the COPYING file, which can be found at the root of the source code       *
 * distribution tree, or in https://www.hdfgroup.org/licenses.               *
 * If you do not have access to either file, you may request a copy from     *
 * help@hdfgroup.org.                                                        *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/********************************************************************
 *
 * Testing thread safety. Thread Cancellation safety
 * -------------------------------------------------
 *
 * The main thread spawns a child to perform a series of dataset writes
 * to a hdf5 file. The main thread and child thread synchronizes within
 * a callback function called during a H5Diterate call afterwhich the
 * main thread attempts to cancel the child thread.
 *
 * The cancellation should only work after the child thread has safely
 * left the H5Diterate call.
 *
 * Temporary files generated:
 *   ttsafe_cancel.h5
 *
 ********************************************************************/
#include "ttsafe.h"

#ifdef H5_HAVE_THREADSAFE
#ifndef H5_HAVE_WIN_THREADS

#define FILENAME    "ttsafe_cancel.h5"
#define DATASETNAME "commonname"

void  *tts_cancel_thread(void *);
void   tts_cancel_barrier(void);
herr_t tts_cancel_callback(void *, hid_t, unsigned, const hsize_t *, void *);
void   cancellation_cleanup(void *);

hid_t cancel_file;
typedef struct cleanup_struct {
    hid_t dataset;
    hid_t datatype;
    hid_t dataspace;
} cancel_cleanup_t;

pthread_t       childthread;
pthread_mutex_t mutex;
pthread_cond_t  cond;

void
tts_cancel(void)
{
    pthread_attr_t            attribute;
    hid_t                     dataset;
    int                       buffer;
    int H5_ATTR_NDEBUG_UNUSED ret;

    /* make thread scheduling global */
    ret = pthread_attr_init(&attribute);
    assert(ret == 0);
#ifdef H5_HAVE_SYSTEM_SCOPE_THREADS
    ret = pthread_attr_setscope(&attribute, PTHREAD_SCOPE_SYSTEM);
    assert(ret == 0);
#endif /* H5_HAVE_SYSTEM_SCOPE_THREADS */

    /* Initialize mutex & condition variables */
    ret = pthread_mutex_init(&mutex, NULL);
    assert(ret == 0);
    ret = pthread_cond_init(&cond, NULL);
    assert(ret == 0);

    /*
     * Create a hdf5 file using H5F_ACC_TRUNC access, default file
     * creation plist and default file access plist
     */
    cancel_file = H5Fcreate(FILENAME, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
    assert(cancel_file >= 0);
    ret = pthread_create(&childthread, &attribute, tts_cancel_thread, NULL);
    assert(ret == 0);
    tts_cancel_barrier();
    ret = pthread_cancel(childthread);
    assert(ret == 0);

    dataset = H5Dopen2(cancel_file, DATASETNAME, H5P_DEFAULT);
    assert(dataset >= 0);
    ret = H5Dread(dataset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, &buffer);
    assert(ret >= 0);

    if (buffer != 11)
        TestErrPrintf("operation unsuccessful with value at %d instead of 11\n", buffer);

    ret = H5Dclose(dataset);
    assert(ret >= 0);
    ret = H5Fclose(cancel_file);
    assert(ret >= 0);

    /* Destroy the thread attribute */
    ret = pthread_attr_destroy(&attribute);
    assert(ret == 0);
} /* end tts_cancel() */

void *
tts_cancel_thread(void H5_ATTR_UNUSED *arg)
{
    hid_t             dataspace = H5I_INVALID_HID;
    hid_t             datatype  = H5I_INVALID_HID;
    hid_t             dataset   = H5I_INVALID_HID;
    int               datavalue;
    int               buffer;
    hsize_t           dimsf[1]; /* dataset dimensions */
    cancel_cleanup_t *cleanup_structure;
    herr_t            status;

    /* define dataspace for dataset */
    dimsf[0]  = 1;
    dataspace = H5Screate_simple(1, dimsf, NULL);
    CHECK(dataspace, H5I_INVALID_HID, "H5Screate_simple");

    /* define datatype for the data using native little endian integers */
    datatype = H5Tcopy(H5T_NATIVE_INT);
    CHECK(datatype, H5I_INVALID_HID, "H5Tcopy");
    status = H5Tset_order(datatype, H5T_ORDER_LE);
    CHECK(status, FAIL, "H5Tset_order");

    /* create a new dataset within the file */
    dataset =
        H5Dcreate2(cancel_file, DATASETNAME, datatype, dataspace, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
    CHECK(dataset, H5I_INVALID_HID, "H5Dcreate2");

    /* If thread is cancelled, make cleanup call */
    cleanup_structure            = (cancel_cleanup_t *)malloc(sizeof(cancel_cleanup_t));
    cleanup_structure->dataset   = dataset;
    cleanup_structure->datatype  = datatype;
    cleanup_structure->dataspace = dataspace;
    pthread_cleanup_push(cancellation_cleanup, cleanup_structure);

    datavalue = 1;
    status    = H5Dwrite(dataset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, &datavalue);
    CHECK(status, FAIL, "H5Dwrite");

    status = H5Dread(dataset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, &buffer);
    CHECK(status, FAIL, "H5Dread");
    status = H5Diterate(&buffer, H5T_NATIVE_INT, dataspace, tts_cancel_callback, &dataset);
    CHECK(status, FAIL, "H5Diterate");

    HDsleep(3);

    datavalue = 100;
    status    = H5Dwrite(dataset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, &datavalue);
    CHECK(status, FAIL, "H5Dwrite");

    status = H5Dclose(dataset);
    CHECK(status, FAIL, "H5Dclose");
    status = H5Tclose(datatype);
    CHECK(status, FAIL, "H5Tclose");
    status = H5Sclose(dataspace);
    CHECK(status, FAIL, "H5Sclose");

    /*
     * Required by pthreads. The argument 0 pops the stack but does not
     * execute the cleanup routine.
     */
    pthread_cleanup_pop(0);

    return NULL;
} /* end tts_cancel_thread() */

herr_t
tts_cancel_callback(void *elem, hid_t H5_ATTR_UNUSED type_id, unsigned H5_ATTR_UNUSED ndim,
                    const hsize_t H5_ATTR_UNUSED *point, void *operator_data)
{
    hid_t  dataset = *(hid_t *)operator_data;
    int    value   = *(int *)elem;
    herr_t status;

    tts_cancel_barrier();
    HDsleep(3);

    if (value != 1) {
        TestErrPrintf("Error! Element value should be 1 and not %d\n", value);
        return FAIL;
    }

    value += 10;
    status = H5Dwrite(dataset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, &value);
    CHECK(status, FAIL, "H5Dwrite");

    return SUCCEED;
} /* end tts_cancel_callback() */

/*
 * Need to perform the dataset, datatype and dataspace close that was never
 * performed because of thread cancellation
 */
void
cancellation_cleanup(void *arg)
{
    cancel_cleanup_t *cleanup_structure = (cancel_cleanup_t *)arg;
    herr_t            status;

    status = H5Dclose(cleanup_structure->dataset);
    CHECK(status, FAIL, "H5Dclose");
    status = H5Tclose(cleanup_structure->datatype);
    CHECK(status, FAIL, "H5Tclose");
    status = H5Sclose(cleanup_structure->dataspace);
    CHECK(status, FAIL, "H5Sclose");

    /* retained for debugging */
    /*  print_func("cancellation noted, cleaning up ... \n"); */
} /* end cancellation_cleanup() */

/*
 * Artificial (and specific to this test) barrier to keep track of whether
 * both the main and child threads have reached a point in the program.
 */
void
tts_cancel_barrier(void)
{
    static int count = 2;
    int        status;

    status = pthread_mutex_lock(&mutex);
    VERIFY(status, 0, "pthread_mutex_lock");

    if (count != 1) {
        count--;
        status = pthread_cond_wait(&cond, &mutex);
        VERIFY(status, 0, "pthread_cond_wait");
    }
    else {
        status = pthread_cond_signal(&cond);
        VERIFY(status, 0, "pthread_cond_signal");
    }

    status = pthread_mutex_unlock(&mutex);
    VERIFY(status, 0, "pthread_mutex_unlock");
} /* end tts_cancel_barrier() */

void
cleanup_cancel(void)
{
    HDunlink(FILENAME);
}

#endif /*H5_HAVE_WIN_THREADS*/
#endif /*H5_HAVE_THREADSAFE*/
