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;
68 bool queueWasClosing = false;
69 NAPI_CALL_RETURN_VOID(env, napi_get_threadsafe_function_context(tsfun, &hint));
70
71 TsFnHint* tsFnInfo = static_cast<TsFnHint*>(hint);
72 if (tsFnInfo != &tsinfo) {
73 napi_fatal_error("DataSourceThread", NAPI_AUTO_LENGTH,
74 "thread-safe function hint is not as expected", NAPI_AUTO_LENGTH);
75 }
76 if (tsFnInfo->startSecondary) {
77 NAPI_CALL_RETURN_VOID(env, napi_acquire_threadsafe_function(tsfun));
78 if (uv_thread_create(&uv_threads[1], ReleaseThreadsafeFunction, tsfun) != 0) {
79 napi_fatal_error("DataSourceThread", NAPI_AUTO_LENGTH,
80 "failed to start secondary thread", NAPI_AUTO_LENGTH);
81 }
82 }
83 for (int index = ARRAY_LENGTH - 1; index > -1 && !queueWasClosing; index--) {
84 auto status = napi_call_threadsafe_function(tsfun, &transmitData[index], tsFnInfo->blockOnFull);
85 std::string statusStr;
86 switch (status) {
87 case napi_queue_full:
88 queueWasFull = true;
89 index++;
90 statusStr = "napi_queue_full";
91 break;
92 case napi_ok:
93 statusStr = "napi_ok";
94 continue;
95 case napi_closing:
96 statusStr = "napi_closing";
97 queueWasClosing = true;
98 break;
99 default:
100 napi_fatal_error("DataSourceThread", NAPI_AUTO_LENGTH,
101 "napi_call_threadsafe_function failed", NAPI_AUTO_LENGTH);
102 }
103 HILOG_INFO("%{public}s,called napi_call_threadsafe_function index = %{public}d, status =%{public}s",
104 __func__, index, statusStr.c_str());
105 }
106 if (!queueWasClosing && napi_release_threadsafe_function(tsfun, napi_tsfn_release) != napi_ok) {
107 napi_fatal_error(
108 "DataSourceThread", NAPI_AUTO_LENGTH, "napi_release_threadsafe_function failed", NAPI_AUTO_LENGTH);
109 }
110 HILOG_INFO("%{public}s,called end", __func__);
111 }
112
113 // Getting the data into JS
CallJsFuntion(napi_env env,napi_value cb,void * hint,void * data)114 static void CallJsFuntion(napi_env env, napi_value cb, void* hint, void* data)
115 {
116 HILOG_INFO("%{public}s called", __func__);
117 if (!(env == nullptr || cb == nullptr)) {
118 napi_value argv = nullptr;
119 NAPI_CALL_RETURN_VOID(env, napi_create_int32(env, *(int*)data, &argv));
120
121 for (int i = ARRAY_LENGTH - 1; i >= 0; i--) {
122 if (testCallbackRef[i]) {
123 napi_value callback = 0;
124 napi_value undefined = nullptr;
125 napi_value result = nullptr;
126 napi_get_undefined(env, &undefined);
127 napi_get_reference_value(env, testCallbackRef[i], &callback);
128 napi_call_function(env, undefined, callback, 1, &argv, &result);
129 napi_delete_reference(env, testCallbackRef[i]);
130 testCallbackRef[i] = nullptr;
131 break;
132 }
133 }
134 }
135 }
136
137 static napi_ref altRef = nullptr;
138
139 // Cleanup Param:jsFinalizeCallBack, abort
StopThread(napi_env env,napi_callback_info info)140 static napi_value StopThread(napi_env env, napi_callback_info info)
141 {
142 HILOG_INFO("%{public}s,called", __func__);
143
144 uv_thread_join(&uv_threads[0]);
145 if (tsinfo.startSecondary) {
146 uv_thread_join(&uv_threads[1]);
147 }
148
149 size_t argc = THREAD_ARG_TWO;
150 napi_value argv[THREAD_ARG_TWO] = { nullptr };
151 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
152 napi_valuetype value_type;
153 NAPI_CALL(env, napi_typeof(env, argv[0], &value_type));
154 NAPI_ASSERT(env, value_type == napi_function, "StopThread argument is a function");
155 NAPI_ASSERT(env, (tsfun != nullptr), "Existing threadsafe function");
156 NAPI_CALL(env, napi_create_reference(env, argv[0], 1, &(tsinfo.jsFinalizeCallBackRef)));
157 bool abort = false;
158 NAPI_CALL(env, napi_get_value_bool(env, argv[1], &abort));
159 NAPI_CALL(env, napi_release_threadsafe_function(tsfun, abort ? napi_tsfn_abort : napi_tsfn_release));
160 tsfun = nullptr;
161 return nullptr;
162 }
163
164 // Join the thread and inform JS that we're done.
FinalizeCallBack(napi_env env,void * data,void * hint)165 static void FinalizeCallBack(napi_env env, void* data, void* hint)
166 {
167 HILOG_INFO("%{public}s,called", __func__);
168
169 TsFnHint* theHint = static_cast<TsFnHint*>(hint);
170 napi_value jsCallback = nullptr;
171 napi_value undefined = nullptr;
172 napi_value result = nullptr;
173
174 NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, theHint->jsFinalizeCallBackRef, &jsCallback));
175 NAPI_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined));
176 NAPI_CALL_RETURN_VOID(env, napi_call_function(env, undefined, jsCallback, 0, nullptr, &result));
177 NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, theHint->jsFinalizeCallBackRef));
178 if (altRef != nullptr) {
179 NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, altRef));
180 altRef = nullptr;
181 }
182 }
183 // jsfunc, abort/release, startSecondary, maxQueueSize
StartThreadInternal(napi_env env,napi_callback_info info,napi_threadsafe_function_call_js cb,bool blockOnFull,bool altRefJSCallBack)184 static napi_value StartThreadInternal(napi_env env, napi_callback_info info,
185 napi_threadsafe_function_call_js cb, bool blockOnFull, bool altRefJSCallBack)
186 {
187 HILOG_INFO("%{public}s,called start", __func__);
188 gCallEnv = env;
189 size_t argc = THREAD_ARG_FOUR;
190 napi_value argv[THREAD_ARG_FOUR] = { 0 };
191
192 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
193 if (altRefJSCallBack) {
194 NAPI_CALL(env, napi_create_reference(env, argv[0], 1, &altRef));
195 argv[0] = nullptr;
196 }
197
198 for (int i = 0; i<ARRAY_LENGTH; i++) {
199 napi_create_reference(env, argv[0], 1, &testCallbackRef[i]);
200 }
201
202 tsinfo.blockOnFull = (blockOnFull ? napi_tsfn_blocking : napi_tsfn_nonblocking);
203
204 NAPI_ASSERT(env, (tsfun == nullptr), "Existing thread-safe function");
205 napi_value asyncName;
206 NAPI_CALL(env, napi_create_string_utf8(env, "N-API Thread-safe Function Test", NAPI_AUTO_LENGTH, &asyncName));
207 NAPI_CALL(env, napi_get_value_uint32(env, argv[THREAD_ARG_THREE], &tsinfo.maxQueueSize));
208
209 NAPI_CALL(env, napi_create_threadsafe_function(env, argv[0], nullptr, asyncName, tsinfo.maxQueueSize,
210 MAX_THREAD_SIZE, uv_threads, FinalizeCallBack, &tsinfo, cb, &tsfun));
211 bool abort = false;
212 NAPI_CALL(env, napi_get_value_bool(env, argv[1], &abort));
213 tsinfo.abort = abort ? napi_tsfn_abort : napi_tsfn_release;
214 NAPI_CALL(env, napi_get_value_bool(env, argv[THREAD_ARG_TWO], &(tsinfo.startSecondary)));
215
216 NAPI_ASSERT(env, (uv_thread_create(&uv_threads[0], DataSourceThread, tsfun) == 0), "Thread creation");
217 HILOG_INFO("%{public}s,called end", __func__);
218 return nullptr;
219 }
220
221 // Startup param: jsfunc, abort/release, startSecondary, maxQueueSize
StartThread(napi_env env,napi_callback_info info)222 static napi_value StartThread(napi_env env, napi_callback_info info)
223 {
224 HILOG_INFO("%{public}s,called", __func__);
225 // blockOnFull:true altRefJSCallBack:false
226 return StartThreadInternal(env, info, CallJsFuntion, true, false);
227 }
228
StartThreadNonblocking(napi_env env,napi_callback_info info)229 static napi_value StartThreadNonblocking(napi_env env, napi_callback_info info)
230 {
231 HILOG_INFO("%{public}s,called", __func__);
232 // blockOnFull:false altRefJSCallBack:false
233 return StartThreadInternal(env, info, CallJsFuntion, false, false);
234 }
235
236 // Module init
ThreadSafeInit(napi_env env,napi_value exports)237 napi_value ThreadSafeInit(napi_env env, napi_value exports)
238 {
239 HILOG_INFO("%{public}s,called", __func__);
240
241 for (size_t index = 0; index < ARRAY_LENGTH; index++) {
242 transmitData[index] = index;
243 }
244 napi_value jsArrayLength = 0;
245 napi_value jsMaxQueueSize = 0;
246 napi_create_uint32(env, ARRAY_LENGTH, &jsArrayLength);
247 napi_create_uint32(env, MAX_QUEUE_SIZE, &jsMaxQueueSize);
248
249 napi_property_descriptor properties[] = {
250 DECLARE_NAPI_FUNCTION("testStartThread", StartThread),
251 DECLARE_NAPI_FUNCTION("testStartThreadNonblocking", StartThreadNonblocking),
252 DECLARE_NAPI_FUNCTION("testStopThread", StopThread),
253 };
254
255 NAPI_CALL(env, napi_define_properties(env, exports, sizeof(properties) / sizeof(properties[0]), properties));
256 return exports;
257 }
258 } // namespace SYSTEM_TEST_NAPI
259 } // namespace NAPI
260 } // namespace ACE
261