[ML] Added SingleShot openModelAsync() implementation 08/253508/6
authorPiotr Kosko/Native/Web API (PLT) /SRPOL/Engineer/Samsung Electronics <p.kosko@samsung.com>
Fri, 12 Feb 2021 09:36:44 +0000 (10:36 +0100)
committerPiotr Kosko/Native/Web API (PLT) /SRPOL/Engineer/Samsung Electronics <p.kosko@samsung.com>
Wed, 17 Feb 2021 05:52:27 +0000 (06:52 +0100)
[ACR] https://code.sec.samsung.net/jira/browse/TWDAPI-273

[Verification] Code compiles without errors.
Checked in chrome console with few calls:
// Success calls:
tizen.ml.single.openModelAsync("documents/mobilenet_v1_1.0_224_quant.tflite", (s) => console.log(s))
tizen.ml.single.openModelAsync("documents/mobilenet_v1_1.0_224_quant.tflite", (s) => console.log(s), null, null, null, "TENSORFLOW_LITE")
tizen.ml.single.openModelAsync("documents/mobilenet_v1_1.0_224_quant.tflite", (s) => console.log(s), null, null, null, "TENSORFLOW_LITE", "ANY", true)

// Fails for invalid or not-existing file:
tizen.ml.single.openModelAsync("documents/fail.tflite", (s) => console.log(s), (e) => console.log(e)) // Abort Error
tizen.ml.single.openModelAsync("documents/notexisting", (s) => console.log(s), (e) => console.log(e)) // NotFound
tizen.ml.single.openModelAsync("notexisting", (s) => console.log(s), (e) => console.log(e)) // NotFound

// ASYNC version verification //
// without storage privilege
tizen.ml.single.openModelAsync("notexistingfile", (s) => console.log(s), (e) => console.log("Async: " + JSON.stringify(e)))   // async NotFoundError
tizen.ml.single.openModelAsync("/dddd/notexistingfile", (s) => console.log(s), (e) => console.log("Async: " + JSON.stringify(e)))   // async NotFoundError
tizen.ml.single.openModelAsync("documents/notexistingfile", (s) => console.log(s), (e) => console.log("Async: " + JSON.stringify(e)))   // throws SecurityError
tizen.ml.single.openModelAsync("documents/mobilenet_v1_1.0_224_quant.tflite", (s) => console.log(s), (e) => console.log("Async: " + JSON.stringify(e)))   // throws SecurityError
// with storage privilege
tizen.ml.single.openModelAsync("notexistingfile", (s) => console.log(s), (e) => console.log("Async: " + JSON.stringify(e)))   // async NotFoundError
tizen.ml.single.openModelAsync("/dddd/notexistingfile", (s) => console.log(s), (e) => console.log("Async: " + JSON.stringify(e)))   // async NotFoundError
tizen.ml.single.openModelAsync("documents/notexistingfile", (s) => console.log(s), (e) => console.log("Async: " + JSON.stringify(e)))   // async NotFoundError
tizen.ml.single.openModelAsync("documents/mobilenet_v1_1.0_224_quant.tflite", (s) => console.log(s), (e) => console.log("Async: " + JSON.stringify(e)))   // success

// SYNC version verification //
// without storage privilege
tizen.ml.single.openModel("notexistingfile")   // throws NotFoundError
tizen.ml.single.openModel("/dddd/notexistingfile")   // throws NotFoundError
tizen.ml.single.openModel("documents/notexistingfile")   // throws SecurityError
tizen.ml.single.openModel("documents/mobilenet_v1_1.0_224_quant.tflite")   // throws SecurityError

// with storage privilege
tizen.ml.single.openModel("notexistingfile")   // throws NotFoundError
tizen.ml.single.openModel("/dddd/notexistingfile")   // throws NotFoundError
tizen.ml.single.openModel("documents/notexistingfile")   // throws NotFoundError
tizen.ml.single.openModel("documents/mobilenet_v1_1.0_224_quant.tflite")   // success

Change-Id: I93e009efec4e1bcbe6d6c3b381b34cada910d8aa

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

index 503d1b1..d516997 100755 (executable)
@@ -93,8 +93,108 @@ MachineLearningSingle.prototype.openModel = function() {
 };
 
 // MachineLearningSingle::openModelAsync()
