1
1
#include <erl_nif.h>
2
+ #include "jq.h"
2
3
#include "jv.h"
3
4
#include "port_nif_common.h"
4
5
6
+ #include <stdatomic.h>
5
7
#include <stdbool.h>
6
8
#include <setjmp.h>
7
9
@@ -96,6 +98,9 @@ typedef struct {
96
98
JQStateCacheEntry_lru_ptr_dynarr caches ;
97
99
// Lock protecting the above field from concurrent modifications
98
100
ErlNifMutex * lock ;
101
+ // NIF resource that holds a jq state that is used when timing out
102
+ // NIF calls
103
+ ErlNifResourceType * jq_state_holder_resource_type ;
99
104
} module_private_data ;
100
105
101
106
@@ -162,6 +167,9 @@ jq_state* get_jq_state(
162
167
JQStateCacheEntry_lru_get (cache , new_entry );
163
168
if (cache_entry != NULL ) {
164
169
// Return jq_state from cache
170
+ // It is important that we reset the cancel state so
171
+ // that the jq execution don't get canceled immediately
172
+ jq_reset_cancel_state (cache_entry -> state );
165
173
return cache_entry -> state ;
166
174
}
167
175
} else {
@@ -178,6 +186,7 @@ jq_state* get_jq_state(
178
186
erljq_free (error_message );
179
187
return NULL ;
180
188
}
189
+ jq_reset_cancel_state (jq );
181
190
if (cache != NULL ) {
182
191
// Add new entry to cache
183
192
char * filter_program_string = erljq_alloc (erl_jq_filter .size );
@@ -192,7 +201,7 @@ jq_state* get_jq_state(
192
201
}
193
202
194
203
195
- // Process the JSON obejct value using the compiled filter program in the
204
+ // Process the JSON object value using the compiled filter program in the
196
205
// given jq_state
197
206
static int process_json (
198
207
jq_state * jq ,
@@ -233,6 +242,14 @@ static int process_json(
233
242
(char * )enif_make_new_binary (env , binsz , error_msg_bin_ptr );
234
243
memcpy (bin_data , error_message , binsz );
235
244
erljq_free (error_message );
245
+ // We might have results in the result strings (e.g., when there is a
246
+ // timeout) so we need to free them as well
247
+ size_t nr_of_result_objects = String_dynarr_size (& result_strings );
248
+ for (size_t i = 0 ; i < nr_of_result_objects ; i ++ ) {
249
+ String result = String_dynarr_item_at (& result_strings , i );
250
+ erljq_free (result .string );
251
+ }
252
+ String_dynarr_destroy (& result_strings );
236
253
}
237
254
return res ;
238
255
}
@@ -253,9 +270,66 @@ static ERL_NIF_TERM make_ok_return(ErlNifEnv* env, ERL_NIF_TERM result) {
253
270
return enif_make_tuple2 (env , enif_make_atom (env , "ok" ), result );
254
271
}
255
272
273
+ typedef struct {
274
+ ErlNifMutex * lock ;
275
+ volatile atomic_bool consumed_by_process_json_nif ;
276
+ jq_state * state ;
277
+ } JQCancelableStateHolderResource ;
278
+
279
+ static ERL_NIF_TERM
280
+ create_jq_resource_nif (
281
+ ErlNifEnv * env ,
282
+ int argc ,
283
+ const ERL_NIF_TERM argv []) {
284
+ module_private_data * module_data = enif_priv_data (env );
285
+ JQCancelableStateHolderResource * cancelable_state_holder =
286
+ enif_alloc_resource (module_data -> jq_state_holder_resource_type ,
287
+ sizeof (JQCancelableStateHolderResource ));
288
+ cancelable_state_holder -> state = NULL ;
289
+ static char * lock_name = "jq_nif_resource_lock" ;
290
+ cancelable_state_holder -> lock = enif_mutex_create (lock_name );
291
+ atomic_init (& cancelable_state_holder -> consumed_by_process_json_nif , false);
292
+ ERL_NIF_TERM term = enif_make_resource (env , cancelable_state_holder );
293
+ enif_release_resource (cancelable_state_holder );
294
+ return term ;
295
+ }
296
+
297
+ static ERL_NIF_TERM
298
+ cancel_jq_resource_nif (
299
+ ErlNifEnv * env ,
300
+ int argc ,
301
+ const ERL_NIF_TERM argv []) {
302
+ module_private_data * module_data = enif_priv_data (env );
303
+ JQCancelableStateHolderResource * cancelable_state_holder ;
304
+ if (enif_get_resource (
305
+ env ,
306
+ argv [0 ],
307
+ module_data -> jq_state_holder_resource_type ,
308
+ (void * * )& cancelable_state_holder )) {
309
+ if (!atomic_load (& cancelable_state_holder -> consumed_by_process_json_nif )) {
310
+ // The resource has not been passed to process_json_nif yet. We
311
+ // schedule ourselves out and will try again later
312
+ enif_consume_timeslice (env , 100 );
313
+ return enif_make_atom (env , "retry" );
314
+ }
315
+ enif_mutex_lock (cancelable_state_holder -> lock );
316
+ if (cancelable_state_holder -> state != NULL ) {
317
+ // jq_cancel is a function to cancel the jq execution from
318
+ // another thread. This function only exists in the our
319
+ // patched version of the jq library.
320
+ jq_cancel (cancelable_state_holder -> state );
321
+ }
322
+ enif_mutex_unlock (cancelable_state_holder -> lock );
323
+ } else {
324
+ return enif_make_badarg (env );
325
+ }
326
+ return enif_make_atom (env , "ok" );
327
+ }
328
+
256
329
// NIF function taking a binaries for a filter program and a JSON text
257
330
// and returning the result of processing the JSON text with the
258
- // filter program
331
+ // filter program. The third argument can be a NIF resource that other
332
+ // threads can use to cancel the execution
259
333
static ERL_NIF_TERM process_json_nif (ErlNifEnv * env , int argc , const ERL_NIF_TERM argv []) {
260
334
ERL_NIF_TERM error_msg_bin ;
261
335
// ----------------------------- init --------------------------------------
@@ -264,7 +338,25 @@ static ERL_NIF_TERM process_json_nif(ErlNifEnv* env, int argc, const ERL_NIF_TER
264
338
int ret = JQ_ERROR_UNKNOWN ;
265
339
ERL_NIF_TERM ret_term ;
266
340
int dumpopts = 512 ; // JV_PRINT_SPACE1
267
-
341
+ JQCancelableStateHolderResource * cancelable_state_holder = NULL ;
342
+ bool cancelable_state_holder_opened = false;
343
+ if (argc > 2 ) {
344
+ // Third argument is a NIF resource for canceling
345
+ // the jq execution from another thread
346
+ module_private_data * module_data = enif_priv_data (env );
347
+ if (!enif_get_resource (
348
+ env ,
349
+ argv [2 ],
350
+ module_data -> jq_state_holder_resource_type ,
351
+ (void * * )& cancelable_state_holder )) {
352
+ ret = JQ_ERROR_BADARG ;
353
+ const char * error_message =
354
+ "Expected a resource as third argument" ;
355
+ error_msg_bin =
356
+ make_error_msg_bin (env , error_message , strlen (error_message ));
357
+ goto out ;
358
+ }
359
+ }
268
360
// --------------------------- read args -----------------------------------
269
361
ErlNifBinary erl_jq_filter ;
270
362
ErlNifBinary erl_json_text ;
@@ -280,13 +372,22 @@ static ERL_NIF_TERM process_json_nif(ErlNifEnv* env, int argc, const ERL_NIF_TER
280
372
if (setjmp (nomem_handling_jmp_buf )) {
281
373
// Give badarg exception as this seems more reasonable than allocating and
282
374
// returning an error tuple when we have run out of memory
375
+ if (cancelable_state_holder != NULL && !cancelable_state_holder_opened ) {
376
+ atomic_store (& cancelable_state_holder -> consumed_by_process_json_nif , true);
377
+ }
283
378
return enif_make_badarg (env );
284
379
}
285
380
// --------- get jq state and compile filter program if not cached ---------
286
381
jq = get_jq_state (env , & error_msg_bin , & ret , erl_jq_filter , & remove_jq_object );
287
382
if (jq == NULL ) {
288
383
goto out ;
384
+ } else if (cancelable_state_holder != NULL ) {
385
+ cancelable_state_holder -> state = jq ;
386
+ // jq state can be timed out after the following unlock call
387
+ atomic_store (& cancelable_state_holder -> consumed_by_process_json_nif , true);
388
+ cancelable_state_holder_opened = true;
289
389
}
390
+
290
391
ERL_NIF_TERM ret_list = enif_make_list (env , 0 );
291
392
ret = process_json (
292
393
jq ,
@@ -296,8 +397,17 @@ static ERL_NIF_TERM process_json_nif(ErlNifEnv* env, int argc, const ERL_NIF_TER
296
397
0 ,
297
398
dumpopts ,
298
399
& error_msg_bin );
400
+ if (cancelable_state_holder != NULL ) {
401
+ // We don't want any timeouts after this point
402
+ enif_mutex_lock (cancelable_state_holder -> lock );
403
+ cancelable_state_holder -> state = NULL ;
404
+ enif_mutex_unlock (cancelable_state_holder -> lock );
405
+ }
299
406
300
407
out :// ----------------------------- release -----------------------------------
408
+ if (cancelable_state_holder != NULL && !cancelable_state_holder_opened ) {
409
+ atomic_store (& cancelable_state_holder -> consumed_by_process_json_nif , true);
410
+ }
301
411
switch (ret ) {
302
412
default :
303
413
assert (0 && "invalid ret" );
@@ -306,6 +416,7 @@ static ERL_NIF_TERM process_json_nif(ErlNifEnv* env, int argc, const ERL_NIF_TER
306
416
case JQ_ERROR_BADARG :
307
417
case JQ_ERROR_COMPILE :
308
418
case JQ_ERROR_PARSE :
419
+ case JQ_ERROR_TIMEOUT :
309
420
case JQ_ERROR_PROCESS : {
310
421
ret_term = make_error_return (env , ret , error_msg_bin );
311
422
break ;
@@ -386,6 +497,12 @@ static int get_int_config(
386
497
return 0 ;
387
498
}
388
499
500
+
501
+ static void jq_state_holder_resource_dtor (ErlNifEnv * caller_env , void * obj ) {
502
+ JQCancelableStateHolderResource * cancelable_state_holder = obj ;
503
+ enif_mutex_destroy (cancelable_state_holder -> lock );
504
+ }
505
+
389
506
static int load_helper (
390
507
ErlNifEnv * caller_env ,
391
508
void * * priv_data ,
@@ -436,6 +553,14 @@ static int load_helper(
436
553
erljq_free (data );
437
554
return 1 ;
438
555
}
556
+ static const char * jq_state_holder_resource_type_name = "jq_state_holder_resource_type" ;
557
+ data -> jq_state_holder_resource_type = enif_open_resource_type (
558
+ caller_env ,
559
+ NULL ,
560
+ jq_state_holder_resource_type_name ,
561
+ jq_state_holder_resource_dtor ,
562
+ ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER ,
563
+ NULL );
439
564
JQStateCacheEntry_lru_ptr_dynarr_init (& data -> caches );
440
565
* priv_data = data ;
441
566
return 0 ;
@@ -478,6 +603,9 @@ static ErlNifFunc nif_funcs[] = {
478
603
for a much longer time than is should).
479
604
*/
480
605
{"process_json" , 2 , process_json_nif , ERL_NIF_DIRTY_JOB_CPU_BOUND },
606
+ {"create_jq_resource" , 0 , create_jq_resource_nif , 0 },
607
+ {"cancel_jq_resource" , 1 , cancel_jq_resource_nif , 0 },
608
+ {"process_json_with_jq_resource" , 3 , process_json_nif , ERL_NIF_DIRTY_JOB_CPU_BOUND },
481
609
{"set_filter_program_lru_cache_max_size" , 1 , set_filter_program_lru_cache_max_size_nif , 0 },
482
610
{"get_filter_program_lru_cache_max_size" , 0 , get_filter_program_lru_cache_max_size_nif , 0 }
483
611
};
0 commit comments