/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * 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 LICENSE 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. Deliberate per-thread errors to test error stack
 * -----------------------------------------------------------------------
 *
 * Create 16 multiple threads to create datasets with the same name. The
 * library should respond with 15 equivalent error stack printouts (one for
 * each bad thread). The final hdf5 file should be a valid file with one
 * entry.
 *
 * Temporary files generated:
 *
 *     ttsafe_error.h5
 *
 ********************************************************************/
#include "ttsafe.h"
#define H5VL_FRIEND /* Suppress error about including H5VLpkg    */
#define H5VL_TESTING
#include "H5VLpkg.h" /* Virtual Object Layer                 */

#ifdef H5_HAVE_THREADSAFE_API

#define NUM_THREAD 16
#define FILENAME   "ttsafe_error.h5"

/* Having a common dataset name is an error */
#define DATASETNAME          "commonname"
#define EXPECTED_ERROR_DEPTH 11
#define WRITE_NUMBER         37

/* Typedefs */
typedef struct err_num_struct {
    hid_t maj_num;
    hid_t min_num;
} err_num_t;

/* Global variables */
hid_t        error_file_g  = H5I_INVALID_HID;
int          error_flag_g  = 0;
int          error_count_g = 0;
err_num_t    expected_g[EXPECTED_ERROR_DEPTH];
H5TS_mutex_t error_mutex_g;

/* Prototypes */
static herr_t                  error_callback(hid_t, void *);
static herr_t                  walk_error_callback(unsigned, const H5E_error2_t *, void *);
static H5TS_THREAD_RETURN_TYPE tts_error_thread(void *);

void
tts_error(void H5_ATTR_UNUSED *params)
{
    hid_t         def_fapl = H5I_INVALID_HID;
    hid_t         vol_id   = H5I_INVALID_HID;
    hid_t         dataset  = H5I_INVALID_HID;
    H5TS_thread_t threads[NUM_THREAD];
    int           value, i;
    int           is_native;
    herr_t        status;

    /* Must initialize these at runtime */
    expected_g[0].maj_num = H5E_DATASET;
    expected_g[0].min_num = H5E_CANTCREATE;

    expected_g[1].maj_num = H5E_DATASET;
    expected_g[1].min_num = H5E_CANTCREATE;

    expected_g[2].maj_num = H5E_VOL;
    expected_g[2].min_num = H5E_CANTCREATE;

    expected_g[3].maj_num = H5E_VOL;
    expected_g[3].min_num = H5E_CANTCREATE;

    expected_g[4].maj_num = H5E_DATASET;
    expected_g[4].min_num = H5E_CANTINIT;

    expected_g[5].maj_num = H5E_DATASET;
    expected_g[5].min_num = H5E_CANTINIT;

    expected_g[6].maj_num = H5E_LINK;
    expected_g[6].min_num = H5E_CANTINIT;

    expected_g[7].maj_num = H5E_LINK;
    expected_g[7].min_num = H5E_CANTINSERT;

    expected_g[8].maj_num = H5E_SYM;
    expected_g[8].min_num = H5E_NOTFOUND;

    expected_g[9].maj_num = H5E_SYM;
    expected_g[9].min_num = H5E_CALLBACK;

    expected_g[10].maj_num = H5E_LINK;
    expected_g[10].min_num = H5E_EXISTS;

    status = H5TS_mutex_init(&error_mutex_g, H5TS_MUTEX_TYPE_PLAIN);
    CHECK_I(status, "H5TS_mutex_init");

    def_fapl = H5Pcreate(H5P_FILE_ACCESS);
    CHECK(def_fapl, H5I_INVALID_HID, "H5Pcreate");

    status = H5Pget_vol_id(def_fapl, &vol_id);
    CHECK(status, FAIL, "H5Pget_vol_id");

    is_native = H5VL__is_native_connector_test(vol_id);
    CHECK(is_native, FAIL, "H5VL__is_native_connector_test");

    if (is_native) {
        /* Create a hdf5 file using H5F_ACC_TRUNC access, default file
         * creation plist and default file access plist
         */
        error_file_g = H5Fcreate(FILENAME, H5F_ACC_TRUNC, H5P_DEFAULT, def_fapl);
        CHECK(error_file_g, H5I_INVALID_HID, "H5Fcreate");

        for (i = 0; i < NUM_THREAD; i++)
            if (H5TS_thread_create(&threads[i], tts_error_thread, NULL) < 0)
                TestErrPrintf("thread # %d did not start", i);

        for (i = 0; i < NUM_THREAD; i++)
            if (H5TS_thread_join(threads[i], NULL) < 0)
                TestErrPrintf("thread %d failed to join", i);

        if (error_flag_g) {
            TestErrPrintf(
                "At least one thread reported a value that was different from the expected value\n");
            printf(
                "(Update the expected_g[] array in tts_error for this test if the error stack changed!)\n");
        }

        if (error_count_g != NUM_THREAD - 1)
            TestErrPrintf("Error: %d threads failed instead of %d\n", error_count_g, NUM_THREAD - 1);

        dataset = H5Dopen2(error_file_g, DATASETNAME, H5P_DEFAULT);
        CHECK(dataset, H5I_INVALID_HID, "H5Dopen2");

        status = H5Dread(dataset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, &value);
        CHECK(status, FAIL, "H5Dread");

        if (value != WRITE_NUMBER)
            TestErrPrintf("Error: Successful thread wrote value %d instead of %d\n", value, WRITE_NUMBER);

        status = H5Dclose(dataset);
        CHECK(status, FAIL, "H5Dclose");
        status = H5Fclose(error_file_g);
        CHECK(status, FAIL, "H5Fclose");
    } /* end if */
    else
        printf("Non-native VOL connector used, skipping test\n");

    status = H5Idec_ref(vol_id);
    CHECK(status, FAIL, "H5Idec_ref");

    status = H5TS_mutex_destroy(&error_mutex_g);
    CHECK_I(status, "H5TS_mutex_destroy");
} /* end tts_error() */

