#include "tensor_filter_single.h"
+#define ML_SINGLE_MAGIC 0xfeedfeed
+
/**
* @brief Default time to wait for an output in appsink (3 seconds).
*/
(ts).tv_nsec = ((msec) % 1000) * 1000000; \
} while (0)
+/** verify the magic number for ml_single obj */
+#define ML_SINGLE_MAGIC_CHECK(arg) do { \
+ if (arg->magic != ML_SINGLE_MAGIC) { \
+ ml_loge ("The given param, single is invalid."); \
+ return ML_ERROR_INVALID_PARAMETER; \
+ } \
+} while (0)
+
/* ML single api data structure for handle */
typedef struct
{
GTensorFilterSingle *filter; /**< tensor filter element */
ml_tensors_info_s in_info; /**< info about input */
ml_tensors_info_s out_info; /**< info about output */
+ guint magic; /**< code to verify valid handle */
pthread_t thread; /**< thread for invoking */
pthread_mutex_t mutex; /**< mutex for synchronization */
pthread_cond_t cond; /**< condition for synchronization */
ml_tensors_data_h input; /**< input received from user */
- ml_tensors_data_h * output; /**< output to be sent back to user */
+ ml_tensors_data_h *output; /**< output to be sent back to user */
struct timespec timeout; /**< timeout for invoking */
gboolean data_ready; /**< data is ready to be processed */
gboolean join; /**< thread should be joined */
* @brief thread to execute calls to invoke
*/
static void *
-invoke_thread (void * arg)
+invoke_thread (void *arg)
{
ml_single *single_h;
GTensorFilterSingleClass *klass;
for (i = 0; i < single_h->out_info.num_tensors; i++) {
/** memory will be allocated by tensor_filter_single */
out_tensors[i].data = NULL;
- out_tensors[i].size = ml_tensor_info_get_size (&single_h->out_info.info[i]);
+ out_tensors[i].size =
+ ml_tensor_info_get_size (&single_h->out_info.info[i]);
out_tensors[i].type = single_h->out_info.info[i].type;
}
pthread_mutex_unlock (&single_h->mutex);
}
/** loop over to wait for the next element */
-wait_for_next:
+ wait_for_next:
single_h->status = status;
single_h->data_ready = FALSE;
pthread_cond_broadcast (&single_h->cond);
exit:
single_h->data_ready = FALSE;
- pthread_cond_broadcast (&single_h->cond);
pthread_mutex_unlock (&single_h->mutex);
return NULL;
}
* @brief Set the info for input/output tensors
*/
static int
-ml_single_set_inout_tensors_info (GObject *object,
- const gchar *prefix, ml_tensors_info_s *tensors_info)
+ml_single_set_inout_tensors_info (GObject * object,
+ const gchar * prefix, ml_tensors_info_s * tensors_info)
{
int status = ML_ERROR_NONE;
GstTensorsInfo info;
/** Create ml_single object */
single_h = g_new0 (ml_single, 1);
g_assert (single_h);
+ single_h->magic = ML_SINGLE_MAGIC;
+
single_h->filter = g_object_new (G_TYPE_TENSOR_FILTER_SINGLE, NULL);
MSEC_TO_TIMESPEC (single_h->timeout, SINGLE_DEFAULT_TIMEOUT);
if (single_h->filter == NULL) {
ml_tensors_info_initialize (&single_h->in_info);
ml_tensors_info_initialize (&single_h->out_info);
- /* 5. Start the nnfw to egt inout configurations if needed */
+ /* 5. Start the nnfw to get inout configurations if needed */
klass = g_type_class_peek (G_TYPE_TENSOR_FILTER_SINGLE);
if (!klass) {
status = ML_ERROR_INVALID_PARAMETER;
/* 6. Set in/out configs and metadata */
if (in_tensors_info) {
/** set the tensors info here */
- if (!klass->input_configured(single_h->filter)) {
+ if (!klass->input_configured (single_h->filter)) {
status = ml_single_set_inout_tensors_info (filter_obj, "input",
in_tensors_info);
if (status != ML_ERROR_NONE)
if (out_tensors_info) {
/** set the tensors info here */
- if (!klass->output_configured(single_h->filter)) {
+ if (!klass->output_configured (single_h->filter)) {
status = ml_single_set_inout_tensors_info (filter_obj, "output",
out_tensors_info);
if (status != ML_ERROR_NONE)
single_h->data_ready = FALSE;
single_h->join = FALSE;
- if (pthread_create (&single_h->thread, NULL, invoke_thread, (void *)single_h) < 0) {
+ if (pthread_create (&single_h->thread, NULL, invoke_thread,
+ (void *) single_h) < 0) {
ml_loge ("Failed to create the invoke thread.");
status = ML_ERROR_UNKNOWN;
goto error;
}
single_h = (ml_single *) single;
+ ML_SINGLE_MAGIC_CHECK (single_h);
+ single_h->magic = 0;
pthread_mutex_lock (&single_h->mutex);
single_h->join = TRUE;
pthread_cond_broadcast (&single_h->cond);
pthread_mutex_unlock (&single_h->mutex);
pthread_join (single_h->thread, NULL);
+ /** locking ensures correctness with parallel calls on close */
if (single_h->filter) {
GTensorFilterSingleClass *klass;
klass = g_type_class_peek (G_TYPE_TENSOR_FILTER_SINGLE);
in_data = (ml_tensors_data_s *) input;
*output = NULL;
+ ML_SINGLE_MAGIC_CHECK (single_h);
if (!single_h->filter || single_h->join) {
ml_loge ("The given param is invalid, model is missing.");
return ML_ERROR_INVALID_PARAMETER;
status = ML_ERROR_TIMED_OUT;
/** This is set to notify invoke_thread to not process if timedout */
single_h->data_ready = FALSE;
- }
- else if (err == EPERM)
+ } else if (err == EPERM)
status = ML_ERROR_PERMISSION_DENIED;
else
status = ML_ERROR_INVALID_PARAMETER;
return ML_ERROR_INVALID_PARAMETER;
single_h = (ml_single *) single;
+ ML_SINGLE_MAGIC_CHECK (single_h);
/* allocate handle for tensors info */
ml_tensors_info_create (info);
return ML_ERROR_INVALID_PARAMETER;
single_h = (ml_single *) single;
+ ML_SINGLE_MAGIC_CHECK (single_h);
/* allocate handle for tensors info */
ml_tensors_info_create (info);
return ML_ERROR_INVALID_PARAMETER;
single_h = (ml_single *) single;
+ ML_SINGLE_MAGIC_CHECK (single_h);
+ pthread_mutex_lock (&single_h->mutex);
MSEC_TO_TIMESPEC (single_h->timeout, timeout);
+ pthread_mutex_unlock (&single_h->mutex);
+
return ML_ERROR_NONE;
}
#define ML_SINGLE_SUPPORT_TIMEOUT
#endif
+#define ML_SINGLE_MAGIC 0xfeedfeed
+
+/** verify the magic number for ml_single obj */
+#define ML_SINGLE_MAGIC_CHECK(arg) do { \
+ if (arg->magic != ML_SINGLE_MAGIC) { \
+ ml_loge ("The given param, single is invalid."); \
+ return ML_ERROR_INVALID_PARAMETER; \
+ } \
+} while (0)
+
/**
* @brief Default time to wait for an output in appsink (3 seconds).
*/
ml_tensors_info_s in_info;
ml_tensors_info_s out_info;
+ guint magic; /**< code to verify valid handle */
GMutex lock; /**< Lock for internal values */
gboolean clear_previous_buffer; /**< Previous buffer was timed out, need to clear old buffer */
- gboolean is_valid; /**< True if the single handle is valid */
guint timeout; /**< Timeout in milliseconds */
} ml_single;
single_h = g_new0 (ml_single, 1);
g_assert (single_h);
+ single_h->magic = ML_SINGLE_MAGIC;
single_h->pipe = pipe;
single_h->src = appsrc;
single_h->sink = appsink;
single_h->filter = filter;
single_h->timeout = SINGLE_DEFAULT_TIMEOUT;
single_h->clear_previous_buffer = FALSE;
- single_h->is_valid = FALSE;
ml_tensors_info_initialize (&single_h->in_info);
ml_tensors_info_initialize (&single_h->out_info);
g_mutex_init (&single_h->lock);
goto error;
}
- g_mutex_lock (&single_h->lock);
- single_h->is_valid = TRUE;
- g_mutex_unlock (&single_h->lock);
-
error:
if (status != ML_ERROR_NONE) {
ml_single_close (single_h);
}
single_h = (ml_single *) single;
- g_mutex_lock (&single_h->lock);
+ ML_SINGLE_MAGIC_CHECK (single_h);
- single_h->is_valid = FALSE;
+ single_h->magic = 0;
+ g_mutex_lock (&single_h->lock);
if (single_h->src) {
gst_object_unref (single_h->src);
}
single_h = (ml_single *) single;
+ ML_SINGLE_MAGIC_CHECK (single_h);
g_mutex_lock (&single_h->lock);
in_data = (ml_tensors_data_s *) input;
*output = NULL;
- if (!single_h->is_valid) {
- ml_loge ("The single handle is invalid.");
- status = ML_ERROR_INVALID_PARAMETER;
- goto done;
- }
-
/* Validate input data */
if (in_data->num_tensors != single_h->in_info.num_tensors) {
ml_loge ("The given param input is invalid, different number of memory blocks.");
if (!sample) {
ml_loge ("Failed to get the result from sink element.");
+#ifdef ML_SINGLE_SUPPORT_TIMEOUT
single_h->clear_previous_buffer = TRUE;
status = ML_ERROR_TIMED_OUT;
+#else
+ status = ML_ERROR_UNKNOWN;
+#endif
goto done;
}
return ML_ERROR_INVALID_PARAMETER;
single_h = (ml_single *) single;
+ ML_SINGLE_MAGIC_CHECK (single_h);
g_mutex_lock (&single_h->lock);
- if (!single_h->is_valid) {
- ml_loge ("The single handle is invalid.");
- status = ML_ERROR_INVALID_PARAMETER;
- } else {
- ml_single_get_tensors_info_from_filter (single_h->filter, is_input, info);
- }
+ ml_single_get_tensors_info_from_filter (single_h->filter, is_input, info);
g_mutex_unlock (&single_h->lock);
return status;
return ML_ERROR_INVALID_PARAMETER;
single_h = (ml_single *) single;
+ ML_SINGLE_MAGIC_CHECK (single_h);
g_mutex_lock (&single_h->lock);
- if (single_h->is_valid) {
- single_h->timeout = (guint) timeout;
- } else {
- ml_loge ("The single handle is invalid.");
- status = ML_ERROR_INVALID_PARAMETER;
- }
+ single_h->timeout = (guint) timeout;
g_mutex_unlock (&single_h->lock);
return status;
}
/**
+ * @brief Structure containing values to run single shot
+ */
+typedef struct {
+ gchar *test_model;
+ guint num_runs;
+ guint timeout;
+ guint min_time_to_run;
+ gboolean expect;
+ ml_single_h *single;
+} single_shot_thread_data;
+
+/**
+ * @brief Open and run on single shot API with provided data
+ */
+void * single_shot_loop_test (void * arg)
+{
+ guint i;
+ int status = ML_ERROR_NONE;
+ ml_single_h single;
+ single_shot_thread_data *ss_data = (single_shot_thread_data *) arg;
+
+ status = ml_single_open (&single, ss_data->test_model, NULL, NULL,
+ ML_NNFW_TYPE_TENSORFLOW_LITE, ML_NNFW_HW_ANY);
+ if (ss_data->expect)
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ ss_data->single = &single;
+
+ /* set timeout */
+ if (ss_data->timeout != 0) {
+ status = ml_single_set_timeout (single, ss_data->timeout);
+ if (ss_data->expect)
+ EXPECT_NE (status, ML_ERROR_INVALID_PARAMETER);
+ if (status == ML_ERROR_NOT_SUPPORTED)
+ ss_data->timeout = 0;
+ }
+
+ ml_tensors_info_h in_info;
+ ml_tensors_data_h input, output;
+ ml_tensor_dimension in_dim;
+
+ ml_tensors_info_create (&in_info);
+
+ in_dim[0] = 3;
+ in_dim[1] = 224;
+ in_dim[2] = 224;
+ in_dim[3] = 1;
+ ml_tensors_info_set_count (in_info, 1);
+ ml_tensors_info_set_tensor_type (in_info, 0, ML_TENSOR_TYPE_UINT8);
+ ml_tensors_info_set_tensor_dimension (in_info, 0, in_dim);
+
+ input = output = NULL;
+
+ /* generate dummy data */
+ status = ml_tensors_data_create (in_info, &input);
+ if (ss_data->expect) {
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ EXPECT_TRUE (input != NULL);
+ }
+
+ for (i=0; i<ss_data->num_runs; i++) {
+ status = ml_single_invoke (single, input, &output);
+ if (ss_data->expect) {
+ if (ss_data->timeout != 0 && ss_data->timeout < ss_data->min_time_to_run) {
+ EXPECT_EQ (status, ML_ERROR_TIMED_OUT);
+ EXPECT_TRUE (output == NULL);
+ } else {
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ EXPECT_TRUE (output != NULL);
+ }
+ }
+ output = NULL;
+ }
+
+ ml_tensors_data_destroy (output);
+ ml_tensors_data_destroy (input);
+ ml_tensors_info_destroy (in_info);
+
+ status = ml_single_close (single);
+ if (ss_data->expect)
+ EXPECT_EQ (status, ML_ERROR_NONE);
+
+ return NULL;
+}
+
+/**
* @brief Test NNStreamer single shot (tensorflow-lite)
* @detail Testcase with timeout.
*/
EXPECT_EQ (status, ML_ERROR_TIMED_OUT);
EXPECT_TRUE (output == NULL);
+ /* set timeout 5 s */
+ status = ml_single_set_timeout (single, 5000);
+
+ status = ml_single_invoke (single, input, &output);
+ EXPECT_EQ (status, ML_ERROR_NONE);
+ EXPECT_TRUE (output != NULL);
+
ml_tensors_data_destroy (output);
ml_tensors_data_destroy (input);
ml_tensors_info_destroy (in_info);
g_free (test_model);
}
+
+/**
+ * @brief Test NNStreamer single shot (tensorflow-lite)
+ * @detail Testcase with multiple runs in parallel. Some of the
+ * running instances will timeout, however others will not.
+ */
+TEST (nnstreamer_capi_singleshot, parallel_runs)
+{
+ const gchar *root_path = g_getenv ("NNSTREAMER_BUILD_ROOT_PATH");
+ gchar *test_model;
+ const gint num_threads = 3;
+ const gint num_cases = 3;
+ pthread_t thread[num_threads * num_cases];
+ single_shot_thread_data ss_data[num_cases];
+ guint i, j;
+
+ /* supposed to run test in build directory */
+ if (root_path == NULL)
+ root_path = "..";
+
+ test_model = g_build_filename (root_path, "tests", "test_models", "models",
+ "mobilenet_v1_1.0_224_quant.tflite", NULL);
+ ASSERT_TRUE (g_file_test (test_model, G_FILE_TEST_EXISTS));
+
+ for (i=0; i<num_cases; i++) {
+ ss_data[i].test_model = test_model;
+ ss_data[i].num_runs = 10;
+ ss_data[i].min_time_to_run = 10; /** 10 msec */
+ ss_data[i].expect = TRUE;
+ }
+
+ /** Default timeout runs */
+ ss_data[0].timeout = 0;
+ /** small timeout runs */
+ ss_data[1].timeout = 5;
+ /** large timeout runs */
+ ss_data[2].timeout = 10000;
+
+ /**
+ * make thread running things in background, each with different timeout,
+ * some fails, some runs, all opens pipelines by themselves in parallel
+ */
+ for (j=0; j<num_cases; j++) {
+ for (i=0; i<num_threads; i++) {
+ pthread_create (&thread[i+j * num_threads], NULL, single_shot_loop_test,
+ (void *) &ss_data[j]);
+ }
+ }
+
+ for (i=0; i<num_threads * num_cases; i++) {
+ pthread_join(thread[i], NULL);
+ }
+
+ g_free (test_model);
+}
+
+/**
+ * @brief Test NNStreamer single shot (tensorflow-lite)
+ * @detail Close the single handle while running. This test shuuld not crash.
+ * This closes the single handle twice, while opens it once
+ */
+TEST (nnstreamer_capi_singleshot, close_while_running)
+{
+ const gchar *root_path = g_getenv ("NNSTREAMER_BUILD_ROOT_PATH");
+ gchar *test_model;
+ pthread_t thread;
+ single_shot_thread_data ss_data;
+
+ /* supposed to run test in build directory */
+ if (root_path == NULL)
+ root_path = "..";
+
+ test_model = g_build_filename (root_path, "tests", "test_models", "models",
+ "mobilenet_v1_1.0_224_quant.tflite", NULL);
+ ASSERT_TRUE (g_file_test (test_model, G_FILE_TEST_EXISTS));
+
+ ss_data.test_model = test_model;
+ ss_data.num_runs = 10;
+ ss_data.min_time_to_run = 10; /** 10 msec */
+ ss_data.expect = FALSE;
+ ss_data.timeout = 3000;
+ ss_data.single = NULL;
+
+ pthread_create (&thread, NULL, single_shot_loop_test, (void *) &ss_data);
+
+ /** Start the thread and let the code start */
+ g_usleep (100000); /** 100 msec */
+
+ /**
+ * Call single API functions while its running. One run takes 100ms on average.
+ * So, these calls would in the middle of running and should not crash
+ * However, their status can be of failure, if the thread is closed earlier
+ */
+ if (ss_data.single) {
+ ml_single_set_timeout (*ss_data.single, ss_data.timeout);
+ ml_single_close (*ss_data.single);
+ }
+
+ pthread_join(thread, NULL);
+
+ g_free (test_model);
+}
+
#endif /* ENABLE_TENSORFLOW_LITE */
/**