1 /*
2  * Copyright (c) 2021 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #include <uv.h>
16 #include "js_napi_common.h"
17 #include "napi/native_api.h"
18 #include "napi/native_common.h"
19 #include "napi/native_node_api.h"
20 #include "utils/log.h"
21 namespace ACE {
22 namespace NAPI {
23 namespace SYSTEM_TEST_NAPI {
24 static constexpr int ARRAY_LENGTH = 10;
25 static constexpr int MAX_QUEUE_SIZE = 2;
26 static constexpr int MAX_THREAD_SIZE = 2;
27 static constexpr int THREAD_ARG_TWO = 2;
28 static constexpr int THREAD_ARG_THREE = 3;
29 static constexpr int THREAD_ARG_FOUR = 4;
30 
31 static uv_thread_t uv_threads[MAX_THREAD_SIZE];
32 static napi_threadsafe_function tsfun;
33 
34 struct TS_FN_HINT {
35     napi_threadsafe_function_call_mode blockOnFull = napi_tsfn_blocking;
36     napi_threadsafe_function_release_mode abort = napi_tsfn_abort;
37     bool startSecondary = false;
38     napi_ref jsFinalizeCallBackRef = nullptr;
39     uint32_t maxQueueSize = 0;
40 };
41 using TsFnHint = struct TS_FN_HINT;
42 
43 static TsFnHint tsinfo;
44 
45 // Thread data to transmit to JS
46 static int transmitData[ARRAY_LENGTH];
47 static napi_ref testCallbackRef[ARRAY_LENGTH] = { nullptr };
48 
ReleaseThreadsafeFunction(void * data)49 static void ReleaseThreadsafeFunction(void* data)
50 {
51     HILOG_INFO("%{public}s,called", __func__);
52     napi_threadsafe_function tsfun = static_cast<napi_threadsafe_function>(data);
53 
54     if (napi_release_threadsafe_function(tsfun, napi_tsfn_release) != napi_ok) {
55         napi_fatal_error("ReleaseThreadsafeFunction",
56             NAPI_AUTO_LENGTH, "napi_release_threadsafe_function failed", NAPI_AUTO_LENGTH);
57     }
58 }
59 static napi_env gCallEnv = nullptr;
60 // Source thread producing the data
DataSourceThread(void * data)61 static void DataSourceThread(void* data)
62 {
63     HILOG_INFO("%{public}s,called start", __func__);
64     napi_env env = gCallEnv;
65     napi_threadsafe_function tsfun = static_cast<napi_threadsafe_function>(data);
66     void* hint = nullptr;
67     bool queueWasFull = false, queueWasClosing = false;
68     NAPI_CALL_RETURN_VOID(env, napi_get_threadsafe_function_context(tsfun, &hint));
69 
70     TsFnHint* tsFnInfo = static_cast<TsFnHint*>(hint);
71     if (tsFnInfo != &tsinfo) {
72         napi_fatal_error("DataSourceThread", NAPI_AUTO_LENGTH,
73             "thread-safe function hint is not as expected", NAPI_AUTO_LENGTH);
74     }
75     if (tsFnInfo->startSecondary) {
76         NAPI_CALL_RETURN_VOID(env, napi_acquire_threadsafe_function(tsfun));
77         if (uv_thread_create(&uv_threads[1], ReleaseThreadsafeFunction, tsfun) != 0) {
78             napi_fatal_error("DataSourceThread", NAPI_AUTO_LENGTH,
79                 "failed to start secondary thread", NAPI_AUTO_LENGTH);
80         }
81     }
82     for (int index = ARRAY_LENGTH - 1; index > -1 && !queueWasClosing; index--) {
83         auto status = napi_call_threadsafe_function(tsfun, &transmitData[index], tsFnInfo->blockOnFull);
84         std::string statusStr;
85         switch (status) {
86             case napi_queue_full:
87                 queueWasFull = true;
88                 index++;
89                 statusStr = "napi_queue_full";
90                 break;
91             case napi_ok:
92                 statusStr = "napi_ok";
93                 continue;
94             case napi_closing:
95                 statusStr = "napi_closing";
96                 queueWasClosing = true;
97                 break;
98             default:
99                 napi_fatal_error("DataSourceThread", NAPI_AUTO_LENGTH,
100                     "napi_call_threadsafe_function failed", NAPI_AUTO_LENGTH);
101         }
102         HILOG_INFO("%{public}s,called napi_call_threadsafe_function index = %{public}d, status =%{public}s",
103             __func__, index, statusStr.c_str());
104     }
105     if (!queueWasClosing && napi_release_threadsafe_function(tsfun, napi_tsfn_release) != napi_ok) {
106         napi_fatal_error(
107             "DataSourceThread", NAPI_AUTO_LENGTH, "napi_release_threadsafe_function failed", NAPI_AUTO_LENGTH);
108     }
109     HILOG_INFO("%{public}s,called end", __func__);
110 }
111 
112 // Getting the data into JS
CallJsFuntion(napi_env env,napi_value cb,void * hint,void * data)113 static void CallJsFuntion(napi_env env, napi_value cb, void* hint, void* data)
114 {
115     HILOG_INFO("%{public}s called", __func__);
116     if (!(env == nullptr || cb == nullptr)) {
117         napi_value argv = nullptr;
118         NAPI_CALL_RETURN_VOID(env, napi_create_int32(env, *(int*)data, &argv));
119 
120         for (int i = ARRAY_LENGTH - 1; i >= 0; i--) {
121             if (testCallbackRef[i]) {
122                 napi_value callback = 0, undefined = nullptr, result = nullptr;
123                 napi_get_undefined(env, &undefined);
124                 napi_get_reference_value(env, testCallbackRef[i], &callback);
125                 napi_call_function(env, undefined, callback, 1, &argv, &result);
126                 napi_delete_reference(env, testCallbackRef[i]);
127                 testCallbackRef[i] = nullptr;
128                 break;
129             }
130         }
131     }
132 }
133 
134 static napi_ref altRef = nullptr;
135 
136 // Cleanup Param:jsFinalizeCallBack, abort
StopThread(napi_env env,napi_callback_info info)137 static napi_value StopThread(napi_env env, napi_callback_info info)
138 {
139     HILOG_INFO("%{public}s,called", __func__);
140 
141     uv_thread_join(&uv_threads[0]);
142     if (tsinfo.startSecondary) {
143         uv_thread_join(&uv_threads[1]);
144     }
145 
146     size_t argc = THREAD_ARG_TWO;
147     napi_value argv[THREAD_ARG_TWO] = { nullptr };
148     NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
149     napi_valuetype value_type;
150     NAPI_CALL(env, napi_typeof(env, argv[0], &value_type));
151     NAPI_ASSERT(env, value_type == napi_function, "StopThread argument is a function");
152     NAPI_ASSERT(env, (tsfun != nullptr), "Existing threadsafe function");
153     NAPI_CALL(env, napi_create_reference(env, argv[0], 1, &(tsinfo.jsFinalizeCallBackRef)));
154     bool abort = false;
155     NAPI_CALL(env, napi_get_value_bool(env, argv[1], &abort));
156     NAPI_CALL(env, napi_release_threadsafe_function(tsfun, abort ? napi_tsfn_abort : napi_tsfn_release));
157     tsfun = nullptr;
158     return nullptr;
159 }
160 
161 // Join the thread and inform JS that we're done.
FinalizeCallBack(napi_env env,void * data,void * hint)162 static void FinalizeCallBack(napi_env env, void* data, void* hint)
163 {
164     HILOG_INFO("%{public}s,called", __func__);
165 
166     TsFnHint* theHint = static_cast<TsFnHint*>(hint);
167     napi_value jsCallback = nullptr, undefined = nullptr, result = nullptr;
168 
169     NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, theHint->jsFinalizeCallBackRef, &jsCallback));
170     NAPI_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined));
171     NAPI_CALL_RETURN_VOID(env, napi_call_function(env, undefined, jsCallback, 0, nullptr, &result));
172     NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, theHint->jsFinalizeCallBackRef));
173     if (altRef != nullptr) {
174         NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, altRef));
175         altRef = nullptr;
176     }
177 }
178 // jsfunc, abort/release, startSecondary, maxQueueSize
StartThreadInternal(napi_env env,napi_callback_info info,napi_threadsafe_function_call_js cb,bool blockOnFull,bool altRefJSCallBack)179 static napi_value StartThreadInternal(napi_env env, napi_callback_info info,
180     napi_threadsafe_function_call_js cb, bool blockOnFull, bool altRefJSCallBack)
181 {
182     HILOG_INFO("%{public}s,called start", __func__);
183     gCallEnv = env;
184     size_t argc = THREAD_ARG_FOUR;
185     napi_value argv[THREAD_ARG_FOUR] = { 0 };
186 
187     NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
188     if (altRefJSCallBack) {
189         NAPI_CALL(env, napi_create_reference(env, argv[0], 1, &altRef));
190         argv[0] = nullptr;
191     }
192 
193     for (int i = 0; i<ARRAY_LENGTH; i++) {
194         napi_create_reference(env, argv[0], 1, &testCallbackRef[i]);
195     }
196 
197     tsinfo.blockOnFull = (blockOnFull ? napi_tsfn_blocking : napi_tsfn_nonblocking);
198 
199     NAPI_ASSERT(env, (tsfun == nullptr), "Existing thread-safe function");
200     napi_value asyncName;
201     NAPI_CALL(env, napi_create_string_utf8(env, "N-API Thread-safe Function Test", NAPI_AUTO_LENGTH, &asyncName));
202     NAPI_CALL(env, napi_get_value_uint32(env, argv[THREAD_ARG_THREE], &tsinfo.maxQueueSize));
203 
204     NAPI_CALL(env, napi_create_threadsafe_function(env, argv[0], nullptr, asyncName, tsinfo.maxQueueSize,
205         MAX_THREAD_SIZE, uv_threads, FinalizeCallBack, &tsinfo, cb, &tsfun));
206     bool abort = false;
207     NAPI_CALL(env, napi_get_value_bool(env, argv[1], &abort));
208     tsinfo.abort = abort ? napi_tsfn_abort : napi_tsfn_release;
209     NAPI_CALL(env, napi_get_value_bool(env, argv[THREAD_ARG_TWO], &(tsinfo.startSecondary)));
210 
211     NAPI_ASSERT(env, (uv_thread_create(&uv_threads[0], DataSourceThread, tsfun) == 0), "Thread creation");
212     HILOG_INFO("%{public}s,called end", __func__);
213     return nullptr;
214 }
215 
216 // Startup  param: jsfunc, abort/release, startSecondary, maxQueueSize
StartThread(napi_env env,napi_callback_info info)217 static napi_value StartThread(napi_env env, napi_callback_info info)
218 {
219     HILOG_INFO("%{public}s,called", __func__);
220     // blockOnFull:true  altRefJSCallBack:false
221     return StartThreadInternal(env, info, CallJsFuntion, true, false);
222 }
223 
StartThreadNonblocking(napi_env env,napi_callback_info info)224 static napi_value StartThreadNonblocking(napi_env env, napi_callback_info info)
225 {
226     HILOG_INFO("%{public}s,called", __func__);
227     // blockOnFull:false  altRefJSCallBack:false
228     return StartThreadInternal(env, info, CallJsFuntion, false, false);
229 }
230 
231 // Module init
ThreadSafeInit(napi_env env,napi_value exports)232 napi_value ThreadSafeInit(napi_env env, napi_value exports)
233 {
234     HILOG_INFO("%{public}s,called", __func__);
235 
236     for (size_t index = 0; index < ARRAY_LENGTH; index++) {
237         transmitData[index] = index;
238     }
239     napi_value jsArrayLength = 0, jsMaxQueueSize = 0;
240     napi_create_uint32(env, ARRAY_LENGTH, &jsArrayLength);
241     napi_create_uint32(env, MAX_QUEUE_SIZE, &jsMaxQueueSize);
242 
243     napi_property_descriptor properties[] = {
244         DECLARE_NAPI_FUNCTION("testStartThread", StartThread),
245         DECLARE_NAPI_FUNCTION("testStartThreadNonblocking", StartThreadNonblocking),
246         DECLARE_NAPI_FUNCTION("testStopThread", StopThread),
247     };
248 
249     NAPI_CALL(env, napi_define_properties(env, exports, sizeof(properties) / sizeof(properties[0]), properties));
250     return exports;
251 }
252 } // namespace SYSTEM_TEST_NAPI
253 } // namespace NAPI
254 } // namespace ACE
255