static H5TS_THREAD_RETURN_TYPE
tts_error_thread(void H5_ATTR_UNUSED *arg)
{
    hid_t       dataspace = H5I_INVALID_HID;
    hid_t       datatype  = H5I_INVALID_HID;
    hid_t       dataset   = H5I_INVALID_HID;
    hsize_t     dimsf[1]; /* dataset dimensions */
    H5E_auto2_t old_error_cb;
    void       *old_error_client_data;
    int         value;
    herr_t      status;

    /* preserve previous error stack handler */
    status = H5Eget_auto2(H5E_DEFAULT, &old_error_cb, &old_error_client_data);
    CHECK(status, FAIL, "H5Eget_auto2");

    /* set each thread's error stack handler */
    status = H5Eset_auto2(H5E_DEFAULT, error_callback, NULL);
    CHECK(status, FAIL, "H5Eset_auto2");

    /* 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(error_file_g, DATASETNAME, datatype, dataspace, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
    /* Most of these will fail, so don't check the error here */
    if (dataset >= 0) {
        value  = WRITE_NUMBER;
        status = H5Dwrite(dataset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, &value);
        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");

    /* turn our error stack handler off */
    status = H5Eset_auto2(H5E_DEFAULT, old_error_cb, old_error_client_data);
    CHECK(status, FAIL, "H5Eset_auto2");

    return (H5TS_thread_ret_t)0;
} /* end tts_error_thread() */

static herr_t
error_callback(hid_t H5_ATTR_UNUSED estack_id, void *client_data)
{
    H5TS_mutex_lock(&error_mutex_g);
    error_count_g++;
    H5TS_mutex_unlock(&error_mutex_g);
    return H5Ewalk2(H5E_DEFAULT, H5E_WALK_DOWNWARD, walk_error_callback, client_data);
}

static herr_t
walk_error_callback(unsigned n, const H5E_error2_t *err_desc, void H5_ATTR_UNUSED *client_data)
{
    hid_t maj_num = H5I_INVALID_HID;
    hid_t min_num = H5I_INVALID_HID;

    if (err_desc) {
        maj_num = err_desc->maj_num;
        min_num = err_desc->min_num;

        if (n <= EXPECTED_ERROR_DEPTH && maj_num == expected_g[n].maj_num && min_num == expected_g[n].min_num)
            return SUCCEED;
    }

    /* Unexpected error stack entry, print some info and set flag */
    fprintf(stderr, "Unexpected error stack entry!\n");
    fprintf(stderr, "Stack entry: %d\n", n);
    fprintf(stderr,
            "Actual: maj_num = %" PRIxHID ", min_num = %" PRIxHID
            ", line = %u, func = '%s', file = '%s', desc = '%s'\n",
            err_desc->maj_num, err_desc->min_num, err_desc->line, err_desc->func_name, err_desc->file_name,
            err_desc->desc);
    fprintf(stderr, "Expected: maj_num = %" PRIxHID ", min_num = %" PRIxHID "\n", expected_g[n].maj_num,
            expected_g[n].min_num);
    error_flag_g = -1;

    return SUCCEED;
}

void
cleanup_error(void H5_ATTR_UNUSED *params)
{
    if (GetTestCleanup()) {
        HDunlink(FILENAME);
    }
}

#endif /* H5_HAVE_THREADSAFE_API */
