1#include <assert.h>
2#include <stdio.h>
3#include <node_api.h>
4#include <uv.h>
5#include "../../js-native-api/common.h"
6
7// this needs to be greater than the thread pool size
8#define MAX_CANCEL_THREADS 6
9
10typedef struct {
11  int32_t _input;
12  int32_t _output;
13  napi_ref _callback;
14  napi_async_work _request;
15} carrier;
16
17static carrier the_carrier;
18static carrier async_carrier[MAX_CANCEL_THREADS];
19
20static void Execute(napi_env env, void* data) {
21  uv_sleep(1000);
22  carrier* c = (carrier*)(data);
23
24  assert(c == &the_carrier);
25
26  c->_output = c->_input * 2;
27}
28
29static void Complete(napi_env env, napi_status status, void* data) {
30  carrier* c = (carrier*)(data);
31
32  if (c != &the_carrier) {
33    napi_throw_type_error(env, NULL, "Wrong data parameter to Complete.");
34    return;
35  }
36
37  if (status != napi_ok) {
38    napi_throw_type_error(env, NULL, "Execute callback failed.");
39    return;
40  }
41
42  napi_value argv[2];
43
44  NODE_API_CALL_RETURN_VOID(env, napi_get_null(env, &argv[0]));
45  NODE_API_CALL_RETURN_VOID(env, napi_create_int32(env, c->_output, &argv[1]));
46  napi_value callback;
47  NODE_API_CALL_RETURN_VOID(env,
48      napi_get_reference_value(env, c->_callback, &callback));
49  napi_value global;
50  NODE_API_CALL_RETURN_VOID(env, napi_get_global(env, &global));
51
52  napi_value result;
53  NODE_API_CALL_RETURN_VOID(env,
54    napi_call_function(env, global, callback, 2, argv, &result));
55
56  NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, c->_callback));
57  NODE_API_CALL_RETURN_VOID(env, napi_delete_async_work(env, c->_request));
58}
59
60static napi_value Test(napi_env env, napi_callback_info info) {
61  size_t argc = 3;
62  napi_value argv[3];
63  napi_value _this;
64  napi_value resource_name;
65  void* data;
66  NODE_API_CALL(env,
67    napi_get_cb_info(env, info, &argc, argv, &_this, &data));
68  NODE_API_ASSERT(env, argc >= 3, "Not enough arguments, expected 2.");
69
70  napi_valuetype t;
71  NODE_API_CALL(env, napi_typeof(env, argv[0], &t));
72  NODE_API_ASSERT(env, t == napi_number,
73      "Wrong first argument, integer expected.");
74  NODE_API_CALL(env, napi_typeof(env, argv[1], &t));
75  NODE_API_ASSERT(env, t == napi_object,
76    "Wrong second argument, object expected.");
77  NODE_API_CALL(env, napi_typeof(env, argv[2], &t));
78  NODE_API_ASSERT(env, t == napi_function,
79    "Wrong third argument, function expected.");
80
81  the_carrier._output = 0;
82
83  NODE_API_CALL(env,
84      napi_get_value_int32(env, argv[0], &the_carrier._input));
85  NODE_API_CALL(env,
86    napi_create_reference(env, argv[2], 1, &the_carrier._callback));
87
88  NODE_API_CALL(env, napi_create_string_utf8(
89      env, "TestResource", NAPI_AUTO_LENGTH, &resource_name));
90  NODE_API_CALL(env, napi_create_async_work(env, argv[1], resource_name,
91    Execute, Complete, &the_carrier, &the_carrier._request));
92  NODE_API_CALL(env,
93      napi_queue_async_work(env, the_carrier._request));
94
95  return NULL;
96}
97
98static void BusyCancelComplete(napi_env env, napi_status status, void* data) {
99  carrier* c = (carrier*)(data);
100  NODE_API_CALL_RETURN_VOID(env, napi_delete_async_work(env, c->_request));
101}
102
103static void CancelComplete(napi_env env, napi_status status, void* data) {
104  carrier* c = (carrier*)(data);
105
106  if (status == napi_cancelled) {
107    // ok we got the status we expected so make the callback to
108    // indicate the cancel succeeded.
109    napi_value callback;
110    NODE_API_CALL_RETURN_VOID(env,
111        napi_get_reference_value(env, c->_callback, &callback));
112    napi_value global;
113    NODE_API_CALL_RETURN_VOID(env, napi_get_global(env, &global));
114    napi_value result;
115    NODE_API_CALL_RETURN_VOID(env,
116      napi_call_function(env, global, callback, 0, NULL, &result));
117  }
118
119  NODE_API_CALL_RETURN_VOID(env, napi_delete_async_work(env, c->_request));
120  NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, c->_callback));
121}
122
123static void CancelExecute(napi_env env, void* data) {
124  uv_sleep(1000);
125}
126
127static napi_value TestCancel(napi_env env, napi_callback_info info) {
128  size_t argc = 1;
129  napi_value argv[1];
130  napi_value _this;
131  napi_value resource_name;
132  void* data;
133
134  NODE_API_CALL(env, napi_create_string_utf8(
135      env, "TestResource", NAPI_AUTO_LENGTH, &resource_name));
136
137  // make sure the work we are going to cancel will not be
138  // able to start by using all the threads in the pool
139  for (int i = 1; i < MAX_CANCEL_THREADS; i++) {
140    NODE_API_CALL(env, napi_create_async_work(env, NULL, resource_name,
141      CancelExecute, BusyCancelComplete,
142      &async_carrier[i], &async_carrier[i]._request));
143    NODE_API_CALL(env, napi_queue_async_work(env, async_carrier[i]._request));
144  }
145
146  // now queue the work we are going to cancel and then cancel it.
147  // cancel will fail if the work has already started, but
148  // we have prevented it from starting by consuming all of the
149  // workers above.
150  NODE_API_CALL(env,
151    napi_get_cb_info(env, info, &argc, argv, &_this, &data));
152  NODE_API_CALL(env, napi_create_async_work(env, NULL, resource_name,
153    CancelExecute, CancelComplete,
154    &async_carrier[0], &async_carrier[0]._request));
155  NODE_API_CALL(env,
156      napi_create_reference(env, argv[0], 1, &async_carrier[0]._callback));
157  NODE_API_CALL(env, napi_queue_async_work(env, async_carrier[0]._request));
158  NODE_API_CALL(env, napi_cancel_async_work(env, async_carrier[0]._request));
159  return NULL;
160}
161
162struct {
163  napi_ref ref;
164  napi_async_work work;
165} repeated_work_info = { NULL, NULL };
166
167static void RepeatedWorkerThread(napi_env env, void* data) {}
168
169static void RepeatedWorkComplete(napi_env env, napi_status status, void* data) {
170  napi_value cb, js_status;
171  NODE_API_CALL_RETURN_VOID(env,
172      napi_get_reference_value(env, repeated_work_info.ref, &cb));
173  NODE_API_CALL_RETURN_VOID(env,
174      napi_delete_async_work(env, repeated_work_info.work));
175  NODE_API_CALL_RETURN_VOID(env,
176      napi_delete_reference(env, repeated_work_info.ref));
177  repeated_work_info.work = NULL;
178  repeated_work_info.ref = NULL;
179  NODE_API_CALL_RETURN_VOID(env,
180      napi_create_uint32(env, (uint32_t)status, &js_status));
181  NODE_API_CALL_RETURN_VOID(env,
182      napi_call_function(env, cb, cb, 1, &js_status, NULL));
183}
184
185static napi_value DoRepeatedWork(napi_env env, napi_callback_info info) {
186  size_t argc = 1;
187  napi_value cb, name;
188  NODE_API_ASSERT(env, repeated_work_info.ref == NULL,
189      "Reference left over from previous work");
190  NODE_API_ASSERT(env, repeated_work_info.work == NULL,
191      "Work pointer left over from previous work");
192  NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &cb, NULL, NULL));
193  NODE_API_CALL(env, napi_create_reference(env, cb, 1, &repeated_work_info.ref));
194  NODE_API_CALL(env,
195      napi_create_string_utf8(env, "Repeated Work", NAPI_AUTO_LENGTH, &name));
196  NODE_API_CALL(env,
197      napi_create_async_work(env, NULL, name, RepeatedWorkerThread,
198          RepeatedWorkComplete, &repeated_work_info, &repeated_work_info.work));
199  NODE_API_CALL(env, napi_queue_async_work(env, repeated_work_info.work));
200  return NULL;
201}
202
203static napi_value Init(napi_env env, napi_value exports) {
204  napi_property_descriptor properties[] = {
205    DECLARE_NODE_API_PROPERTY("Test", Test),
206    DECLARE_NODE_API_PROPERTY("TestCancel", TestCancel),
207    DECLARE_NODE_API_PROPERTY("DoRepeatedWork", DoRepeatedWork),
208  };
209
210  NODE_API_CALL(env, napi_define_properties(
211      env, exports, sizeof(properties) / sizeof(*properties), properties));
212
213  return exports;
214}
215
216NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
217