Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions c/include/nnstreamer-single.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ int ml_single_open_full (ml_single_h *single, const char *model, const ml_tensor
* @details Note that this should be called before destroying the inference data by ml_tensors_data_destroy().
* If not, the inference engine might try to access the data that is already freed.
* And it causes the segmentation fault.
* An output buffer allocated by the neural network framework is handed back to it
* while closing, so such an output reports a NULL buffer of size 0 afterwards.
* @since_tizen 5.5
* @param[in] single The model handle to be closed.
* @return @c 0 on success. Otherwise a negative error value.
Expand Down
57 changes: 43 additions & 14 deletions c/src/ml-api-inference-single.c
Original file line number Diff line number Diff line change
Expand Up @@ -361,20 +361,57 @@ __destroy_notify (gpointer data_h, gpointer single_data)
{
ml_single *single_h;
ml_tensors_data_s *data;
gboolean fw_allocated;

data = (ml_tensors_data_s *) data_h;
single_h = (ml_single *) single_data;

/* the destroy callback is set only for the data allocated by the framework */
fw_allocated = (data->destroy != NULL);

if (G_LIKELY (single_h->filter)) {
if (single_h->klass->allocate_in_invoke (single_h->filter)) {
fw_allocated = single_h->klass->allocate_in_invoke (single_h->filter);

if (fw_allocated)
single_h->klass->destroy_notify (single_h->filter, data->tensors);
}

if (fw_allocated) {
guint i;

/* the buffers belong to the framework, they must not be freed again */
for (i = 0; i < data->num_tensors; i++) {
data->tensors[i].data = NULL;
data->tensors[i].size = 0;
}
}

/* reset callback function */
data->destroy = NULL;
}

/**
* @brief Releases the output data which may be registered in the destroy list.
* @note Do not call ml_tensors_data_destroy() on such data while holding
* single_h->mutex; its destroy callback takes the same mutex again.
*/
static void
__release_output_data (ml_single * single_h, ml_tensors_data_h output)
{
ml_tensors_data_s *data = (ml_tensors_data_s *) output;

if (!data)
return;

single_h->destroy_data_list =
g_list_remove (single_h->destroy_data_list, output);

if (data->destroy)
__destroy_notify (data, single_h);

ml_tensors_data_destroy (output);
}

/**
* @brief Wrapper function for __destroy_notify
*/
Expand Down Expand Up @@ -480,9 +517,7 @@ __process_output (ml_single * single_h, ml_tensors_data_h output)
* Caller of the invoke thread has returned back with timeout.
* So, free the memory allocated by the invoke as their is no receiver.
*/
single_h->destroy_data_list =
g_list_remove (single_h->destroy_data_list, output);
ml_tensors_data_destroy (output);
__release_output_data (single_h, output);
} else {
out_data = (ml_tensors_data_s *) output;
set_destroy_notify (single_h, out_data, FALSE);
Expand Down Expand Up @@ -544,11 +579,8 @@ invoke_thread (void *arg)
single_h->invoking = FALSE;

if (status != ML_ERROR_NONE || single_h->state == JOIN_REQUESTED) {
if (alloc_output) {
single_h->destroy_data_list =
g_list_remove (single_h->destroy_data_list, output);
ml_tensors_data_destroy (output);
}
if (alloc_output)
__release_output_data (single_h, output);

if (single_h->state == JOIN_REQUESTED)
goto exit;
Expand All @@ -573,11 +605,8 @@ invoke_thread (void *arg)
if (single_h->input)
ml_tensors_data_destroy (single_h->input);

if (alloc_output && single_h->output) {
single_h->destroy_data_list =
g_list_remove (single_h->destroy_data_list, single_h->output);
ml_tensors_data_destroy (single_h->output);
}
if (alloc_output && single_h->output)
__release_output_data (single_h, single_h->output);

single_h->input = single_h->output = NULL;
g_cond_broadcast (&single_h->cond);
Expand Down
1 change: 1 addition & 0 deletions debian/ml-api-unittests.install
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/usr/lib/nnstreamer/bin/unittest-ml/tests/unittest_capi*
/usr/lib/nnstreamer/bin/unittest-ml/tests/libml_api_customfilter*
/usr/lib/nnstreamer/bin/unittest-ml/tests/test_models
8 changes: 8 additions & 0 deletions tests/capi/meson.build
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# Custom filter allocating the output in invoke, used by the single-shot tests.
shared_library('ml_api_customfilter_slow_allocator',
'ml_api_customfilter_slow_allocator.c',
dependencies: [glib_dep, gst_dep, nnstreamer_dep],
install: get_option('install-test'),
install_dir: unittest_install_dir
)

unittest_capi_inference_single = executable('unittest_capi_inference_single',
'unittest_capi_inference_single.cc',
dependencies: [nns_capi_single_dep, gtest_dep],
Expand Down
113 changes: 113 additions & 0 deletions tests/capi/ml_api_customfilter_slow_allocator.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file ml_api_customfilter_slow_allocator.c
* @date 4 Sep 2026
* @brief Custom filter for the ML API unittests.
* @see https://github.com/nnstreamer/api
* @author MyungJoo Ham <myungjoo.ham@samsung.com>
* @bug No known bugs
*
* This copies the input into a buffer that the filter itself allocates, so a
* single-shot handle using it takes the "allocate_in_invoke" path. Each invoke
* deliberately takes longer than a short ml_single_set_timeout(), which lets
* the unittests reach the timeout handling without depending on the machine.
*/

#include <string.h>
#include <glib.h>
#include <tensor_filter_custom.h>
#include <nnstreamer_plugin_api.h>
#include <nnstreamer_util.h>

#define INVOKE_DELAY_USEC (200000U)

/**
* @brief init callback of tensor_filter custom
*/
static void *
pt_init (const GstTensorFilterProperties * prop)
{
UNUSED (prop);
return g_new0 (guint, 1);
}

/**
* @brief exit callback of tensor_filter custom
*/
static void
pt_exit (void *private_data, const GstTensorFilterProperties * prop)
{
UNUSED (prop);
g_free (private_data);
}

/**
* @brief setInputDimension callback of tensor_filter custom
*/
static int
set_inputDim (void *private_data, const GstTensorFilterProperties * prop,
const GstTensorsInfo * in_info, GstTensorsInfo * out_info)
{
UNUSED (private_data);
UNUSED (prop);

gst_tensors_info_copy (out_info, in_info);
return 0;
}

/**
* @brief allocate-invoke callback of tensor_filter custom
*/
static int
pt_allocate_invoke (void *private_data, const GstTensorFilterProperties * prop,
const GstTensorMemory * input, GstTensorMemory * output)
{
GstTensorsInfo *out_meta, *in_meta;
guint i;
UNUSED (private_data);

if (prop->input_meta.num_tensors != prop->output_meta.num_tensors)
return -1;

out_meta = (GstTensorsInfo *) & prop->output_meta;
in_meta = (GstTensorsInfo *) & prop->input_meta;

g_usleep (INVOKE_DELAY_USEC);

for (i = 0; i < out_meta->num_tensors; i++) {
GstTensorInfo *_out = gst_tensors_info_get_nth_info (out_meta, i);
GstTensorInfo *_in = gst_tensors_info_get_nth_info (in_meta, i);
gsize size = gst_tensor_info_get_size (_out);
gsize in_size = gst_tensor_info_get_size (_in);

output[i].data = g_malloc (size);
memcpy (output[i].data, input[i].data, MIN (size, in_size));
}

return 0;
}

/**
* @brief destroy-notify callback of tensor_filter custom
*/
static void
pt_destroy_notify (void *data)
{
g_free (data);
}

/**
* @brief tensor_filter custom subplugin definition
*/
static NNStreamer_custom_class NNStreamer_custom_body = {
.initfunc = pt_init,
.exitfunc = pt_exit,
.setInputDim = set_inputDim,
.allocate_invoke = pt_allocate_invoke,
.destroy_notify = pt_destroy_notify,
};

/* The dyn-loaded object */
NNStreamer_custom_class *NNStreamer_custom = &NNStreamer_custom_body;
Loading
Loading