[ML][Single] Added InvokeAsync() implementation 20/260420/6
authorPiotr Kosko/Tizen API (PLT) /SRPOL/Engineer/Samsung Electronics <p.kosko@samsung.com>
Wed, 23 Jun 2021 11:32:22 +0000 (13:32 +0200)
committerPiotr Kosko/Tizen API (PLT) /SRPOL/Engineer/Samsung Electronics <p.kosko@samsung.com>
Fri, 25 Jun 2021 06:42:43 +0000 (08:42 +0200)
[ACR] https://code.sec.samsung.net/jira/browse/TWDAPI-278

[Verification] Code compiles without errors.
TCT passrate of existing APIs didn't change.

Checked in chrome console with below snippets:
// initialize test data
model = tizen.ml.single.openModel("documents/model.tflite");

var tensorsInfo = new tizen.ml.TensorsInfo();
tensorsInfo.addTensorInfo("tensor", "UINT8", [3, 224, 224]);
var tensorsData = tensorsInfo.getTensorsData();

var tensorsInfoInvalid = new tizen.ml.TensorsInfo();
tensorsInfoInvalid.addTensorInfo("tensor", "UINT8", [3, 125, 125]);
var tensorsDataInvalid = tensorsInfoInvalid.getTensorsData();

function errorCallback(error) {
  console.log(error);
}

function successCallback(tensorsDataOut) {
  console.log("Inference finished successfully");
  console.log(tensorsDataOut.getTensorRawData(0));
  tensorsDataOut.dispose();
}

// success
// test1
model.invokeAsync(tensorsData, successCallback, errorCallback);
// test2
model.invokeAsync(tensorsData, successCallback);

// errors
// test3
model.invokeAsync(tensorsData);  // TypeMismatchError - sync
// test4
model.invokeAsync(null, successCallback);  // TypeMismatchError - sync
//  test5
model.invokeAsync(tensorsDataInvalid, successCallback, errorCallback);  // AbortError - async
// test6
model.setTimeout(1)
model.invokeAsync(tensorsData, successCallback, errorCallback);  // TimeoutError - async

// clear tensorsData
tensorsData.dispose();
tensorsInfo.dispose();

// test7 - use of disposed tesnsorsData
model.invokeAsync(tensorsData, successCallback, errorCallback);  // AbortError - sync

// clear other data
tensorsDataInvalid.dispose();
tensorsInfoInvalid.dispose();
model.close();

Change-Id: I59900d7bab9a76939e27d68cb2bcd5434f446b3d

src/ml/js/ml_single.js
src/ml/ml_instance.cc

index b9598d7..1ecbaf6 100755 (executable)
@@ -331,6 +331,57 @@ SingleShot.prototype.invoke = function() {
     return new TensorsData(result.tensorsDataId, result.tensorsInfoId);
 };
 
+var ValidInvokeAsyncErrors = ['TimeoutError', 'NotSupportedError', 'AbortError'];
+SingleShot.prototype.invokeAsync = function() {
+    var args = validator_.validateArgs(arguments, [
+        {
+            name: 'inTensorsData',
+            type: types_.PLATFORM_OBJECT,
+            values: TensorsData
+        },
+        {
+            name: 'successCallback',
+            type: types_.FUNCTION
+        },
+        { name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true }
+    ]);
+
+    var callback = function(result) {
+        if (native_.isFailure(result)) {
+            setTimeout(function() {
+                native_.callIfPossible(
+                    args.errorCallback,
+                    native_.getErrorObjectAndValidate(
+                        result,
+                        ValidInvokeAsyncErrors,
+                        AbortError
+                    )
+                );
+            }, 0);
+        } else {
+            native_.callIfPossible(
+                args.successCallback,
+                new TensorsData(result.tensorsDataId, result.tensorsInfoId)
+            );
+        }
+    };
+
+    var nativeArgs = {
+        id: this._id,
+        tensorsDataId: args.inTensorsData._id,
+        async: true
+    };
+
+    var result = native_.call('MLSingleShotInvoke', nativeArgs, callback);
+    if (native_.isFailure(result)) {
+        throw native_.getErrorObjectAndValidate(
+            result,
+            ValidInvokeAsyncErrors,
+            AbortError
+        );
+    }
+};
+
 var GetSetValueValidExceptions = [
     'AbortError',
     'InvalidValuesError',
index c6be541..5cd456c 100644 (file)
@@ -889,24 +889,56 @@ void MlInstance::MLSingleShotInvoke(const picojson::value& args, picojson::objec
 
   int id = static_cast<int>(args.get(kId).get<double>());
   int tensors_data_id = static_cast<int>(args.get(kTensorsDataId).get<double>());
+  bool async =
+      (args.contains(kAsync) && args.get(kAsync).is<bool>()) ? args.get(kAsync).get<bool>() : false;
 
   TensorsData* in_tensors_data = GetTensorsDataManager().GetTensorsData(tensors_data_id);
+  if (async && in_tensors_data) {
+    // in case of async flow need to prevent destroying entry data during invoke
+    // from JS, creation of a copy
+    in_tensors_data = GetTensorsInfoManager().CreateTensorsData(in_tensors_data->GetTensorsInfo());
+  }
   if (nullptr == in_tensors_data) {
     LogAndReportError(PlatformResult(ErrorCode::ABORT_ERR, "Internal TensorsData error"), &out,
                       ("Could not find TensorsData handle with given id: %d", tensors_data_id));
     return;
   }
 
-  TensorsData* out_tensors_data = nullptr;
-  auto ret = single_manager_.Invoke(id, in_tensors_data, &out_tensors_data);
-  if (!ret) {
-    ReportError(ret, &out);
-    return;
-  }
+  auto logic = [this, id, tensors_data_id, in_tensors_data, async](decltype(out) out) {
+    TensorsData* out_tensors_data = nullptr;
+    auto ret = single_manager_.Invoke(id, in_tensors_data, &out_tensors_data);
+    if (async) {
+      // in case of async flow, the in_tensor_data with underlying TensorsInfo
+      // was copied, thus need to be released here
+      GetTensorsInfoManager().DisposeTensorsInfo(in_tensors_data->GetTensorsInfo());
+      GetTensorsDataManager().DisposeTensorsData(in_tensors_data);
+    }
+    if (!ret) {
+      ReportError(ret, &out);
+      return;
+    }
 
-  out[kTensorsDataId] = picojson::value(static_cast<double>(out_tensors_data->Id()));
-  out[kTensorsInfoId] = picojson::value(static_cast<double>(out_tensors_data->TensorsInfoId()));
-  ReportSuccess(out);
+    out[kTensorsDataId] = picojson::value(static_cast<double>(out_tensors_data->Id()));
+    out[kTensorsInfoId] = picojson::value(static_cast<double>(out_tensors_data->TensorsInfoId()));
+    ReportSuccess(out);
+  };
+
+  if (!async) {
+    logic(out);
+  } else {
+    CHECK_ARGS(args, kCallbackId, double, out);
+    double callback_id = args.get(kCallbackId).get<double>();
+    this->worker_.add_job([this, callback_id, logic] {
+      picojson::value response = picojson::value(picojson::object());
+      picojson::object& async_out = response.get<picojson::object>();
+      async_out[kCallbackId] = picojson::value(callback_id);
+      logic(async_out);
+      this->PostMessage(response.serialize().c_str());
+    });
+
+    // Sync return
+    ReportSuccess(out);
+  }
 }
 
 void MlInstance::MLSingleShotGetValue(const picojson::value& args, picojson::object& out) {