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