+var ValidOpenModelAsyncCallbackErrors = [
+    'InvalidValuesError',
+    'NotFoundError',
+    'NotSupportedError',
+    'AbortError'
+];
+var ValidOpenModelAsyncExceptions = ['TypeMismatchError', 'SecurityError', 'AbortError'];
+MachineLearningSingle.prototype.openModelAsync = function() {
+    var args = validator_.validateArgs(arguments, [
+        {
+            name: 'modelPath',
+            type: types_.STRING
+        },
+        {
+            name: 'successCallback',
+            type: types_.FUNCTION
+        },
+        {
+            name: 'errorCallback',
+            type: types_.FUNCTION,
+            optional: true,
+            nullable: true
+        },
+        {
+            name: 'inTensorsInfo',
+            type: types_.PLATFORM_OBJECT,
+            values: TensorsInfo,
+            optional: true,
+            nullable: true
+        },
+        {
+            name: 'outTensorsInfo',
+            type: types_.PLATFORM_OBJECT,
+            values: TensorsInfo,
+            optional: true,
+            nullable: true
+        },
+        {
+            name: 'fwType',
+            type: types_.ENUM,
+            values: Object.keys(NNFWType),
+            optional: true
+        },
+        {
+            name: 'hwType',
+            type: types_.ENUM,
+            values: Object.keys(HWType),
+            optional: true
+        },
+        {
+            name: 'isDynamicMode',
+            type: types_.BOOLEAN,
+            optional: true
+        }
+    ]);
+    try {
+        // get normalized URI of a file
+        args.modelPath = tizen.filesystem.toURI(args.modelPath);
+    } catch (e) {
+        setTimeout(function() {
+            native_.callIfPossible(
+                args.errorCallback,
+                new WebAPIException(WebAPIException.NOT_FOUND_ERR, 'Path is invalid')
+            );
+        }, 0);
+        return;
+    }
 
-// OpenModelSuccessCallback
+    var nativeArgs = {
+        modelPath: args.modelPath,
+        inTensorsInfo: args.inTensorsInfo ? args.inTensorsInfo._id : NO_ID,
+        outTensorsInfo: args.outTensorsInfo ? args.outTensorsInfo._id : NO_ID,
+        fwType: args.fwType ? args.fwType : 'ANY',
+        hwType: args.hwType ? args.hwType : 'ANY',
+        isDynamicMode: args.isDynamicMode ? args.isDynamicMode : false,
+        async: true
+    };
+
+    var callback = function(result) {
+        if (native_.isFailure(result)) {
+            native_.callIfPossible(
+                args.errorCallback,
+                native_.getErrorObjectAndValidate(
+                    result,
+                    ValidOpenModelAsyncCallbackErrors,
+                    AbortError
+                )
+            );
+        } else {
+            args.successCallback(new SingleShot(result.id));
+        }
+    };
+
+    var result = native_.call('MLSingleManagerOpenModel', nativeArgs, callback);
+    if (native_.isFailure(result)) {
+        throw native_.getErrorObjectAndValidate(
+            result,
+            ValidOpenModelAsyncExceptions,
+            AbortError
+        );
+    }
+};
 
 // SingleShot interface (input & output)
 var ValidInputExceptions = ['TypeMismatchError', 'AbortError'];
index 7c48902..aee61d0 100644 (file)
@@ -117,8 +117,6 @@ MlInstance::MlInstance()
 
   // Single API begin
   REGISTER_METHOD(MLSingleManagerOpenModel);
-  // MachineLearningSingle::openModelAsync()
-  // OpenModelSuccessCallback
   REGISTER_METHOD(MLSingleShotGetTensorsInfo);
   REGISTER_METHOD(MLSingleShotSetInputInfo);
   // SingleShot::invoke()
@@ -154,6 +152,7 @@ MlInstance::MlInstance()
 
 MlInstance::~MlInstance() {
   ScopeLogger();
+  worker_.stop();
 }
 
 TensorsInfoManager& MlInstance::GetTensorsInfoManager() {
@@ -187,7 +186,7 @@ void MlInstance::MLTensorsInfoCreate(const picojson::value& args, picojson::obje
                       ("Could not create new TensorsInfo handle"));
     return;
   }
-  out["id"] = picojson::value(static_cast<double>(tensorsInfo->Id()));
+  out[kId] = picojson::value(static_cast<double>(tensorsInfo->Id()));
   ReportSuccess(out);
 }
 
@@ -730,6 +729,8 @@ const std::string kFwType = "fwType";
 const std::string kHwType = "hwType";
 const std::string kIsDynamicMode = "isDynamicMode";
 const std::string kValue = "value";
+const std::string kCallbackId = "callbackId";
+const std::string kAsync = "async";
 
 const int kNoId = -1;
 }  //  namespace
@@ -744,13 +745,7 @@ void MlInstance::MLSingleManagerOpenModel(const picojson::value& args, picojson:
   CHECK_ARGS(args, kIsDynamicMode, bool, out);
 
   const auto& model_path = common::tools::ConvertUriToPath(args.get(kModelPath).get<std::string>());
-  PlatformResult result = common::tools::CheckFileAvailability(model_path);
-  if (!result) {
-    LogAndReportError(
-        PlatformResult(ErrorCode::NOT_FOUND_ERR, "File does not exist or is not accessible"), &out,
-        ("File does not exist or is not accessible: %s", model_path.c_str()));
-    return;
-  }
+  CHECK_STORAGE_ACCESS(model_path, &out);
 
   TensorsInfo* in_tensors_info = nullptr;
   auto inTensorId = static_cast<int>(args.get(kInTensorsInfo).get<double>());
@@ -775,7 +770,8 @@ void MlInstance::MLSingleManagerOpenModel(const picojson::value& args, picojson:
   }
 
   ml_nnfw_type_e nnfw_e = ML_NNFW_TYPE_ANY;
-  result = types::NNFWTypeEnum.getValue(args.get(kFwType).get<std::string>(), &nnfw_e);
+  PlatformResult result =
+      types::NNFWTypeEnum.getValue(args.get(kFwType).get<std::string>(), &nnfw_e);
   if (!result) {
     LogAndReportError(result, &out);
     return;
@@ -790,21 +786,49 @@ void MlInstance::MLSingleManagerOpenModel(const picojson::value& args, picojson:
 
   auto is_dynamic_mode = args.get(kIsDynamicMode).get<bool>();
 
-  int res_id = -1;
-  result = single_manager_.OpenModel(model_path, in_tensors_info, out_tensors_info, nnfw_e, hw_e,
-                                     is_dynamic_mode, &res_id);
-  if (!result) {
-    ReportError(result, &out);
-    return;
-  }
+  auto logic = [this, model_path, in_tensors_info, out_tensors_info, nnfw_e, hw_e,
+                is_dynamic_mode](decltype(out) out) {
+    PlatformResult result = common::tools::CheckFileAvailability(model_path);
+    if (!result) {
+      LogAndReportError(
+          PlatformResult(ErrorCode::NOT_FOUND_ERR, "File does not exist or is not accessible"),
+          &out, ("File does not exist or is not accessible: %s", model_path.c_str()));
+      return;
+    }
 
-  out["id"] = picojson::value(static_cast<double>(res_id));
-  ReportSuccess(out);
-}
+    int res_id = -1;
+    result = single_manager_.OpenModel(model_path, in_tensors_info, out_tensors_info, nnfw_e, hw_e,
+                                       is_dynamic_mode, &res_id);
+    if (!result) {
+      ReportError(result, &out);
+      return;
+    }
 
-// MachineLearningSingle::openModelAsync()
+    out[kId] = picojson::value(static_cast<double>(res_id));
+    ReportSuccess(out);
+  };
 
-// OpenModelSuccessCallback
+  bool async =
+      (args.contains(kAsync) && args.get(kAsync).is<bool>()) ? args.get(kAsync).get<bool>() : false;
+
+  if (!async) {
+    logic(out);
+  } else {
+    // Async logic
+    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);
+  }
+}
 
 // SingleShot input/output
 // TODO move to the up section with field names
@@ -827,7 +851,7 @@ void MlInstance::MLSingleShotGetTensorsInfo(const picojson::value& args, picojso
     return;
   }
 
-  out["id"] = picojson::value(static_cast<double>(res_id));
+  out[kId] = picojson::value(static_cast<double>(res_id));
   ReportSuccess(out);
 }
 
index eaa7cfc..a44ce69 100644 (file)
@@ -18,6 +18,7 @@
 #define ML_ML_INSTANCE_H_
 
 #include "common/extension.h"
+#include "common/worker.h"
 
 #include "ml/ml_pipeline_manager.h"
 #include "ml/ml_single_manager.h"
@@ -66,13 +67,12 @@ class MlInstance : public common::ParsedInstance {
    */
   TensorsInfoManager tensors_info_manager_;
   TensorsDataManager tensors_data_manager_;
+  common::Worker worker_;
   // Common ML API end
 
   // Single API begin
   SingleManager single_manager_;
   void MLSingleManagerOpenModel(const picojson::value& args, picojson::object& out);
-  // MachineLearningSingle::openModelAsync()
-  // OpenModelSuccessCallback
   void MLSingleShotGetTensorsInfo(const picojson::value& args, picojson::object& out);
   void MLSingleShotSetInputInfo(const picojson::value& args, picojson::object& out);
   // SingleShot::invoke()
index e21eaf2..68c262b 100644 (file)
@@ -48,18 +48,18 @@ PlatformResult SingleManager::OpenModel(const std::string& modelPath, TensorsInf
     return util::ToPlatformResult(ret, "Failed to open model");
   }
 
+  std::lock_guard<std::mutex> singles_lock(singles_mutex_);
   int id = nextId_++;
   singles_[id] = std::make_unique<SingleShot>(id, handle);
   *res_id = id;
   return PlatformResult{};
 }
 
-// MachineLearningSingle::openModelAsync()
-// OpenModelSuccessCallback
 // SingleShot input
 SingleShot* SingleManager::GetSingleShot(int id) {
   ScopeLogger("id: %d", id);
 
+  std::lock_guard<std::mutex> singles_lock(singles_mutex_);
   if (singles_.end() != singles_.find(id)) {
     return singles_[id].get();
   }
index 3482c3e..ad232aa 100644 (file)
@@ -17,6 +17,8 @@
 #ifndef ML_ML_SINGLE_MANAGER_H_
 #define ML_ML_SINGLE_MANAGER_H_
 
+#include <mutex>
+
 #include "common/platform_result.h"
 
 #include "ml_singleshot.h"
@@ -54,6 +56,7 @@ class SingleManager {
  private:
   int nextId_;
   std::map<int, std::unique_ptr<SingleShot>> singles_;
+  std::mutex singles_mutex_;
   TensorsInfoManager* tim_;
   SingleShot* GetSingleShot(int id);
 };