@reboot-dev/reboot 0.24.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/reboot_native.cc CHANGED
@@ -81,6 +81,23 @@ struct PythonNodeAdaptor {
81
81
 
82
82
  template <typename F>
83
83
  void ScheduleCallbackOnNodeEventLoop(F&& f) {
84
+ // It is possible that Node.js is shutting down and has already
85
+ // called the `thread_safe_function` finalizer and trying to use
86
+ // `thread_safe_function` will raise SIGABRT or SIGSEGV.
87
+ //
88
+ // Given that we're shutting down we just return!
89
+ //
90
+ // If the callback is to free memory, e.g., from a
91
+ // `NapiSafeReference`, that's not a problem because we're
92
+ // shutting down so all the memory will get freed then! ;-)
93
+ //
94
+ // If this is for some other critical function that is necessary
95
+ // to do _before_ shutting down then we'll need to rethink this
96
+ // approaach.
97
+ if (thread_safe_function_finalized) {
98
+ return;
99
+ }
100
+
84
101
  napi_status status = thread_safe_function.BlockingCall(
85
102
  [f = std::forward<F>(f)](
86
103
  Napi::Env env,
@@ -116,6 +133,8 @@ struct PythonNodeAdaptor {
116
133
 
117
134
  Napi::ThreadSafeFunction thread_safe_function;
118
135
 
136
+ bool thread_safe_function_finalized = false;
137
+
119
138
  std::thread thread;
120
139
  std::mutex mutex;
121
140
  std::condition_variable python_functions_not_empty;
@@ -133,6 +152,83 @@ static Napi::FunctionReference* js_launchSubprocessConsensus =
133
152
  new Napi::FunctionReference();
134
153
 
135
154
 
155
+ struct NapiReferenceDeleter {
156
+ template <typename T>
157
+ void operator()(Napi::Reference<T>* reference) {
158
+ adaptor->ScheduleCallbackOnNodeEventLoop(
159
+ [reference](Napi::Env) {
160
+ delete reference;
161
+ });
162
+ }
163
+ };
164
+
165
+
166
+ template <typename T>
167
+ class NapiSafeReference {
168
+ public:
169
+ NapiSafeReference(T t)
170
+ : _reference(
171
+ new Napi::Reference<T>(Napi::Persistent(std::move(t))),
172
+ NapiReferenceDeleter()) {}
173
+
174
+ NapiSafeReference(Napi::Reference<T>&& reference)
175
+ : _reference(
176
+ new Napi::Reference<T>(std::move(reference)),
177
+ NapiReferenceDeleter()) {}
178
+
179
+ // Helper for getting the value of the reference. We require a
180
+ // `Napi::Env` to ensure we only try and get the value from within a
181
+ // Node thread (e.g., using the main thread or from within a NAPI
182
+ // thread safe function).
183
+ T Value(Napi::Env) const {
184
+ return _reference->Value();
185
+ }
186
+
187
+ private:
188
+ std::shared_ptr<Napi::Reference<T>> _reference;
189
+ };
190
+
191
+ using NapiSafeFunctionReference = NapiSafeReference<Napi::Function>;
192
+ using NapiSafeObjectReference = NapiSafeReference<Napi::Object>;
193
+
194
+ // For every servicer we construct a _subclass_ of our Python
195
+ // generated Node adaptor, e.g., `GreeterServicerNodeAdaptor`, _at
196
+ // runtime_ which we add to the following Python module. This module
197
+ // also includes our C++ <-> Python class wrappers for storing NAPI
198
+ // objects in Python to properly handle memory management.
199
+ PYBIND11_EMBEDDED_MODULE(reboot_native, m) {
200
+ py::class_<NapiSafeFunctionReference>(
201
+ m,
202
+ "NapiSafeFunctionReference");
203
+
204
+ py::class_<NapiSafeObjectReference>(
205
+ m,
206
+ "NapiSafeObjectReference");
207
+ }
208
+
209
+
210
+ Napi::External<py::object> make_napi_external(
211
+ Napi::Env env,
212
+ py::object* py_object,
213
+ const napi_type_tag* type_tag = nullptr) {
214
+ auto js_external = Napi::External<py::object>::New(
215
+ env,
216
+ py_object,
217
+ [](Napi::Env, py::object* py_object) {
218
+ adaptor->ScheduleCallbackOnPythonEventLoop(
219
+ [py_object]() {
220
+ delete py_object;
221
+ });
222
+ });
223
+
224
+ if (type_tag != nullptr) {
225
+ js_external.TypeTag(type_tag);
226
+ }
227
+
228
+ return js_external;
229
+ }
230
+
231
+
136
232
  void PythonNodeAdaptor::Initialize(
137
233
  Napi::Env& env,
138
234
  const Napi::Function& js_callback) {
@@ -143,8 +239,12 @@ void PythonNodeAdaptor::Initialize(
143
239
  /* Max queue size (0 = unlimited): */ 0,
144
240
  /* Initial thread count: */ 1,
145
241
  /* Context: */ this,
146
- /* Finalizer: */
147
- [](Napi::Env env, void*, PythonNodeAdaptor*) {},
242
+ // Finalizer:
243
+ [this](Napi::Env env, void*, PythonNodeAdaptor*) {
244
+ // Set that we've been finalized so that we don't use
245
+ // `thread_safe_function` again, see comment in
246
+ // `ScheduleCallbackOnNodeEventLoop`.
247
+ thread_safe_function_finalized = true; },
148
248
  /* Finalizer data: */ (void*) nullptr);
149
249
 
150
250
  // We start with our thread safe function unreferenced so that
@@ -301,6 +401,476 @@ std::string message_from_js_error(const Napi::Object& js_error) {
301
401
  }
302
402
 
303
403
 
404
+ template <typename F, typename T = std::invoke_result_t<F>>
405
+ T RunCallbackOnPythonEventLoop(F&& f) {
406
+ std::promise<T> promise;
407
+
408
+ adaptor->ScheduleCallbackOnPythonEventLoop(
409
+ [&f, &promise]() {
410
+ try {
411
+ if constexpr (std::is_void<T>::value) {
412
+ f();
413
+ promise.set_value();
414
+ } else {
415
+ promise.set_value(f());
416
+ }
417
+ } catch (const std::exception& e) {
418
+ promise.set_exception(
419
+ std::make_exception_ptr(
420
+ std::runtime_error(e.what())));
421
+ }
422
+ });
423
+
424
+ return promise.get_future().get();
425
+ }
426
+
427
+
428
+ template <typename F, typename T = std::invoke_result_t<F, Napi::Env>>
429
+ T RunCallbackOnNodeEventLoop(F&& f, bool warn = false) {
430
+ std::promise<T> promise;
431
+
432
+ adaptor->ScheduleCallbackOnNodeEventLoop(
433
+ [&f, &warn, &promise](Napi::Env env) {
434
+ try {
435
+ if constexpr (std::is_void<T>::value) {
436
+ f(env);
437
+ promise.set_value();
438
+ } else {
439
+ promise.set_value(f(env));
440
+ }
441
+ } catch (const Napi::Error& e) {
442
+ if (warn) {
443
+ env.Global()
444
+ .Get("console")
445
+ .As<Napi::Object>()
446
+ .Get("warn")
447
+ .As<Napi::Function>()
448
+ .Call({e.Value(), Napi::String::New(env, "\n")});
449
+ }
450
+ promise.set_exception(
451
+ std::make_exception_ptr(
452
+ std::runtime_error(e.what())));
453
+ } catch (const std::exception& e) {
454
+ if (warn) {
455
+ env.Global()
456
+ .Get("console")
457
+ .As<Napi::Object>()
458
+ .Get("warn")
459
+ .As<Napi::Function>()
460
+ .Call(
461
+ {Napi::String::New(env, e.what()),
462
+ Napi::String::New(env, "\n")});
463
+ }
464
+ promise.set_exception(
465
+ std::make_exception_ptr(
466
+ std::runtime_error(e.what())));
467
+ }
468
+ });
469
+
470
+ return promise.get_future().get();
471
+ }
472
+
473
+
474
+ template <typename PythonCallback, typename NodeCallback>
475
+ Napi::Promise NodePromiseFromPythonCallback(
476
+ Napi::Env env,
477
+ PythonCallback&& python_callback,
478
+ NodeCallback&& node_callback) {
479
+ auto deferred = std::make_shared<Napi::Promise::Deferred>(env);
480
+ auto promise = deferred->Promise();
481
+
482
+ adaptor->ScheduleCallbackOnPythonEventLoop(
483
+ [python_callback = std::forward<PythonCallback>(python_callback),
484
+ node_callback = std::forward<NodeCallback>(node_callback),
485
+ deferred = std::move(deferred)]() mutable {
486
+ try {
487
+ auto result = python_callback();
488
+ adaptor->ScheduleCallbackOnNodeEventLoop(
489
+ [node_callback = std::forward<NodeCallback>(node_callback),
490
+ deferred = std::move(deferred),
491
+ result = std::move(result)](Napi::Env env) mutable {
492
+ try {
493
+ deferred->Resolve(node_callback(env, std::move(result)));
494
+ } catch (const Napi::Error& e) {
495
+ deferred->Reject(e.Value());
496
+ } catch (const std::exception& e) {
497
+ // TODO: is this code unreachable because Node.js
498
+ // will properly always only pass us a `Napi::Error`?
499
+ deferred->Reject(
500
+ Napi::Error::New(env, e.what()).Value());
501
+ } catch (...) {
502
+ // TODO: is this code unreachable because Node.js
503
+ // will properly always only pass us a `Napi::Error`?
504
+ deferred->Reject(
505
+ Napi::Error::New(env, "Unknown error").Value());
506
+ }
507
+ });
508
+ } catch (pybind11::error_already_set& e) {
509
+ std::string exception = py_exception_str(e.value());
510
+ adaptor->ScheduleCallbackOnNodeEventLoop(
511
+ [deferred = std::move(deferred),
512
+ exception = std::move(exception)](Napi::Env env) mutable {
513
+ deferred->Reject(Napi::Error::New(env, exception).Value());
514
+ });
515
+ }
516
+ });
517
+
518
+ return promise;
519
+ }
520
+
521
+
522
+ template <
523
+ typename PythonFutureCallback,
524
+ typename PythonDoneCallback,
525
+ typename NodeCallback>
526
+ Napi::Promise NodePromiseFromPythonFuture(
527
+ Napi::Env env,
528
+ PythonFutureCallback&& python_future_callback,
529
+ PythonDoneCallback&& python_done_callback,
530
+ NodeCallback&& node_callback) {
531
+ auto deferred = std::make_shared<Napi::Promise::Deferred>(env);
532
+ auto promise = deferred->Promise();
533
+
534
+ adaptor->ScheduleCallbackOnPythonEventLoop(
535
+ [python_future_callback = std::forward<PythonFutureCallback>(
536
+ python_future_callback),
537
+ python_done_callback = std::forward<PythonDoneCallback>(
538
+ python_done_callback),
539
+ node_callback = std::forward<NodeCallback>(node_callback),
540
+ deferred = std::move(deferred)]() mutable {
541
+ try {
542
+ // TODO: check that `py_future` is a future!
543
+ py::object py_future = python_future_callback();
544
+
545
+ py_future.attr("add_done_callback")(py::cpp_function(
546
+ [python_done_callback = std::forward<PythonDoneCallback>(
547
+ python_done_callback),
548
+ node_callback = std::forward<NodeCallback>(node_callback),
549
+ deferred = std::move(deferred),
550
+ // NOTE: need to keep a reference to `py_future` so that it
551
+ // doesn't get destroyed before completing!
552
+ py_future = new py::object(py_future)](py::object) mutable {
553
+ py::object py_exception = py_future->attr("exception")();
554
+ if (!py_exception.is_none()) {
555
+ py::str py_exception_string = py_exception_str(py_exception);
556
+ std::string exception = py_exception_string;
557
+
558
+ adaptor->ScheduleCallbackOnNodeEventLoop(
559
+ [deferred = std::move(deferred),
560
+ exception = std::move(exception)](Napi::Env env) {
561
+ deferred->Reject(
562
+ Napi::Error::New(env, exception).Value());
563
+ });
564
+ } else {
565
+ // TODO: try / catch around `python_done_callback`.
566
+ py::object py_result = py_future->attr("result")();
567
+ auto result = python_done_callback(std::move(py_result));
568
+
569
+ adaptor->ScheduleCallbackOnNodeEventLoop(
570
+ [node_callback =
571
+ std::forward<NodeCallback>(node_callback),
572
+ deferred = std::move(deferred),
573
+ result = std::move(result)](Napi::Env env) mutable {
574
+ // TODO: try / catch around `node_callback`.
575
+ auto js_result = node_callback(env, std::move(result));
576
+ deferred->Resolve(js_result);
577
+ });
578
+ }
579
+ delete py_future;
580
+ },
581
+ py::arg("future")));
582
+ } catch (pybind11::error_already_set& e) {
583
+ std::string exception = py_exception_str(e.value());
584
+ adaptor->ScheduleCallbackOnNodeEventLoop(
585
+ [deferred = std::move(deferred),
586
+ exception = std::move(exception)](Napi::Env env) mutable {
587
+ deferred->Reject(Napi::Error::New(env, exception).Value());
588
+ });
589
+ }
590
+ });
591
+
592
+ return promise;
593
+ }
594
+
595
+
596
+ template <typename PythonFutureCallback>
597
+ Napi::Promise NodePromiseFromPythonFuture(
598
+ Napi::Env env,
599
+ PythonFutureCallback&& python_future_callback) {
600
+ return NodePromiseFromPythonFuture(
601
+ env,
602
+ std::forward<PythonFutureCallback>(python_future_callback),
603
+ [](py::object py_result) {
604
+ // TODO: check that `py_result` is `None`.
605
+ return std::nullopt;
606
+ },
607
+ [](Napi::Env env, std::nullopt_t) {
608
+ return env.Null();
609
+ });
610
+ }
611
+
612
+
613
+ template <
614
+ typename PythonTaskCallback,
615
+ typename PythonDoneCallback,
616
+ typename NodeCallback,
617
+ typename... CreateTaskArgs>
618
+ Napi::Promise NodePromiseFromPythonTask(
619
+ Napi::Env env,
620
+ std::string&& name,
621
+ std::tuple<std::string, std::string, CreateTaskArgs...>&& create_task,
622
+ PythonTaskCallback&& python_task_callback,
623
+ PythonDoneCallback&& python_done_callback,
624
+ NodeCallback&& node_callback) {
625
+ return NodePromiseFromPythonFuture(
626
+ env,
627
+ [name = std::move(name),
628
+ create_task = std::move(create_task),
629
+ python_task_callback = std::forward<PythonTaskCallback>(
630
+ python_task_callback)]() mutable {
631
+ py::object py_task = std::apply(
632
+ [&name, &python_task_callback](
633
+ const std::string& import,
634
+ const std::string& attr,
635
+ auto&&... args) mutable {
636
+ return py::module::import(import.c_str())
637
+ .attr(attr.c_str())(
638
+ python_task_callback(),
639
+ std::forward<decltype(args)>(args)...,
640
+ "name"_a = name.c_str());
641
+ },
642
+ std::move(create_task));
643
+
644
+ return py_task;
645
+ },
646
+ std::forward<PythonDoneCallback>(python_done_callback),
647
+ std::forward<NodeCallback>(node_callback));
648
+ }
649
+
650
+
651
+ template <typename PythonTaskCallback, typename... CreateTaskArgs>
652
+ Napi::Promise NodePromiseFromPythonTask(
653
+ Napi::Env env,
654
+ std::string&& name,
655
+ std::tuple<std::string, std::string, CreateTaskArgs...>&& create_task,
656
+ PythonTaskCallback&& python_task_callback) {
657
+ return NodePromiseFromPythonTask(
658
+ env,
659
+ std::move(name),
660
+ std::move(create_task),
661
+ std::forward<PythonTaskCallback>(python_task_callback),
662
+ [](py::object py_result) {
663
+ // TODO: check that `py_result` is `None`.
664
+ return std::nullopt;
665
+ },
666
+ [](Napi::Env env, std::nullopt_t) {
667
+ return env.Null();
668
+ });
669
+ }
670
+
671
+
672
+ template <
673
+ typename PythonTaskCallback,
674
+ typename PythonDoneCallback,
675
+ typename NodeCallback>
676
+ Napi::Promise NodePromiseFromPythonTaskWithContext(
677
+ Napi::Env env,
678
+ std::string&& name,
679
+ NapiSafeReference<Napi::External<py::object>>& js_context,
680
+ PythonTaskCallback&& python_task_callback,
681
+ PythonDoneCallback&& python_done_callback,
682
+ NodeCallback&& node_callback) {
683
+ py::object* py_context = js_context.Value(env).Data();
684
+ return NodePromiseFromPythonTask(
685
+ env,
686
+ std::move(name),
687
+ std::make_tuple(
688
+ std::string("reboot.nodejs.python"),
689
+ std::string("create_task_with_context"),
690
+ py_context),
691
+ [js_context, // Ensures `py_context` remains valid.
692
+ python_task_callback =
693
+ std::forward<PythonTaskCallback>(python_task_callback)]() mutable {
694
+ return python_task_callback();
695
+ },
696
+ std::forward<PythonDoneCallback>(python_done_callback),
697
+ std::forward<NodeCallback>(node_callback));
698
+ }
699
+
700
+
701
+ template <typename PythonTaskCallback>
702
+ Napi::Promise NodePromiseFromPythonTaskWithContext(
703
+ Napi::Env env,
704
+ std::string&& name,
705
+ NapiSafeReference<Napi::External<py::object>>& js_context,
706
+ PythonTaskCallback&& python_task_callback) {
707
+ return NodePromiseFromPythonTaskWithContext(
708
+ env,
709
+ std::move(name),
710
+ js_context,
711
+ std::forward<PythonTaskCallback>(python_task_callback),
712
+ [](py::object py_result) {
713
+ // TODO: check that `py_result` is `None`.
714
+ return std::nullopt;
715
+ },
716
+ [](Napi::Env env, std::nullopt_t) {
717
+ return env.Null();
718
+ });
719
+ }
720
+
721
+
722
+ template <
723
+ typename NodePromiseCallback,
724
+ typename NodePromiseResolvedCallback,
725
+ typename PythonCallback>
726
+ py::object PythonFutureFromNodePromise(
727
+ NodePromiseCallback&& node_promise_callback,
728
+ NodePromiseResolvedCallback&& node_promise_resolved_callback,
729
+ PythonCallback&& python_callback) {
730
+ py::object py_future = py::module::import("asyncio").attr("Future")();
731
+
732
+ adaptor->ScheduleCallbackOnNodeEventLoop(
733
+ [node_promise_callback =
734
+ std::forward<NodePromiseCallback>(node_promise_callback),
735
+ node_promise_resolved_callback =
736
+ std::forward<NodePromiseResolvedCallback>(
737
+ node_promise_resolved_callback),
738
+ python_callback = std::forward<PythonCallback>(python_callback),
739
+ py_future = new py::object(py_future)](Napi::Env env) mutable {
740
+ try {
741
+ Napi::Object js_promise = node_promise_callback(env);
742
+
743
+ js_promise
744
+ .Get("then")
745
+ .As<Napi::Function>()
746
+ .Call(
747
+ js_promise,
748
+ {Napi::Function::New(
749
+ env,
750
+ [node_promise_resolved_callback =
751
+ std::forward<NodePromiseResolvedCallback>(
752
+ node_promise_resolved_callback),
753
+ python_callback = std::forward<PythonCallback>(
754
+ python_callback),
755
+ py_future](const Napi::CallbackInfo& info) mutable {
756
+ // TODO: put a try/catch around
757
+ // `node_promise_resolved_callback` to handle
758
+ // any errors.
759
+
760
+ auto result = node_promise_resolved_callback(
761
+ info.Env(),
762
+ info[0]);
763
+
764
+ adaptor->ScheduleCallbackOnPythonEventLoop(
765
+ [python_callback = std::forward<PythonCallback>(
766
+ python_callback),
767
+ py_future,
768
+ result = std::move(result)]() mutable {
769
+ bool cancelled =
770
+ py_future->attr("cancelled")().cast<bool>();
771
+ if (!cancelled) {
772
+ // TODO: put a try/catch around
773
+ // `python_callback` to handle any
774
+ // errors.
775
+ py_future->attr("set_result")(
776
+ python_callback(std::move(result)));
777
+ }
778
+ delete py_future;
779
+ });
780
+ }),
781
+ Napi::Function::New(
782
+ env,
783
+ [py_future](const Napi::CallbackInfo& info) {
784
+ std::string message = message_from_js_error(
785
+ info[0].As<Napi::Object>());
786
+
787
+ adaptor->ScheduleCallbackOnPythonEventLoop(
788
+ [py_future, message = std::move(message)]() {
789
+ bool cancelled =
790
+ py_future->attr("cancelled")()
791
+ .cast<bool>();
792
+ if (!cancelled) {
793
+ py_future->attr("set_exception")(
794
+ py::module::import("builtins")
795
+ .attr("Exception")(
796
+ message));
797
+ }
798
+ delete py_future;
799
+ });
800
+ })});
801
+ } catch (const std::exception& e) {
802
+ // NOTE: we're just catching exception here vs `Napi::Error`
803
+ // since all we care about is `what()` but we're making sure
804
+ // that we'll get all `Napi::Error` with this
805
+ // `static_assert`.
806
+ static_assert(
807
+ std::is_base_of<std::exception, Napi::Error>::value,
808
+ "Expecting `Napi::Error` to be subclass of `std::exception`");
809
+
810
+ std::string message = e.what();
811
+
812
+ adaptor->ScheduleCallbackOnPythonEventLoop(
813
+ [py_future, message = std::move(message)]() {
814
+ bool cancelled =
815
+ py_future->attr("cancelled")().cast<bool>();
816
+ if (!cancelled) {
817
+ py_future->attr("set_exception")(
818
+ py::module::import("builtins")
819
+ .attr("Exception")(message));
820
+ }
821
+ delete py_future;
822
+ });
823
+ } catch (...) {
824
+ // TODO: is this code unreachable because Node.js
825
+ // will properly always only pass us a `Napi::Error`?
826
+ adaptor->ScheduleCallbackOnPythonEventLoop(
827
+ [py_future]() {
828
+ bool cancelled =
829
+ py_future->attr("cancelled")().cast<bool>();
830
+ if (!cancelled) {
831
+ py_future->attr("set_exception")(
832
+ py::module::import("builtins")
833
+ .attr("Exception")("Unknown error"));
834
+ }
835
+ delete py_future;
836
+ });
837
+ }
838
+ });
839
+
840
+ return py_future;
841
+ }
842
+
843
+
844
+ template <
845
+ typename NodePromiseCallback,
846
+ typename NodePromiseResolvedCallback>
847
+ py::object PythonFutureFromNodePromise(
848
+ NodePromiseCallback&& node_promise_callback,
849
+ NodePromiseResolvedCallback&& node_promise_resolved_callback) {
850
+ return PythonFutureFromNodePromise(
851
+ std::forward<NodePromiseCallback>(node_promise_callback),
852
+ std::forward<NodePromiseResolvedCallback>(
853
+ node_promise_resolved_callback),
854
+ [](auto&& result) {
855
+ return std::move(result);
856
+ });
857
+ }
858
+
859
+
860
+ template <typename NodePromiseCallback>
861
+ py::object PythonFutureFromNodePromise(
862
+ NodePromiseCallback&& node_promise_callback) {
863
+ return PythonFutureFromNodePromise(
864
+ std::forward<NodePromiseCallback>(node_promise_callback),
865
+ [](Napi::Env, Napi::Value) {
866
+ return std::nullopt;
867
+ },
868
+ [](auto&&) {
869
+ return py::none();
870
+ });
871
+ }
872
+
873
+
304
874
  void Initialize(const Napi::CallbackInfo& info) {
305
875
  // Ensure we only initialize once, even if nodejs tries to load
306
876
  // the module more than once.
@@ -327,17 +897,11 @@ void ImportPy(const Napi::CallbackInfo& info) {
327
897
  std::string module = info[0].As<Napi::String>().Utf8Value();
328
898
  std::string base64_encoded_rbt_py = info[1].As<Napi::String>().Utf8Value();
329
899
 
330
- std::promise<void> promise;
331
-
332
- adaptor->ScheduleCallbackOnPythonEventLoop(
333
- [&module, &base64_encoded_rbt_py, &promise]() {
900
+ RunCallbackOnPythonEventLoop(
901
+ [&module, &base64_encoded_rbt_py]() {
334
902
  py::module::import("reboot.nodejs.python")
335
903
  .attr("import_py")(module, base64_encoded_rbt_py);
336
-
337
- promise.set_value();
338
904
  });
339
-
340
- promise.get_future().get();
341
905
  }
342
906
 
343
907
 
@@ -401,41 +965,16 @@ std::string uint8array_to_str(const Napi::Uint8Array& arr) {
401
965
 
402
966
 
403
967
  Napi::Value Reboot_constructor(const Napi::CallbackInfo& info) {
404
- std::promise<py::object*> promise;
405
-
406
- adaptor->ScheduleCallbackOnPythonEventLoop(
407
- [&promise]() {
968
+ py::object* py_reboot = RunCallbackOnPythonEventLoop(
969
+ []() {
408
970
  py::object tests = py::module::import("reboot.aio.tests");
409
- promise.set_value(new py::object(tests.attr("Reboot")()));
410
- }
411
- // TODO(benh): improve error handling mechanism to force all
412
- // raised exceptions to be handled.
413
- //
414
- // [](py::error_already_set& e) {
415
-
416
- // },
417
- // [](std::exception& e) {
418
-
419
- // },
420
- // []() {
421
-
422
- // }
423
- );
971
+ return new py::object(tests.attr("Reboot")());
972
+ });
424
973
 
425
- py::object* py_reboot = promise.get_future().get();
426
-
427
- Napi::External<py::object> js_external_reboot =
428
- Napi::External<py::object>::New(
429
- info.Env(),
430
- py_reboot,
431
- [](Napi::Env, py::object* py_reboot) {
432
- adaptor->ScheduleCallbackOnPythonEventLoop(
433
- [py_reboot]() {
434
- delete py_reboot;
435
- });
436
- });
437
-
438
- js_external_reboot.TypeTag(&reboot_aio_tests_Reboot);
974
+ Napi::External<py::object> js_external_reboot = make_napi_external(
975
+ info.Env(),
976
+ py_reboot,
977
+ &reboot_aio_tests_Reboot);
439
978
 
440
979
  return js_external_reboot;
441
980
  }
@@ -468,15 +1007,12 @@ Napi::Value Reboot_createExternalContext(const Napi::CallbackInfo& info) {
468
1007
  app_internal = info[5].As<Napi::Boolean>();
469
1008
  }
470
1009
 
471
- std::promise<py::object*> promise;
472
-
473
- adaptor->ScheduleCallbackOnPythonEventLoop(
1010
+ py::object* py_context = RunCallbackOnPythonEventLoop(
474
1011
  [py_reboot,
475
1012
  &name,
476
1013
  &idempotency_seed,
477
1014
  &bearer_token,
478
- app_internal,
479
- &promise]() {
1015
+ app_internal]() {
480
1016
  py::object py_idempotency_seed = py::none();
481
1017
  if (idempotency_seed.has_value()) {
482
1018
  py_idempotency_seed =
@@ -487,29 +1023,18 @@ Napi::Value Reboot_createExternalContext(const Napi::CallbackInfo& info) {
487
1023
  if (bearer_token.has_value()) {
488
1024
  py_bearer_token = py::str(*bearer_token);
489
1025
  }
490
- promise.set_value(
491
- new py::object(
492
- py_reboot->attr("create_external_context")(
493
- "name"_a = name,
494
- "idempotency_seed"_a = py_idempotency_seed,
495
- "bearer_token"_a = py_bearer_token,
496
- "app_internal"_a = app_internal)));
1026
+ return new py::object(
1027
+ py_reboot->attr("create_external_context")(
1028
+ "name"_a = name,
1029
+ "idempotency_seed"_a = py_idempotency_seed,
1030
+ "bearer_token"_a = py_bearer_token,
1031
+ "app_internal"_a = app_internal));
497
1032
  });
498
1033
 
499
- py::object* py_context = promise.get_future().get();
500
-
501
- Napi::External<py::object> js_external_context =
502
- Napi::External<py::object>::New(
503
- info.Env(),
504
- py_context,
505
- [](Napi::Env, py::object* py_context) {
506
- adaptor->ScheduleCallbackOnPythonEventLoop(
507
- [py_context]() {
508
- delete py_context;
509
- });
510
- });
511
-
512
- js_external_context.TypeTag(&reboot_aio_external_ExternalContext);
1034
+ Napi::External<py::object> js_external_context = make_napi_external(
1035
+ info.Env(),
1036
+ py_context,
1037
+ &reboot_aio_external_ExternalContext);
513
1038
 
514
1039
  return js_from_native_external
515
1040
  .Call(
@@ -518,61 +1043,6 @@ Napi::Value Reboot_createExternalContext(const Napi::CallbackInfo& info) {
518
1043
  }
519
1044
 
520
1045
 
521
- struct NapiReferenceDeleter {
522
- template <typename T>
523
- void operator()(Napi::Reference<T>* reference) {
524
- adaptor->ScheduleCallbackOnNodeEventLoop(
525
- [reference](Napi::Env) {
526
- delete reference;
527
- });
528
- }
529
- };
530
-
531
-
532
- template <typename T>
533
- class NapiSafeReference {
534
- public:
535
- NapiSafeReference(T t)
536
- : _reference(
537
- new Napi::Reference<T>(Napi::Persistent(std::move(t))),
538
- NapiReferenceDeleter()) {}
539
-
540
- NapiSafeReference(Napi::Reference<T>&& reference)
541
- : _reference(
542
- new Napi::Reference<T>(std::move(reference)),
543
- NapiReferenceDeleter()) {}
544
-
545
- // Helper for getting the value of the reference. We require a
546
- // `Napi::Env` to ensure we only try and get the value from within a
547
- // Node thread (e.g., using the main thread or from within a NAPI
548
- // thread safe function).
549
- T Value(Napi::Env) {
550
- return _reference->Value();
551
- }
552
-
553
- private:
554
- std::shared_ptr<Napi::Reference<T>> _reference;
555
- };
556
-
557
- using NapiSafeFunctionReference = NapiSafeReference<Napi::Function>;
558
- using NapiSafeObjectReference = NapiSafeReference<Napi::Object>;
559
-
560
- // For every servicer we construct a _subclass_ of our Python
561
- // generated Node adaptor, e.g., `GreeterServicerNodeAdaptor`, _at
562
- // runtime_ which we add to the following Python module. This module
563
- // also includes our C++ <-> Python class wrappers for storing NAPI
564
- // objects in Python to properly handle memory management.
565
- PYBIND11_EMBEDDED_MODULE(reboot_native, m) {
566
- py::class_<NapiSafeFunctionReference>(
567
- m,
568
- "NapiSafeFunctionReference");
569
-
570
- py::class_<NapiSafeObjectReference>(
571
- m,
572
- "NapiSafeObjectReference");
573
- }
574
-
575
-
576
1046
  // NOTE: must be called within _Node_.
577
1047
  Napi::Object make_js_context(
578
1048
  Napi::Env& env,
@@ -580,58 +1050,34 @@ Napi::Object make_js_context(
580
1050
  py::object* py_aborted,
581
1051
  const std::string& kind) {
582
1052
  Napi::External<py::object> js_external_context =
583
- Napi::External<py::object>::New(
584
- env,
585
- py_context,
586
- [](Napi::Env, py::object* py_context) {
587
- adaptor->ScheduleCallbackOnPythonEventLoop(
588
- [py_context]() {
589
- delete py_context;
590
- });
591
- });
592
-
593
- auto deferred = std::make_shared<Napi::Promise::Deferred>(env);
594
-
595
- auto promise = deferred->Promise();
596
-
597
- adaptor->ScheduleCallbackOnPythonEventLoop(
598
- [py_aborted, deferred = std::move(deferred)]() {
599
- py_aborted->attr("add_done_callback")(py::cpp_function(
600
- [deferred = std::move(deferred)](
601
- py::object py_future) {
602
- py::object py_exception =
603
- py_future.attr("exception")();
604
-
605
- std::optional<std::string> exception;
606
-
607
- if (!py_exception.is_none()) {
608
- py::str py_exception_string =
609
- py_exception_str(py_exception);
610
-
611
- exception.emplace(py_exception_string);
612
- }
613
-
614
- adaptor->ScheduleCallbackOnNodeEventLoop(
615
- [deferred = std::move(deferred),
616
- exception = std::move(exception)](
617
- Napi::Env env) {
618
- if (!exception.has_value()) {
619
- deferred->Resolve(env.Undefined());
620
- } else {
621
- deferred->Reject(
622
- Napi::Error::New(env, *exception)
623
- .Value());
624
- }
625
- });
626
- }));
1053
+ make_napi_external(env, py_context);
1054
+
1055
+ Napi::Promise js_aborted = NodePromiseFromPythonFuture(
1056
+ env,
1057
+ [py_aborted]() {
1058
+ // After returning we won't need `py_aborted` anymore, so we
1059
+ // make a copy on the stack so we can delete our copy on the
1060
+ // heap.
1061
+ py::object py_aborted_on_stack = *py_aborted;
627
1062
  delete py_aborted;
1063
+ return py_aborted_on_stack;
1064
+ },
1065
+ [](py::object py_result) {
1066
+ // TODO: check that `py_result` is `None`.
1067
+ return std::nullopt;
1068
+ },
1069
+ [](Napi::Env env, std::nullopt_t) {
1070
+ // NOTE: we're explicitly returning `undefined` here instead
1071
+ // of `null` which is the default if we were to not include
1072
+ // this lambda or the one above.
1073
+ return env.Undefined();
628
1074
  });
629
1075
 
630
1076
  return js_Context_fromNativeExternal
631
1077
  ->Call(
632
1078
  {js_external_context,
633
1079
  Napi::String::New(env, kind),
634
- promise})
1080
+ js_aborted})
635
1081
  .As<Napi::Object>();
636
1082
  }
637
1083
 
@@ -661,21 +1107,17 @@ py::object make_py_authorizer(NapiSafeObjectReference js_authorizer) {
661
1107
  py::object py_aborted,
662
1108
  std::optional<std::string> bytes_state,
663
1109
  std::optional<std::string> bytes_request) {
664
- py::object py_future =
665
- py::module::import("asyncio").attr("Future")();
666
-
667
1110
  NapiSafeObjectReference* js_authorizer_reference =
668
1111
  self.attr("_js_authorizer")
669
1112
  .cast<NapiSafeObjectReference*>();
670
1113
 
671
- adaptor->ScheduleCallbackOnNodeEventLoop(
1114
+ return PythonFutureFromNodePromise(
672
1115
  [js_authorizer_reference,
673
1116
  py_reader_context = new py::object(py_reader_context),
674
1117
  py_aborted = new py::object(py_aborted),
675
1118
  method_name,
676
1119
  bytes_state = std::move(bytes_state),
677
- bytes_request = std::move(bytes_request),
678
- py_future = new py::object(py_future)](Napi::Env env) {
1120
+ bytes_request = std::move(bytes_request)](Napi::Env env) {
679
1121
  std::vector<Napi::Value> js_args;
680
1122
 
681
1123
  Napi::Object js_context =
@@ -695,63 +1137,18 @@ py::object make_py_authorizer(NapiSafeObjectReference js_authorizer) {
695
1137
  js_args.push_back(js_bytes_state);
696
1138
  js_args.push_back(js_bytes_request);
697
1139
 
698
- Napi::Object js_promise =
699
- js_authorizer.Get("_authorize")
700
- .As<Napi::Function>()
701
- .Call(js_authorizer, {js_args})
702
- .As<Napi::Object>();
703
-
704
- js_promise
705
- .Get("then")
1140
+ return js_authorizer.Get("_authorize")
706
1141
  .As<Napi::Function>()
707
- .Call(
708
- js_promise,
709
- {Napi::Function::New(
710
- env,
711
- [py_future](
712
- const Napi::CallbackInfo& info) {
713
- Napi::Uint8Array js_bytes_decision =
714
- info[0].As<Napi::Uint8Array>();
715
-
716
- std::string bytes_decision = uint8array_to_str(
717
- js_bytes_decision);
718
-
719
- adaptor->ScheduleCallbackOnPythonEventLoop(
720
- [py_future, bytes_decision]() {
721
- bool cancelled =
722
- py_future->attr("cancelled")()
723
- .cast<bool>();
724
- if (!cancelled) {
725
- py_future->attr("set_result")(
726
- py::bytes(bytes_decision));
727
- }
728
- delete py_future;
729
- });
730
- }),
731
- Napi::Function::New(
732
- env,
733
- [py_future](const Napi::CallbackInfo& info) {
734
- std::string message = message_from_js_error(
735
- info[0].As<Napi::Object>());
736
-
737
- adaptor->ScheduleCallbackOnPythonEventLoop(
738
- [py_future,
739
- message = std::move(message)]() {
740
- bool cancelled =
741
- py_future->attr("cancelled")()
742
- .cast<bool>();
743
- if (!cancelled) {
744
- py_future->attr("set_exception")(
745
- py::module::import("builtins")
746
- .attr("Exception")(
747
- message));
748
- }
749
- delete py_future;
750
- });
751
- })});
1142
+ .Call(js_authorizer, {js_args})
1143
+ .As<Napi::Object>();
1144
+ },
1145
+ [](Napi::Env env, Napi::Value js_bytes_decision) {
1146
+ return uint8array_to_str(
1147
+ js_bytes_decision.As<Napi::Uint8Array>());
1148
+ },
1149
+ [](std::string&& bytes_decision) {
1150
+ return py::bytes(bytes_decision);
752
1151
  });
753
-
754
- return py_future;
755
1152
  },
756
1153
  py::name("_authorize"),
757
1154
  py::arg("method_name"),
@@ -795,7 +1192,7 @@ using ServicerDetails =
795
1192
 
796
1193
  // NOTE: must be called within _Node_.
797
1194
  std::vector<std::shared_ptr<ServicerDetails>> make_servicer_details(
798
- Napi::Env& env,
1195
+ Napi::Env env,
799
1196
  const Napi::Array& js_servicers) {
800
1197
  std::vector<std::shared_ptr<ServicerDetails>> servicer_details;
801
1198
 
@@ -887,13 +1284,10 @@ py::object make_py_user_servicer(
887
1284
  py_self.attr("_js_servicer_constructor")
888
1285
  .cast<NapiSafeFunctionReference*>();
889
1286
 
890
- std::promise<NapiSafeObjectReference> promise;
891
-
892
- adaptor->ScheduleCallbackOnNodeEventLoop(
893
- [&promise,
894
- py_self = new py::object(py_self),
895
- js_servicer_constructor](Napi::Env env) {
896
- try {
1287
+ try {
1288
+ return RunCallbackOnNodeEventLoop(
1289
+ [py_self = new py::object(py_self),
1290
+ js_servicer_constructor](Napi::Env env) {
897
1291
  Napi::Object js_servicer =
898
1292
  js_servicer_constructor->Value(env).New({});
899
1293
 
@@ -901,35 +1295,16 @@ py::object make_py_user_servicer(
901
1295
  // external so that we can call `read()` and `write()`
902
1296
  // on it.
903
1297
  Napi::External<py::object> js_external_self =
904
- Napi::External<py::object>::New(
905
- env,
906
- py_self,
907
- [](Napi::Env, py::object* py_self) {
908
- adaptor->ScheduleCallbackOnPythonEventLoop(
909
- [py_self]() {
910
- delete py_self;
911
- });
912
- });
1298
+ make_napi_external(env, py_self);
913
1299
 
914
1300
  js_servicer
915
1301
  .Get("__storeExternal")
916
1302
  .As<Napi::Function>()
917
1303
  .Call(js_servicer, {js_external_self});
918
1304
 
919
- promise.set_value(NapiSafeObjectReference(js_servicer));
920
- } catch (const Napi::Error& e) {
921
- env.Global()
922
- .Get("console")
923
- .As<Napi::Object>()
924
- .Get("log")
925
- .As<Napi::Function>()
926
- .Call({e.Value(), Napi::String::New(env, "\n")});
927
- promise.set_exception(
928
- std::make_exception_ptr(std::runtime_error(e.what())));
929
- }
930
- });
931
- try {
932
- return promise.get_future().get();
1305
+ return NapiSafeObjectReference(js_servicer);
1306
+ },
1307
+ /* warn = */ true);
933
1308
  } catch (const std::exception& e) {
934
1309
  PyErr_SetString(
935
1310
  py::module::import("reboot.aio.servers")
@@ -951,18 +1326,14 @@ py::object make_py_user_servicer(
951
1326
  py::object py_aborted,
952
1327
  const std::string& json_state,
953
1328
  const std::string& json_request) {
954
- py::object py_future =
955
- py::module::import("asyncio").attr("Future")();
956
-
957
- adaptor->ScheduleCallbackOnNodeEventLoop(
1329
+ return PythonFutureFromNodePromise(
958
1330
  [&js_servicer_reference,
959
1331
  kind,
960
1332
  method,
961
1333
  py_context = new py::object(py_context),
962
1334
  py_aborted = new py::object(py_aborted),
963
1335
  json_state,
964
- json_request,
965
- py_future = new py::object(py_future)](Napi::Env env) {
1336
+ json_request](Napi::Env env) {
966
1337
  std::vector<Napi::Value> js_args;
967
1338
 
968
1339
  Napi::Object js_context =
@@ -976,94 +1347,37 @@ py::object make_py_user_servicer(
976
1347
  Napi::Object js_servicer =
977
1348
  js_servicer_reference.Value(env);
978
1349
 
979
- Napi::Object js_promise =
980
- js_servicer
981
- .Get("_" + method)
982
- .As<Napi::Function>()
983
- .Call(js_servicer, js_args)
984
- .As<Napi::Object>();
985
-
986
- js_promise
987
- .Get("then")
1350
+ return js_servicer
1351
+ .Get("_" + method)
988
1352
  .As<Napi::Function>()
989
- .Call(
990
- js_promise,
991
- {Napi::Function::New(
992
- env,
993
- [py_future](const Napi::CallbackInfo& info) {
994
- std::string json =
995
- info[0].As<Napi::String>().Utf8Value();
996
-
997
- adaptor->ScheduleCallbackOnPythonEventLoop(
998
- [py_future, json]() {
999
- bool cancelled =
1000
- py_future->attr("cancelled")()
1001
- .cast<bool>();
1002
- if (!cancelled) {
1003
- py_future->attr("set_result")(json);
1004
- }
1005
- delete py_future;
1006
- });
1007
- }),
1008
- Napi::Function::New(
1009
- env,
1010
- [py_future](const Napi::CallbackInfo& info) {
1011
- std::string message = message_from_js_error(
1012
- info[0].As<Napi::Object>());
1013
-
1014
- adaptor->ScheduleCallbackOnPythonEventLoop(
1015
- [py_future,
1016
- message = std::move(message)]() {
1017
- bool cancelled =
1018
- py_future->attr("cancelled")()
1019
- .cast<bool>();
1020
- if (!cancelled) {
1021
- py_future->attr("set_exception")(
1022
- py::module::import("builtins")
1023
- .attr("Exception")(
1024
- message));
1025
- }
1026
- delete py_future;
1027
- });
1028
- })});
1353
+ .Call(js_servicer, js_args)
1354
+ .As<Napi::Object>();
1355
+ },
1356
+ [](Napi::Env env, Napi::Value value) {
1357
+ return std::string(value.As<Napi::String>().Utf8Value());
1029
1358
  });
1030
-
1031
- return py_future;
1032
1359
  });
1033
1360
 
1034
1361
  // Constructs and returns an Authorizer.
1035
1362
  attributes["_construct_authorizer"] = py::cpp_function(
1036
1363
  [](NapiSafeObjectReference& js_servicer_reference) -> py::object {
1037
- std::promise<std::optional<NapiSafeObjectReference>> promise;
1038
- adaptor->ScheduleCallbackOnNodeEventLoop(
1039
- [&js_servicer_reference, &promise](Napi::Env env) {
1040
- try {
1364
+ std::optional<NapiSafeObjectReference> js_authorizer;
1365
+ try {
1366
+ js_authorizer = RunCallbackOnNodeEventLoop(
1367
+ [&js_servicer_reference](Napi::Env env)
1368
+ -> std::optional<NapiSafeObjectReference> {
1041
1369
  Napi::Object js_servicer = js_servicer_reference.Value(env);
1042
1370
  Napi::Value js_authorizer =
1043
1371
  js_servicer.Get("_authorizer")
1044
1372
  .As<Napi::Function>()
1045
1373
  .Call(js_servicer, {});
1046
1374
  if (!js_authorizer.IsNull()) {
1047
- promise.set_value(
1048
- NapiSafeObjectReference(
1049
- js_authorizer.As<Napi::Object>()));
1375
+ return NapiSafeObjectReference(
1376
+ js_authorizer.As<Napi::Object>());
1050
1377
  } else {
1051
- promise.set_value(std::nullopt);
1378
+ return std::nullopt;
1052
1379
  }
1053
- } catch (const Napi::Error& e) {
1054
- env.Global()
1055
- .Get("console")
1056
- .As<Napi::Object>()
1057
- .Get("log")
1058
- .As<Napi::Function>()
1059
- .Call({e.Value(), Napi::String::New(env, "\n")});
1060
- promise.set_exception(
1061
- std::make_exception_ptr(std::runtime_error(e.what())));
1062
- }
1063
- });
1064
- std::optional<NapiSafeObjectReference> js_authorizer;
1065
- try {
1066
- js_authorizer = promise.get_future().get();
1380
+ });
1067
1381
  } catch (const std::exception& e) {
1068
1382
  PyErr_SetString(
1069
1383
  py::module::import("reboot.aio.servers")
@@ -1156,20 +1470,28 @@ py::object make_py_token_verifier(NapiSafeObjectReference js_token_verifier) {
1156
1470
  [](py::object self,
1157
1471
  py::object py_reader_context,
1158
1472
  py::object py_aborted,
1159
- py::object token) {
1160
- py::object py_future =
1161
- py::module::import("asyncio").attr("Future")();
1162
-
1473
+ py::object py_token) {
1163
1474
  NapiSafeObjectReference* js_token_verifier_reference =
1164
1475
  self.attr("_js_token_verifier")
1165
1476
  .cast<NapiSafeObjectReference*>();
1166
1477
 
1167
- adaptor->ScheduleCallbackOnNodeEventLoop(
1478
+ // Converting to 'py::str' involves Python runtime access.
1479
+ // Perform this conversion here, on the Python thread, before entering
1480
+ // Node.
1481
+ std::optional<std::string> token;
1482
+ if (!py_token.is_none()) {
1483
+ token = py::str(py_token);
1484
+ }
1485
+
1486
+ return PythonFutureFromNodePromise(
1168
1487
  [js_token_verifier_reference,
1488
+ // We allocate 'py_aborted' and 'py_reader_context' with 'new
1489
+ // py::object' on the Python thread, ensuring they remain valid
1490
+ // when passed into Node. Cleanup is handled later on the Python
1491
+ // thread, see 'make_js_context'.
1169
1492
  py_reader_context = new py::object(py_reader_context),
1170
1493
  py_aborted = new py::object(py_aborted),
1171
- token = new py::object(token),
1172
- py_future = new py::object(py_future)](Napi::Env env) {
1494
+ token = std::move(token)](Napi::Env env) {
1173
1495
  std::vector<Napi::Value> js_args;
1174
1496
 
1175
1497
  js_args.push_back(
@@ -1179,90 +1501,49 @@ py::object make_py_token_verifier(NapiSafeObjectReference js_token_verifier) {
1179
1501
  py_aborted,
1180
1502
  "reader"));
1181
1503
 
1182
- if (token->is_none()) {
1183
- js_args.push_back(env.Undefined());
1504
+ if (token.has_value()) {
1505
+ js_args.push_back(Napi::String::New(env, *token));
1184
1506
  } else {
1185
- js_args.push_back(
1186
- Napi::String::New(env, std::string(py::str(*token))));
1507
+ js_args.push_back(env.Undefined());
1187
1508
  }
1188
1509
 
1189
1510
  Napi::Object js_token_verifier =
1190
1511
  js_token_verifier_reference->Value(env);
1191
- Napi::Object js_promise =
1192
- js_token_verifier
1193
- .Get("_verifyToken")
1194
- .As<Napi::Function>()
1195
- .Call(js_token_verifier, {js_args})
1196
- .As<Napi::Object>();
1197
-
1198
- js_promise
1199
- .Get("then")
1512
+
1513
+ return js_token_verifier
1514
+ .Get("_verifyToken")
1200
1515
  .As<Napi::Function>()
1201
- .Call(
1202
- js_promise,
1203
- {Napi::Function::New(
1204
- env,
1205
- [py_future](const Napi::CallbackInfo& info) {
1206
- std::optional<std::string> bytes_auth;
1207
- if (!info[0].IsNull()) {
1208
- bytes_auth =
1209
- uint8array_to_str(
1210
- info[0].As<Napi::Uint8Array>());
1211
- }
1212
-
1213
- adaptor->ScheduleCallbackOnPythonEventLoop(
1214
- [py_future, bytes_auth]() {
1215
- bool cancelled =
1216
- py_future->attr("cancelled")()
1217
- .cast<bool>();
1218
- if (!cancelled) {
1219
- if (bytes_auth.has_value()) {
1220
- py_future->attr("set_result")(
1221
- py::bytes(*bytes_auth));
1222
- } else {
1223
- py_future->attr("set_result")(
1224
- py::none());
1225
- }
1226
- }
1227
- delete py_future;
1228
- });
1229
- }),
1230
- Napi::Function::New(
1231
- env,
1232
- [py_future](const Napi::CallbackInfo& info) {
1233
- std::string message = message_from_js_error(
1234
- info[0].As<Napi::Object>());
1235
-
1236
- adaptor->ScheduleCallbackOnPythonEventLoop(
1237
- [py_future,
1238
- message = std::move(message)]() {
1239
- bool cancelled =
1240
- py_future->attr("cancelled")()
1241
- .cast<bool>();
1242
- if (!cancelled) {
1243
- py_future->attr("set_exception")(
1244
- py::module::import("builtins")
1245
- .attr("Exception")(
1246
- message));
1247
- }
1248
- delete py_future;
1249
- });
1250
- })});
1516
+ .Call(js_token_verifier, {js_args})
1517
+ .As<Napi::Object>();
1518
+ },
1519
+ [](Napi::Env env, Napi::Value value) {
1520
+ std::optional<std::string> bytes_auth;
1521
+ if (!value.IsNull()) {
1522
+ bytes_auth = uint8array_to_str(value.As<Napi::Uint8Array>());
1523
+ }
1524
+ return bytes_auth;
1525
+ },
1526
+ [](std::optional<std::string>&& bytes_auth) -> py::object {
1527
+ if (bytes_auth.has_value()) {
1528
+ return py::bytes(*bytes_auth);
1529
+ } else {
1530
+ return py::none();
1531
+ }
1251
1532
  });
1252
-
1253
- return py_future;
1254
1533
  },
1255
1534
  py::name("_verify_token"),
1256
1535
  py::arg("context"),
1257
1536
  py::arg("aborted"),
1258
- py::arg("token"),
1537
+ py::arg("py_token"),
1259
1538
  py::is_method(py::none()));
1260
1539
 
1261
1540
  py::object py_parent_class =
1262
1541
  py::module::import("reboot.nodejs.python")
1263
1542
  .attr("NodeAdaptorTokenVerifier");
1543
+
1264
1544
  py::object py_parent_metaclass = py::reinterpret_borrow<py::object>(
1265
1545
  (PyObject*) &PyType_Type);
1546
+
1266
1547
  py::object py_token_verifier = py_parent_metaclass(
1267
1548
  "_NodeAdaptorTokenVerifier",
1268
1549
  py::make_tuple(py_parent_class),
@@ -1283,94 +1564,45 @@ Napi::Value Reboot_up(const Napi::CallbackInfo& info) {
1283
1564
 
1284
1565
  py::object* py_reboot = js_external_reboot.Value(info.Env()).Data();
1285
1566
 
1286
- Napi::Array js_servicers = info[1].As<Napi::Array>();
1567
+ auto js_external_application = NapiSafeReference(
1568
+ info[1].As<Napi::External<py::object>>());
1287
1569
 
1288
- std::optional<NapiSafeObjectReference> js_token_verifier;
1289
- if (info[2].IsObject()) {
1290
- js_token_verifier = NapiSafeReference(info[2].As<Napi::Object>());
1291
- }
1570
+ py::object* py_application = js_external_application.Value(info.Env()).Data();
1292
1571
 
1293
1572
  bool local_envoy = false;
1294
- if (!info[3].IsUndefined()) {
1295
- local_envoy = info[3].As<Napi::Boolean>();
1573
+ if (!info[2].IsUndefined()) {
1574
+ local_envoy = info[2].As<Napi::Boolean>();
1296
1575
  }
1297
1576
 
1298
- auto env = info.Env();
1299
- auto servicer_details = make_servicer_details(env, js_servicers);
1300
-
1301
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
1302
-
1303
- auto promise = deferred->Promise();
1304
-
1305
- adaptor->ScheduleCallbackOnPythonEventLoop(
1577
+ return NodePromiseFromPythonTask(
1578
+ info.Env(),
1579
+ "Reboot.up(...) in nodejs",
1580
+ {"asyncio", "create_task"},
1306
1581
  [js_external_reboot, // Ensures `py_reboot` remains valid.
1307
1582
  py_reboot,
1308
- js_token_verifier,
1309
- local_envoy,
1310
- servicer_details = std::move(servicer_details),
1311
- deferred = std::move(deferred)]() {
1312
- py::list py_servicers = make_py_servicers(servicer_details);
1313
- py::object py_token_verifier = py::none();
1314
- if (js_token_verifier.has_value()) {
1315
- py_token_verifier = make_py_token_verifier(*js_token_verifier);
1316
- }
1317
-
1318
- py::object py_task =
1319
- py::module::import("asyncio").attr("create_task")(
1320
- py_reboot->attr("up")(
1321
- "servicers"_a = py_servicers,
1322
- "token_verifier"_a = py_token_verifier,
1323
- // NOTE: while we support subprocess consensuses
1324
- // for `rbt dev` and `rbt serve` we do not support
1325
- // them for tests because we don't have a way to
1326
- // clone a process like we do with multiprocessing
1327
- // in Python.
1328
- "in_process"_a = true,
1329
- "local_envoy"_a = local_envoy),
1330
- "name"_a = "Reboot.up(...) in nodejs");
1331
-
1332
- py_task.attr("add_done_callback")(py::cpp_function(
1333
- [deferred = std::move(deferred),
1334
- // NOTE: need to keep a reference to `py_task` so that it
1335
- // doesn't get destroyed before completing!
1336
- py_task = new py::object(py_task)](py::object py_future) {
1337
- py::object py_exception = py_future.attr("exception")();
1338
-
1339
- std::optional<std::string> application_id;
1340
- std::optional<std::string> exception;
1341
-
1342
- if (py_exception.is_none()) {
1343
- py::str py_application_id =
1344
- py_future.attr("result")().attr("application_id")();
1345
- application_id.emplace(py_application_id);
1346
- } else {
1347
- py::str py_exception_string = py_exception_str(py_exception);
1348
-
1349
- exception.emplace(py_exception_string);
1350
- }
1351
-
1352
- adaptor->ScheduleCallbackOnNodeEventLoop(
1353
- [deferred = std::move(deferred),
1354
- application_id = std::move(application_id),
1355
- exception = std::move(exception)](Napi::Env env) {
1356
- if (application_id.has_value()) {
1357
- auto js_application_config = Napi::Object::New(env);
1358
- js_application_config.Set(
1359
- "applicationId",
1360
- *application_id);
1361
- deferred->Resolve(js_application_config);
1362
- } else {
1363
- deferred->Reject(
1364
- Napi::Error::New(env, *exception).Value());
1365
- }
1366
- });
1367
-
1368
- delete py_task;
1369
- },
1370
- py::arg("future")));
1583
+ js_external_application, // Ensures `py_application` remains valid.
1584
+ py_application,
1585
+ local_envoy]() {
1586
+ return py_reboot->attr("up")(
1587
+ py_application,
1588
+ // NOTE: while we support subprocess consensuses
1589
+ // for `rbt dev` and `rbt serve` we do not support
1590
+ // them for tests because we don't have a way to
1591
+ // clone a process like we do with multiprocessing
1592
+ // in Python.
1593
+ "in_process"_a = true,
1594
+ "local_envoy"_a = local_envoy);
1595
+ },
1596
+ [](py::object py_revision) {
1597
+ py::str py_application_id =
1598
+ py_revision.attr("config").attr("application_id")();
1599
+ return std::string(py_application_id);
1600
+ },
1601
+ [](Napi::Env env, std::string&& application_id) {
1602
+ Napi::Object js_revision = Napi::Object::New(env);
1603
+ js_revision.Set("applicationId", application_id);
1604
+ return js_revision;
1371
1605
  });
1372
-
1373
- return promise;
1374
1606
  }
1375
1607
 
1376
1608
  void Reboot_down(const Napi::CallbackInfo& info) {
@@ -1386,60 +1618,24 @@ Napi::Value Reboot_start(const Napi::CallbackInfo& info) {
1386
1618
 
1387
1619
  // CHECK(js_external_reboot.CheckTypeTag(&reboot_aio_tests_Reboot));
1388
1620
 
1389
- py::object* py_reboot = js_external_reboot.Value(info.Env()).Data();
1390
-
1391
- // Call Ref() on our NAPI thread safe function so that it can't be cleaned up
1392
- // until Unref() is called in `Reboot_stop()`.
1393
- // See:
1394
- // (check_line_length skip)
1395
- // https://nodejs.github.io/node-addon-examples/special-topics/thread-safe-functions/#q-my-application-isnt-exiting-correctly-it-just-hangs.
1396
- // Note that Ref() and Unref() will set and unset an "is_referenced" boolean
1397
- // flag, rather than incrementing and decrementing a reference count.
1398
- adaptor->thread_safe_function.Ref(info.Env());
1399
-
1400
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
1401
-
1402
- auto promise = deferred->Promise();
1403
-
1404
- adaptor->ScheduleCallbackOnPythonEventLoop(
1405
- [py_reboot, deferred = std::move(deferred)]() {
1406
- py::object py_task =
1407
- py::module::import("asyncio").attr("create_task")(
1408
- py_reboot->attr("start")(),
1409
- "name"_a = "Reboot.start() in nodejs");
1410
-
1411
- py_task.attr("add_done_callback")(py::cpp_function(
1412
- [deferred = std::move(deferred),
1413
- // NOTE: need to keep a reference to `py_task` so that it
1414
- // doesn't get destroyed before completing!
1415
- py_task = new py::object(py_task)](py::object py_future) {
1416
- py::object py_exception = py_future.attr("exception")();
1417
-
1418
- std::optional<std::string> exception;
1419
-
1420
- if (!py_exception.is_none()) {
1421
- py::str py_exception_string = py_exception_str(py_exception);
1422
-
1423
- exception.emplace(py_exception_string);
1424
- }
1621
+ py::object* py_reboot = js_external_reboot.Value(info.Env()).Data();
1425
1622
 
1426
- adaptor->ScheduleCallbackOnNodeEventLoop(
1427
- [deferred = std::move(deferred),
1428
- exception = std::move(exception)](Napi::Env env) {
1429
- if (!exception.has_value()) {
1430
- deferred->Resolve(env.Null());
1431
- } else {
1432
- deferred->Reject(
1433
- Napi::Error::New(env, *exception).Value());
1434
- }
1435
- });
1623
+ // Call Ref() on our NAPI thread safe function so that it can't be cleaned up
1624
+ // until Unref() is called in `Reboot_stop()`.
1625
+ // See:
1626
+ // (check_line_length skip)
1627
+ // https://nodejs.github.io/node-addon-examples/special-topics/thread-safe-functions/#q-my-application-isnt-exiting-correctly-it-just-hangs.
1628
+ // Note that Ref() and Unref() will set and unset an "is_referenced" boolean
1629
+ // flag, rather than incrementing and decrementing a reference count.
1630
+ adaptor->thread_safe_function.Ref(info.Env());
1436
1631
 
1437
- delete py_task;
1438
- },
1439
- py::arg("future")));
1632
+ return NodePromiseFromPythonTask(
1633
+ info.Env(),
1634
+ "Reboot.start() in nodejs",
1635
+ {"asyncio", "create_task"},
1636
+ [py_reboot]() {
1637
+ return py_reboot->attr("start")();
1440
1638
  });
1441
-
1442
- return promise;
1443
1639
  }
1444
1640
 
1445
1641
  Napi::Value Reboot_stop(const Napi::CallbackInfo& info) {
@@ -1453,58 +1649,34 @@ Napi::Value Reboot_stop(const Napi::CallbackInfo& info) {
1453
1649
 
1454
1650
  py::object* py_reboot = js_external_reboot.Value(info.Env()).Data();
1455
1651
 
1456
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
1457
-
1458
- auto promise = deferred->Promise();
1459
-
1460
- adaptor->ScheduleCallbackOnPythonEventLoop(
1461
- [py_reboot, deferred = std::move(deferred)]() {
1462
- py::object py_task =
1463
- py::module::import("asyncio").attr("create_task")(
1464
- py_reboot->attr("stop")(),
1465
- "name"_a = "Reboot.stop() in nodejs");
1466
-
1467
- py_task.attr("add_done_callback")(py::cpp_function(
1468
- [deferred = std::move(deferred),
1469
- // NOTE: need to keep a reference to `py_task` so that it
1470
- // doesn't get destroyed before completing!
1471
- py_task = new py::object(py_task)](py::object py_future) {
1472
- py::object py_exception = py_future.attr("exception")();
1473
-
1474
- std::optional<std::string> exception;
1475
-
1476
- if (!py_exception.is_none()) {
1477
- py::str py_exception_string = py_exception_str(py_exception);
1478
-
1479
- exception.emplace(py_exception_string);
1480
- }
1481
-
1482
- adaptor->ScheduleCallbackOnNodeEventLoop(
1483
- [deferred = std::move(deferred),
1484
- exception = std::move(exception)](Napi::Env env) {
1485
- if (!exception.has_value()) {
1486
- deferred->Resolve(env.Null());
1487
- } else {
1488
- deferred->Reject(
1489
- Napi::Error::New(env, *exception).Value());
1490
- }
1491
- // Call Unref() on our NAPI thread safe function so that the
1492
- // object can be destroyed and the event loop can exit.
1493
- // See:
1494
- // (check_line_length skip)
1495
- // https://nodejs.github.io/node-addon-examples/special-topics/thread-safe-functions/#q-my-application-isnt-exiting-correctly-it-just-hangs.
1496
- // Note that Ref() and Unref() will set and unset an
1497
- // "is_referenced" boolean flag, rather than incrementing
1498
- // and decrementing a reference count.
1499
- adaptor->thread_safe_function.Unref(env);
1500
- });
1652
+ Napi::Promise js_promise = NodePromiseFromPythonTask(
1653
+ info.Env(),
1654
+ "Reboot.stop() in nodejs",
1655
+ {"asyncio", "create_task"},
1656
+ [py_reboot]() {
1657
+ return py_reboot->attr("stop")();
1658
+ });
1501
1659
 
1502
- delete py_task;
1503
- },
1504
- py::arg("future")));
1660
+ // Call Unref() on our NAPI thread safe function so that the
1661
+ // object can be destroyed and the event loop can exit.
1662
+ // See:
1663
+ // (check_line_length skip)
1664
+ // https://nodejs.github.io/node-addon-examples/special-topics/thread-safe-functions/#q-my-application-isnt-exiting-correctly-it-just-hangs.
1665
+ // Note that Ref() and Unref() will set and unset an
1666
+ // "is_referenced" boolean flag, rather than incrementing
1667
+ // and decrementing a reference count.
1668
+ auto js_resolve_reject = Napi::Function::New(
1669
+ info.Env(),
1670
+ [](const Napi::CallbackInfo& info) {
1671
+ adaptor->thread_safe_function.Unref(info.Env());
1505
1672
  });
1506
1673
 
1507
- return promise;
1674
+ js_promise
1675
+ .Get("then")
1676
+ .As<Napi::Function>()
1677
+ .Call(js_promise, {js_resolve_reject, js_resolve_reject});
1678
+
1679
+ return js_promise;
1508
1680
  }
1509
1681
 
1510
1682
  // NOTE: We block on a promise here, so this method should not be called outside
@@ -1520,15 +1692,13 @@ Napi::Value Reboot_url(const Napi::CallbackInfo& info) {
1520
1692
 
1521
1693
  py::object* py_reboot = js_external_reboot.Value(info.Env()).Data();
1522
1694
 
1523
- std::promise<std::string> promise;
1524
-
1525
- adaptor->ScheduleCallbackOnPythonEventLoop(
1526
- [py_reboot, &promise]() {
1695
+ std::string url = RunCallbackOnPythonEventLoop(
1696
+ [py_reboot]() {
1527
1697
  py::str url = py_reboot->attr("url")();
1528
- promise.set_value(std::string(url));
1698
+ return std::string(url);
1529
1699
  });
1530
1700
 
1531
- return Napi::String::New(info.Env(), promise.get_future().get());
1701
+ return Napi::String::New(info.Env(), url);
1532
1702
  }
1533
1703
 
1534
1704
  Napi::Value Service_constructor(const Napi::CallbackInfo& info) {
@@ -1542,35 +1712,22 @@ Napi::Value Service_constructor(const Napi::CallbackInfo& info) {
1542
1712
 
1543
1713
  std::string id = js_args.Get("id").As<Napi::String>().Utf8Value();
1544
1714
 
1545
- std::promise<py::object*> promise;
1546
-
1547
- adaptor->ScheduleCallbackOnPythonEventLoop(
1548
- [&rbt_module, &node_adaptor, &id, &promise]() {
1715
+ py::object* py_service = RunCallbackOnPythonEventLoop(
1716
+ [&rbt_module, &node_adaptor, &id]() {
1549
1717
  py::object py_module = py::module::import(rbt_module.c_str());
1550
1718
  py::object py_schedule_type =
1551
1719
  py_module
1552
1720
  .attr(node_adaptor.c_str())
1553
1721
  .attr("_Schedule");
1554
- promise.set_value(
1555
- new py::object(py_module.attr(node_adaptor.c_str())(
1556
- // The call will stay within the same application.
1557
- "application_id"_a = py::none(),
1558
- "state_id"_a = id,
1559
- "schedule_type"_a = py_schedule_type)));
1722
+ return new py::object(py_module.attr(node_adaptor.c_str())(
1723
+ // The call will stay within the same application.
1724
+ "application_id"_a = py::none(),
1725
+ "state_id"_a = id,
1726
+ "schedule_type"_a = py_schedule_type));
1560
1727
  });
1561
1728
 
1562
- py::object* py_service = promise.get_future().get();
1563
-
1564
1729
  Napi::External<py::object> js_external_service =
1565
- Napi::External<py::object>::New(
1566
- info.Env(),
1567
- py_service,
1568
- [](Napi::Env, py::object* py_service) {
1569
- adaptor->ScheduleCallbackOnPythonEventLoop(
1570
- [py_service]() {
1571
- delete py_service;
1572
- });
1573
- });
1730
+ make_napi_external(info.Env(), py_service);
1574
1731
 
1575
1732
  // auto type_tag = MakeTypeTag(module, type);
1576
1733
  // js_external_service.TypeTag(&type_tag);
@@ -1612,23 +1769,20 @@ Napi::Value Service_call(const Napi::CallbackInfo& info) {
1612
1769
 
1613
1770
  std::string json_options = js_args.Get("jsonOptions").As<Napi::String>();
1614
1771
 
1615
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
1616
-
1617
- auto promise = deferred->Promise();
1618
-
1619
- adaptor->ScheduleCallbackOnPythonEventLoop(
1772
+ return NodePromiseFromPythonTaskWithContext(
1773
+ info.Env(),
1774
+ "servicer._" + kind + "(\"" + method + "\", ...) in nodejs",
1775
+ js_external_context,
1620
1776
  [js_external_service, // Ensures `py_service` remains valid.
1621
1777
  py_service,
1622
1778
  kind,
1623
1779
  method,
1624
1780
  request_module,
1625
1781
  request_type,
1626
- // Ensures `py_context` remains valid.
1627
- js_external_context,
1782
+ js_external_context, // Ensures `py_context` remains valid.
1628
1783
  py_context,
1629
1784
  json_request,
1630
- json_options,
1631
- deferred = std::move(deferred)]() {
1785
+ json_options]() {
1632
1786
  // If a type is a nested message type, we can't import it directly
1633
1787
  // as 'from module import Foo.Bar.Baz', instead we need to import the
1634
1788
  // top level 'Foo' and call 'Foo.Bar.Baz' on it.
@@ -1645,57 +1799,19 @@ Napi::Value Service_call(const Napi::CallbackInfo& info) {
1645
1799
  py_request_type = py_request_type.attr(part.c_str());
1646
1800
  }
1647
1801
 
1648
- py::object py_task =
1649
- py::module::import("reboot.nodejs.python")
1650
- .attr("create_task_with_context")(
1651
- py_service->attr(("_" + kind).c_str())(
1652
- method,
1653
- py_context,
1654
- py_request_type,
1655
- json_request,
1656
- json_options),
1657
- py_context,
1658
- "name"_a = ("servicer._" + kind
1659
- + "(\"" + method + "\", ...) in nodejs")
1660
- .c_str());
1661
-
1662
- py_task.attr("add_done_callback")(py::cpp_function(
1663
- [deferred = std::move(deferred),
1664
- // NOTE: need to keep a reference to `py_task` so that it
1665
- // doesn't get destroyed before completing!
1666
- py_task = new py::object(py_task)](py::object py_future) {
1667
- py::object py_exception = py_future.attr("exception")();
1668
-
1669
- std::optional<std::string> json;
1670
- std::optional<std::string> exception;
1671
-
1672
- if (py_exception.is_none()) {
1673
- py::str py_json = py_future.attr("result")();
1674
- json.emplace(py_json);
1675
- } else {
1676
- py::str py_exception_string = py_exception_str(py_exception);
1677
-
1678
- exception.emplace(py_exception_string);
1679
- }
1680
-
1681
- adaptor->ScheduleCallbackOnNodeEventLoop(
1682
- [deferred = std::move(deferred),
1683
- json = std::move(json),
1684
- exception = std::move(exception)](Napi::Env env) {
1685
- if (json.has_value()) {
1686
- deferred->Resolve(
1687
- Napi::String::New(env, *json));
1688
- } else {
1689
- deferred->Reject(
1690
- Napi::Error::New(env, *exception).Value());
1691
- }
1692
- });
1693
-
1694
- delete py_task;
1695
- }));
1802
+ return py_service->attr(("_" + kind).c_str())(
1803
+ method,
1804
+ py_context,
1805
+ py_request_type,
1806
+ json_request,
1807
+ json_options);
1808
+ },
1809
+ [](py::object py_json) {
1810
+ return py_json.cast<std::string>();
1811
+ },
1812
+ [](Napi::Env env, std::string&& json) {
1813
+ return Napi::String::New(env, json);
1696
1814
  });
1697
-
1698
- return promise;
1699
1815
  }
1700
1816
 
1701
1817
 
@@ -1721,71 +1837,32 @@ Napi::Value Task_await(const Napi::CallbackInfo& info) {
1721
1837
 
1722
1838
  std::string json_task_id = js_args.Get("jsonTaskId").As<Napi::String>();
1723
1839
 
1724
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
1725
-
1726
- auto promise = deferred->Promise();
1727
-
1728
- adaptor->ScheduleCallbackOnPythonEventLoop(
1840
+ return NodePromiseFromPythonTaskWithContext(
1841
+ info.Env(),
1842
+ "reboot.nodejs.python.task_await(\""
1843
+ + state_name + "\", \""
1844
+ + method + "\", ...) in nodejs",
1845
+ js_external_context,
1729
1846
  [rbt_module = std::move(rbt_module),
1730
1847
  state_name = std::move(state_name),
1731
1848
  method = std::move(method),
1732
- // Ensures `py_context` remains valid.
1733
- js_external_context,
1849
+ js_external_context, // Ensures `py_context` remains valid.
1734
1850
  py_context,
1735
- json_task_id,
1736
- deferred = std::move(deferred)]() {
1737
- py::object py_task =
1738
- py::module::import("reboot.nodejs.python")
1739
- .attr("create_task_with_context")(
1740
- py::module::import("reboot.nodejs.python")
1741
- .attr("task_await")(
1742
- py_context,
1743
- py::module::import(rbt_module.c_str())
1744
- .attr(state_name.c_str()),
1745
- method,
1746
- json_task_id),
1747
- py_context,
1748
- "name"_a = ("reboot.nodejs.python.task_await(\""
1749
- + state_name + "\", \""
1750
- + method + "\", ...) in nodejs")
1751
- .c_str());
1752
- py_task.attr("add_done_callback")(py::cpp_function(
1753
- [deferred = std::move(deferred),
1754
- // NOTE: need to keep a reference to `py_task` so that it
1755
- // doesn't get destroyed before completing!
1756
- py_task = new py::object(py_task)](py::object py_future) {
1757
- py::object py_exception = py_future.attr("exception")();
1758
-
1759
- std::optional<std::string> json;
1760
- std::optional<std::string> exception;
1761
-
1762
- if (py_exception.is_none()) {
1763
- py::str py_json = py_future.attr("result")();
1764
- json.emplace(py_json);
1765
- } else {
1766
- py::str py_exception_string = py_exception_str(py_exception);
1767
-
1768
- exception.emplace(py_exception_string);
1769
- }
1770
-
1771
- adaptor->ScheduleCallbackOnNodeEventLoop(
1772
- [deferred = std::move(deferred),
1773
- json = std::move(json),
1774
- exception = std::move(exception)](Napi::Env env) {
1775
- if (json.has_value()) {
1776
- deferred->Resolve(
1777
- Napi::String::New(env, *json));
1778
- } else {
1779
- deferred->Reject(
1780
- Napi::Error::New(env, *exception).Value());
1781
- }
1782
- });
1783
-
1784
- delete py_task;
1785
- }));
1851
+ json_task_id]() {
1852
+ return py::module::import("reboot.nodejs.python")
1853
+ .attr("task_await")(
1854
+ py_context,
1855
+ py::module::import(rbt_module.c_str())
1856
+ .attr(state_name.c_str()),
1857
+ method,
1858
+ json_task_id);
1859
+ },
1860
+ [](py::object py_json) {
1861
+ return py_json.cast<std::string>();
1862
+ },
1863
+ [](Napi::Env env, std::string&& json) {
1864
+ return Napi::String::New(env, json);
1786
1865
  });
1787
-
1788
- return promise;
1789
1866
  }
1790
1867
 
1791
1868
  Napi::Value ExternalContext_constructor(const Napi::CallbackInfo& info) {
@@ -1819,11 +1896,8 @@ Napi::Value ExternalContext_constructor(const Napi::CallbackInfo& info) {
1819
1896
  .Utf8Value();
1820
1897
  }
1821
1898
 
1822
- std::promise<py::object*> promise;
1823
-
1824
- adaptor->ScheduleCallbackOnPythonEventLoop(
1825
- [&promise,
1826
- &name,
1899
+ py::object* py_external_context = RunCallbackOnPythonEventLoop(
1900
+ [&name,
1827
1901
  &url,
1828
1902
  &bearer_token,
1829
1903
  &idempotency_seed,
@@ -1833,8 +1907,7 @@ Napi::Value ExternalContext_constructor(const Napi::CallbackInfo& info) {
1833
1907
  "reboot.aio.external");
1834
1908
 
1835
1909
  auto convert_str =
1836
- [](
1837
- const std::optional<std::string>& optional) -> py::object {
1910
+ [](const std::optional<std::string>& optional) -> py::object {
1838
1911
  if (optional.has_value()) {
1839
1912
  return py::str(*optional);
1840
1913
  } else {
@@ -1842,30 +1915,20 @@ Napi::Value ExternalContext_constructor(const Napi::CallbackInfo& info) {
1842
1915
  }
1843
1916
  };
1844
1917
 
1845
- promise.set_value(
1846
- new py::object(py_external.attr("ExternalContext")(
1847
- "name"_a = py::str(name),
1848
- "url"_a = convert_str(url),
1849
- "bearer_token"_a = convert_str(bearer_token),
1850
- "idempotency_seed"_a = convert_str(idempotency_seed),
1851
- "idempotency_required"_a = py::bool_(idempotency_required),
1852
- "idempotency_required_reason"_a = convert_str(
1853
- idempotency_required_reason))));
1918
+ return new py::object(py_external.attr("ExternalContext")(
1919
+ "name"_a = py::str(name),
1920
+ "url"_a = convert_str(url),
1921
+ "bearer_token"_a = convert_str(bearer_token),
1922
+ "idempotency_seed"_a = convert_str(idempotency_seed),
1923
+ "idempotency_required"_a = py::bool_(idempotency_required),
1924
+ "idempotency_required_reason"_a = convert_str(
1925
+ idempotency_required_reason)));
1854
1926
  });
1855
- py::object* py_external_context = promise.get_future().get();
1856
1927
 
1857
- Napi::External<py::object> js_external_context =
1858
- Napi::External<py::object>::New(
1859
- info.Env(),
1860
- py_external_context,
1861
- [](Napi::Env, py::object* py_external_context) {
1862
- adaptor->ScheduleCallbackOnPythonEventLoop(
1863
- [py_external_context]() {
1864
- delete py_external_context;
1865
- });
1866
- });
1867
-
1868
- js_external_context.TypeTag(&reboot_aio_external_ExternalContext);
1928
+ Napi::External<py::object> js_external_context = make_napi_external(
1929
+ info.Env(),
1930
+ py_external_context,
1931
+ &reboot_aio_external_ExternalContext);
1869
1932
 
1870
1933
  return js_external_context;
1871
1934
  }
@@ -1876,62 +1939,190 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
1876
1939
 
1877
1940
  Napi::Array js_servicers = info[1].As<Napi::Array>();
1878
1941
 
1879
- auto env = info.Env();
1880
- auto servicer_details = make_servicer_details(env, js_servicers);
1942
+ Napi::Object js_web_framework = info[2].As<Napi::Object>();
1943
+
1944
+ auto js_web_framework_start = NapiSafeFunctionReference(
1945
+ js_web_framework.Get("start").As<Napi::Function>());
1946
+
1947
+ auto js_web_framework_stop = NapiSafeFunctionReference(
1948
+ js_web_framework.Get("stop").As<Napi::Function>());
1949
+
1950
+ auto servicer_details = make_servicer_details(info.Env(), js_servicers);
1881
1951
 
1882
1952
  auto js_initialize = NapiSafeFunctionReference(
1883
- info[2].As<Napi::Function>());
1953
+ info[3].As<Napi::Function>());
1884
1954
 
1885
1955
  std::optional<std::string> initialize_bearer_token;
1886
- if (!info[3].IsUndefined()) {
1887
- initialize_bearer_token = info[3].As<Napi::String>().Utf8Value();
1956
+ if (!info[4].IsUndefined()) {
1957
+ initialize_bearer_token = info[4].As<Napi::String>().Utf8Value();
1888
1958
  }
1889
1959
 
1890
1960
  std::optional<NapiSafeObjectReference> js_token_verifier;
1891
- if (!info[4].IsUndefined()) {
1892
- js_token_verifier = NapiSafeReference(info[4].As<Napi::Object>());
1961
+ if (!info[5].IsUndefined()) {
1962
+ js_token_verifier = NapiSafeReference(info[5].As<Napi::Object>());
1893
1963
  }
1894
1964
 
1895
- std::promise<py::object*> promise;
1896
-
1897
- adaptor->ScheduleCallbackOnPythonEventLoop(
1898
- [&promise,
1899
- servicer_details = std::move(servicer_details),
1965
+ py::object* py_application = RunCallbackOnPythonEventLoop(
1966
+ [servicer_details = std::move(servicer_details),
1967
+ js_web_framework_start = std::move(js_web_framework_start),
1968
+ js_web_framework_stop = std::move(js_web_framework_stop),
1900
1969
  initialize_bearer_token = std::move(initialize_bearer_token),
1901
1970
  js_initialize = std::move(js_initialize),
1902
1971
  js_token_verifier,
1903
1972
  js_from_native_external = std::move(js_from_native_external)]() {
1904
1973
  py::list py_servicers = make_py_servicers(servicer_details);
1905
1974
 
1975
+ py::object py_web_framework_start = py::cpp_function(
1976
+ [js_web_framework_start = std::move(js_web_framework_start),
1977
+ js_from_native_external /* Need a copy because it is shared. */](
1978
+ std::string consensus_id,
1979
+ py::object py_port,
1980
+ py::object py_channel_manager) {
1981
+ std::optional<int> port;
1982
+ if (!py_port.is_none()) {
1983
+ port = py_port.cast<int>();
1984
+ }
1985
+
1986
+ return PythonFutureFromNodePromise(
1987
+ [js_web_framework_start = std::move(js_web_framework_start),
1988
+ js_from_native_external = std::move(js_from_native_external),
1989
+ consensus_id = std::move(consensus_id),
1990
+ port,
1991
+ py_channel_manager = new py::object(py_channel_manager)](
1992
+ Napi::Env env) mutable {
1993
+ Napi::External<py::object> js_external_channel_manager =
1994
+ make_napi_external(env, py_channel_manager);
1995
+
1996
+ Napi::Function js_create_external_context =
1997
+ Napi::Function::New(
1998
+ env,
1999
+ [js_from_native_external = std::move(
2000
+ js_from_native_external),
2001
+ // Ensures `py_channel_manager` remains valid.
2002
+ js_external_channel_manager = NapiSafeReference(
2003
+ js_external_channel_manager)](
2004
+ const Napi::CallbackInfo& info) mutable {
2005
+ Napi::Object js_args = info[0].As<Napi::Object>();
2006
+
2007
+ std::string name =
2008
+ js_args
2009
+ .Get("name")
2010
+ .As<Napi::String>()
2011
+ .Utf8Value();
2012
+
2013
+ std::optional<std::string> bearer_token;
2014
+ if (!js_args.Get("bearerToken").IsUndefined()) {
2015
+ bearer_token =
2016
+ js_args.Get("bearerToken")
2017
+ .As<Napi::String>()
2018
+ .Utf8Value();
2019
+ }
2020
+
2021
+ py::object* py_channel_manager =
2022
+ js_external_channel_manager
2023
+ .Value(info.Env())
2024
+ .Data();
2025
+
2026
+ return NodePromiseFromPythonCallback(
2027
+ info.Env(),
2028
+ [js_from_native_external,
2029
+ name = std::move(name),
2030
+ bearer_token = std::move(bearer_token),
2031
+ // Ensures `py_channel_manager`
2032
+ // remains valid. Need a copy
2033
+ // because
2034
+ // `js_create_external_context` may
2035
+ // get called more than once.
2036
+ js_external_channel_manager,
2037
+ py_channel_manager]() mutable {
2038
+ py::object py_bearer_token = py::none();
2039
+ if (bearer_token.has_value()) {
2040
+ py_bearer_token = py::str(*bearer_token);
2041
+ }
2042
+
2043
+ return new py::object(
2044
+ py::module::import(
2045
+ "reboot.aio.external")
2046
+ .attr("ExternalContext")(
2047
+ "name"_a = py::str(name),
2048
+ "channel_manager"_a =
2049
+ py_channel_manager,
2050
+ "bearer_token"_a =
2051
+ py_bearer_token));
2052
+ },
2053
+ [js_from_native_external = std::move(
2054
+ js_from_native_external)](
2055
+ Napi::Env env,
2056
+ py::object* py_context) {
2057
+ // (check_line_length skip)
2058
+ Napi::External<py::object> js_external_context =
2059
+ make_napi_external(
2060
+ env,
2061
+ py_context,
2062
+ // (check_line_length skip)
2063
+ &reboot_aio_external_ExternalContext);
2064
+
2065
+ return js_from_native_external
2066
+ .Value(env)
2067
+ .Call(
2068
+ env.Global(),
2069
+ {js_external_context});
2070
+ });
2071
+ });
2072
+
2073
+ return js_web_framework_start
2074
+ .Value(env)
2075
+ .Call(
2076
+ env.Global(),
2077
+ {Napi::String::New(env, consensus_id),
2078
+ port.has_value()
2079
+ ? Napi::Number::New(env, *port)
2080
+ : env.Null(),
2081
+ js_create_external_context})
2082
+ .As<Napi::Object>();
2083
+ },
2084
+ [](Napi::Env env, Napi::Value value) {
2085
+ Napi::Number js_port = value.As<Napi::Number>();
2086
+ return js_port.Int32Value();
2087
+ });
2088
+ });
2089
+
2090
+ py::object py_web_framework_stop = py::cpp_function(
2091
+ [js_web_framework_stop = std::move(js_web_framework_stop)](
2092
+ std::string consensus_id) {
2093
+ return PythonFutureFromNodePromise(
2094
+ [js_web_framework_stop = std::move(js_web_framework_stop),
2095
+ consensus_id = std::move(consensus_id)](
2096
+ Napi::Env env) mutable {
2097
+ return js_web_framework_stop
2098
+ .Value(env)
2099
+ .Call(
2100
+ env.Global(),
2101
+ {Napi::String::New(env, consensus_id)})
2102
+ .As<Napi::Object>();
2103
+ });
2104
+ });
2105
+
1906
2106
  py::object py_initialize = py::cpp_function(
1907
2107
  [js_initialize = std::move(js_initialize),
1908
- js_from_native_external = std::move(js_from_native_external)](
2108
+ js_from_native_external /* Need a copy because it is shared. */](
1909
2109
  py::object py_context) mutable {
1910
- py::object py_future =
1911
- py::module::import("asyncio").attr("Future")();
1912
-
1913
- adaptor->ScheduleCallbackOnNodeEventLoop(
1914
- [js_initialize, // NOTE: need a _copy_ of
1915
- // `js_initialize` here since
1916
- // `py_initialize` may be called more
1917
- // than once!
1918
- js_from_native_external = std::move(js_from_native_external),
1919
- py_context = new py::object(py_context),
1920
- py_future = new py::object(py_future)](
2110
+ return PythonFutureFromNodePromise(
2111
+ [js_initialize,
2112
+ js_from_native_external, // NOTE: need a _copy_ of
2113
+ // both `js_initialize`
2114
+ // and
2115
+ // `js_from_native_external`
2116
+ // here since
2117
+ // `py_initialize` may be
2118
+ // called more than once!
2119
+ py_context = new py::object(py_context)](
1921
2120
  Napi::Env env) mutable {
1922
2121
  Napi::External<py::object> js_external_context =
1923
- Napi::External<py::object>::New(
2122
+ make_napi_external(
1924
2123
  env,
1925
2124
  py_context,
1926
- [](Napi::Env, py::object* py_context) {
1927
- adaptor->ScheduleCallbackOnPythonEventLoop(
1928
- [py_context]() {
1929
- delete py_context;
1930
- });
1931
- });
1932
-
1933
- js_external_context.TypeTag(
1934
- &reboot_aio_external_ExternalContext);
2125
+ &reboot_aio_external_ExternalContext);
1935
2126
 
1936
2127
  Napi::Object js_context =
1937
2128
  js_from_native_external
@@ -1941,57 +2132,11 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
1941
2132
  {js_external_context})
1942
2133
  .As<Napi::Object>();
1943
2134
 
1944
- Napi::Object js_promise =
1945
- js_initialize
1946
- .Value(env)
1947
- .Call(env.Global(), {js_context})
1948
- .As<Napi::Object>();
1949
-
1950
- js_promise
1951
- .Get("then")
1952
- .As<Napi::Function>()
1953
- .Call(
1954
- js_promise,
1955
- {Napi::Function::New(
1956
- env,
1957
- [py_future](const Napi::CallbackInfo& info) {
1958
- adaptor->ScheduleCallbackOnPythonEventLoop(
1959
- [py_future]() {
1960
- bool cancelled =
1961
- py_future
1962
- ->attr("cancelled")()
1963
- .cast<bool>();
1964
- if (!cancelled) {
1965
- py_future->attr("set_result")(
1966
- py::none());
1967
- }
1968
- delete py_future;
1969
- });
1970
- }),
1971
- Napi::Function::New(
1972
- env,
1973
- [py_future](const Napi::CallbackInfo& info) {
1974
- std::string message = message_from_js_error(
1975
- info[0].As<Napi::Object>());
1976
-
1977
- adaptor->ScheduleCallbackOnPythonEventLoop(
1978
- [py_future,
1979
- message = std::move(message)]() {
1980
- bool cancelled =
1981
- py_future->attr("cancelled")()
1982
- .cast<bool>();
1983
- if (!cancelled) {
1984
- py_future->attr("set_exception")(
1985
- py::module::import("builtins")
1986
- .attr("Exception")(
1987
- message));
1988
- }
1989
- delete py_future;
1990
- });
1991
- })});
2135
+ return js_initialize
2136
+ .Value(env)
2137
+ .Call(env.Global(), {js_context})
2138
+ .As<Napi::Object>();
1992
2139
  });
1993
-
1994
- return py_future;
1995
2140
  });
1996
2141
 
1997
2142
  py::object py_initialize_bearer_token = py::none();
@@ -2004,44 +2149,22 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
2004
2149
  py_token_verifier = make_py_token_verifier(*js_token_verifier);
2005
2150
  }
2006
2151
 
2007
- promise.set_value(
2008
- new py::object(
2009
- py::module::import("reboot.aio.applications")
2010
- .attr("Application")(
2011
- "servicers"_a = py_servicers,
2012
- "initialize"_a = py_initialize,
2013
- "initialize_bearer_token"_a =
2014
- py_initialize_bearer_token,
2015
- "token_verifier"_a = py_token_verifier)));
2016
- }
2017
- // TODO(benh): improve error handling mechanism to force all
2018
- // raised exceptions to be handled.
2019
- //
2020
- // [](py::error_already_set& e) {
2021
-
2022
- // },
2023
- // [](std::exception& e) {
2024
-
2025
- // },
2026
- // []() {
2027
-
2028
- // }
2029
- );
2030
-
2031
- py::object* py_application = promise.get_future().get();
2032
-
2033
- Napi::External<py::object> js_external_application =
2034
- Napi::External<py::object>::New(
2035
- info.Env(),
2036
- py_application,
2037
- [](Napi::Env, py::object* py_application) {
2038
- adaptor->ScheduleCallbackOnPythonEventLoop(
2039
- [py_application]() {
2040
- delete py_application;
2041
- });
2042
- });
2152
+ return new py::object(
2153
+ py::module::import("reboot.aio.applications")
2154
+ .attr("NodeApplication")(
2155
+ "servicers"_a = py_servicers,
2156
+ "web_framework_start"_a = py_web_framework_start,
2157
+ "web_framework_stop"_a = py_web_framework_stop,
2158
+ "initialize"_a = py_initialize,
2159
+ "initialize_bearer_token"_a =
2160
+ py_initialize_bearer_token,
2161
+ "token_verifier"_a = py_token_verifier));
2162
+ });
2043
2163
 
2044
- js_external_application.TypeTag(&reboot_aio_applications_Application);
2164
+ Napi::External<py::object> js_external_application = make_napi_external(
2165
+ info.Env(),
2166
+ py_application,
2167
+ &reboot_aio_applications_Application);
2045
2168
 
2046
2169
  return js_external_application;
2047
2170
  }
@@ -2064,58 +2187,45 @@ Napi::Value Application_run(const Napi::CallbackInfo& info) {
2064
2187
  py::object* py_application =
2065
2188
  js_external_application.Value(info.Env()).Data();
2066
2189
 
2067
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
2068
-
2069
- auto promise = deferred->Promise();
2070
-
2071
- adaptor->ScheduleCallbackOnPythonEventLoop(
2190
+ Napi::Promise js_promise = NodePromiseFromPythonTask(
2191
+ info.Env(),
2192
+ "Application.run() in nodejs",
2193
+ {"reboot.nodejs.python", "create_task"},
2072
2194
  [js_external_application, // Ensures `py_application` remains valid.
2073
- py_application,
2074
- deferred = std::move(deferred)]() {
2075
- py::object py_task =
2076
- py::module::import("reboot.nodejs.python")
2077
- .attr("create_task")(
2078
- py_application->attr("run")(),
2079
- "name"_a = "Application.run() in nodejs");
2080
-
2081
- py_task.attr("add_done_callback")(py::cpp_function(
2082
- [deferred = std::move(deferred),
2083
- // NOTE: need to keep a reference to `py_task` so that it
2084
- // doesn't get destroyed before completing!
2085
- py_task = new py::object(py_task)](py::object py_future) {
2086
- py::object py_exception = py_future.attr("exception")();
2087
-
2088
- std::optional<std::string> exception;
2089
-
2090
- if (!py_exception.is_none()) {
2091
- py::str py_exception_string = py_exception_str(py_exception);
2092
-
2093
- exception.emplace(py_exception_string);
2094
- }
2095
-
2096
- adaptor->ScheduleCallbackOnNodeEventLoop(
2097
- [deferred = std::move(deferred),
2098
- exception = std::move(exception)](Napi::Env env) {
2099
- if (!exception.has_value()) {
2100
- deferred->Resolve(env.Null());
2101
- } else {
2102
- env.Global()
2103
- .Get("process")
2104
- .As<Napi::Object>()
2105
- .Set("exitCode", 1);
2106
- deferred->Reject(
2107
- Napi::Error::New(env, *exception).Value());
2108
- }
2109
- // When `Application.run` exits, we unref our
2110
- // thread_safe_function to cause the runtime to exit.
2111
- adaptor->thread_safe_function.Unref(env);
2112
- });
2113
-
2114
- delete py_task;
2115
- }));
2195
+ py_application]() {
2196
+ return py_application->attr("run")();
2116
2197
  });
2117
2198
 
2118
- return promise;
2199
+ // More to do if `Application.run` returns ...
2200
+ js_promise
2201
+ .Get("then")
2202
+ .As<Napi::Function>()
2203
+ .Call(
2204
+ js_promise,
2205
+ {Napi::Function::New(
2206
+ info.Env(),
2207
+ [](const Napi::CallbackInfo& info) {
2208
+ // When `Application.run` exits, we unref our
2209
+ // thread_safe_function to cause the runtime to exit.
2210
+ adaptor->thread_safe_function.Unref(info.Env());
2211
+ }),
2212
+ Napi::Function::New(
2213
+ info.Env(),
2214
+ [](const Napi::CallbackInfo& info) {
2215
+ // There was an error, let's also set the
2216
+ // `process.exitCode` to reflect this.
2217
+ info.Env()
2218
+ .Global()
2219
+ .Get("process")
2220
+ .As<Napi::Object>()
2221
+ .Set("exitCode", 1);
2222
+
2223
+ // When `Application.run` exits, we unref our
2224
+ // thread_safe_function to cause the runtime to exit.
2225
+ adaptor->thread_safe_function.Unref(info.Env());
2226
+ })});
2227
+
2228
+ return js_promise;
2119
2229
  }
2120
2230
 
2121
2231
 
@@ -2127,22 +2237,18 @@ Napi::Value Context_auth(const Napi::CallbackInfo& info) {
2127
2237
 
2128
2238
  py::object* py_context = js_external_context.Data();
2129
2239
 
2130
- std::promise<std::optional<std::string>> promise;
2131
-
2132
- adaptor->ScheduleCallbackOnPythonEventLoop(
2133
- [py_context, &promise]() {
2240
+ std::optional<std::string> auth_bytes = RunCallbackOnPythonEventLoop(
2241
+ [py_context]() -> std::optional<std::string> {
2134
2242
  py::object py_auth = py_context->attr("auth");
2135
2243
 
2136
2244
  if (py_auth.is_none()) {
2137
- promise.set_value(std::nullopt);
2245
+ return std::nullopt;
2138
2246
  } else {
2139
2247
  std::string auth_bytes = py::bytes(py_auth.attr("to_proto_bytes")());
2140
- promise.set_value(auth_bytes);
2248
+ return auth_bytes;
2141
2249
  }
2142
2250
  });
2143
2251
 
2144
- std::optional<std::string> auth_bytes = promise.get_future().get();
2145
-
2146
2252
  if (auth_bytes.has_value()) {
2147
2253
  Napi::Env env = info.Env();
2148
2254
  return str_to_uint8array(env, *auth_bytes);
@@ -2160,15 +2266,13 @@ Napi::Value Context_stateId(const Napi::CallbackInfo& info) {
2160
2266
 
2161
2267
  py::object* py_context = js_external_context.Data();
2162
2268
 
2163
- std::promise<std::string> promise;
2164
-
2165
- adaptor->ScheduleCallbackOnPythonEventLoop(
2166
- [py_context, &promise]() {
2269
+ std::string state_id = RunCallbackOnPythonEventLoop(
2270
+ [py_context]() {
2167
2271
  py::str state_id = py_context->attr("state_id");
2168
- promise.set_value(std::string(state_id));
2272
+ return std::string(state_id);
2169
2273
  });
2170
2274
 
2171
- return Napi::String::New(info.Env(), promise.get_future().get());
2275
+ return Napi::String::New(info.Env(), state_id);
2172
2276
  }
2173
2277
 
2174
2278
 
@@ -2180,21 +2284,17 @@ Napi::Value Context_iteration(const Napi::CallbackInfo& info) {
2180
2284
 
2181
2285
  py::object* py_context = js_external_context.Data();
2182
2286
 
2183
- std::promise<std::optional<int>> promise;
2184
-
2185
- adaptor->ScheduleCallbackOnPythonEventLoop(
2186
- [py_context, &promise]() {
2287
+ std::optional<int> iteration = RunCallbackOnPythonEventLoop(
2288
+ [py_context]() -> std::optional<int> {
2187
2289
  py::object iteration = py_context->attr("iteration");
2188
2290
 
2189
2291
  if (iteration.is_none()) {
2190
- promise.set_value(std::optional<int>());
2292
+ return std::nullopt;
2191
2293
  } else {
2192
- promise.set_value(iteration.cast<int>());
2294
+ return iteration.cast<int>();
2193
2295
  }
2194
2296
  });
2195
2297
 
2196
- std::optional<int> iteration = promise.get_future().get();
2197
-
2198
2298
  if (iteration.has_value()) {
2199
2299
  return Napi::Number::New(info.Env(), *iteration);
2200
2300
  } else {
@@ -2211,15 +2311,30 @@ Napi::Value Context_cookie(const Napi::CallbackInfo& info) {
2211
2311
 
2212
2312
  py::object* py_context = js_external_context.Data();
2213
2313
 
2214
- std::promise<std::string> promise;
2215
-
2216
- adaptor->ScheduleCallbackOnPythonEventLoop(
2217
- [py_context, &promise]() {
2314
+ std::string cookie = RunCallbackOnPythonEventLoop(
2315
+ [py_context]() {
2218
2316
  py::str cookie = py_context->attr("cookie");
2219
- promise.set_value(std::string(cookie));
2317
+ return std::string(cookie);
2318
+ });
2319
+
2320
+ return Napi::String::New(info.Env(), cookie);
2321
+ }
2322
+
2323
+
2324
+ Napi::Value Context_appInternal(const Napi::CallbackInfo& info) {
2325
+ Napi::External<py::object> js_external_context =
2326
+ info[0].As<Napi::External<py::object>>();
2327
+
2328
+ // CHECK(...CheckTypeTag(...));
2329
+
2330
+ py::object* py_context = js_external_context.Data();
2331
+
2332
+ bool app_internal = RunCallbackOnPythonEventLoop(
2333
+ [py_context]() {
2334
+ return py_context->attr("app_internal").cast<bool>();
2220
2335
  });
2221
2336
 
2222
- return Napi::String::New(info.Env(), promise.get_future().get());
2337
+ return Napi::Boolean::New(info.Env(), app_internal);
2223
2338
  }
2224
2339
 
2225
2340
 
@@ -2254,19 +2369,15 @@ Napi::Value Context_generateIdempotentStateId(const Napi::CallbackInfo& info) {
2254
2369
  alias = js_alias.As<Napi::String>().Utf8Value();
2255
2370
  }
2256
2371
 
2257
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
2258
-
2259
- auto promise = deferred->Promise();
2260
-
2261
- adaptor->ScheduleCallbackOnPythonEventLoop(
2372
+ return NodePromiseFromPythonCallback(
2373
+ info.Env(),
2262
2374
  [js_external_context, // Ensures `py_context` remains valid.
2263
2375
  py_context,
2264
2376
  state_type = std::move(state_type),
2265
2377
  service_name = std::move(service_name),
2266
2378
  method = std::move(method),
2267
2379
  key = std::move(key),
2268
- alias = std::move(alias),
2269
- deferred = std::move(deferred)]() {
2380
+ alias = std::move(alias)]() {
2270
2381
  py::object py_key = py::none();
2271
2382
  if (key.has_value()) {
2272
2383
  py_key = py::cast(*key);
@@ -2282,34 +2393,20 @@ Napi::Value Context_generateIdempotentStateId(const Napi::CallbackInfo& info) {
2282
2393
  "key"_a = py_key,
2283
2394
  "alias"_a = py_alias));
2284
2395
 
2285
- py::object generate_idempotent_state_id =
2396
+ py::object py_generate_idempotent_state_id =
2286
2397
  py_context->attr("generate_idempotent_state_id");
2287
- try {
2288
- py::object result = generate_idempotent_state_id(
2289
- state_type,
2290
- service_name,
2291
- method,
2292
- py_idempotency);
2293
2398
 
2294
- auto state_id = result.cast<std::string>();
2399
+ py::object py_state_id = py_generate_idempotent_state_id(
2400
+ state_type,
2401
+ service_name,
2402
+ method,
2403
+ py_idempotency);
2295
2404
 
2296
- adaptor->ScheduleCallbackOnNodeEventLoop(
2297
- [state_id = std::move(state_id),
2298
- deferred = std::move(deferred)](Napi::Env env) {
2299
- deferred->Resolve(Napi::String::New(env, state_id));
2300
- });
2301
- } catch (pybind11::error_already_set& e) {
2302
- std::string exception_message = py_exception_str(e.value());
2303
- adaptor->ScheduleCallbackOnNodeEventLoop(
2304
- [exception_message = std::move(exception_message),
2305
- deferred = std::move(deferred)](Napi::Env env) {
2306
- deferred->Reject(
2307
- Napi::Error::New(env, exception_message).Value());
2308
- });
2309
- }
2405
+ return py_state_id.cast<std::string>();
2406
+ },
2407
+ [](Napi::Env env, std::string&& state_id) {
2408
+ return Napi::String::New(env, state_id);
2310
2409
  });
2311
-
2312
- return promise;
2313
2410
  }
2314
2411
 
2315
2412
 
@@ -2324,16 +2421,11 @@ Napi::Value WriterContext_set_sync(const Napi::CallbackInfo& info) {
2324
2421
 
2325
2422
  bool sync = info[1].As<Napi::Boolean>();
2326
2423
 
2327
- std::promise<void> promise;
2328
-
2329
- adaptor->ScheduleCallbackOnPythonEventLoop(
2330
- [&py_context, sync, &promise]() {
2424
+ RunCallbackOnPythonEventLoop(
2425
+ [&py_context, sync]() {
2331
2426
  py_context->attr("sync") = sync;
2332
- promise.set_value();
2333
2427
  });
2334
2428
 
2335
- promise.get_future().get();
2336
-
2337
2429
  return info.Env().Undefined();
2338
2430
  }
2339
2431
 
@@ -2349,121 +2441,34 @@ Napi::Value retry_reactively_until(const Napi::CallbackInfo& info) {
2349
2441
  auto js_condition = NapiSafeFunctionReference(
2350
2442
  info[1].As<Napi::Function>());
2351
2443
 
2352
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
2353
-
2354
- auto promise = deferred->Promise();
2355
-
2356
- adaptor->ScheduleCallbackOnPythonEventLoop(
2444
+ return NodePromiseFromPythonTaskWithContext(
2445
+ info.Env(),
2446
+ "retry_reactively_until(...) in nodejs",
2447
+ js_external_context,
2357
2448
  [js_external_context, // Ensures `py_context` remains valid.
2358
2449
  py_context,
2359
- js_condition = std::move(js_condition),
2360
- deferred = std::move(deferred)]() {
2450
+ js_condition = std::move(js_condition)]() {
2361
2451
  py::object py_condition = py::cpp_function(
2362
2452
  [js_condition = std::move(js_condition)]() mutable {
2363
- py::object py_future =
2364
- py::module::import("asyncio").attr("Future")();
2365
-
2366
- adaptor->ScheduleCallbackOnNodeEventLoop(
2367
- [js_condition, // NOTE: need a _copy_ of
2368
- // `js_condition` here since
2369
- // `py_condition` may be called more
2370
- // than once!
2371
- py_future = new py::object(py_future)](
2372
- Napi::Env env) mutable {
2373
- Napi::Object js_promise =
2374
- js_condition
2375
- .Value(env)
2376
- .Call(env.Global(), {})
2377
- .As<Napi::Object>();
2378
-
2379
- js_promise
2380
- .Get("then")
2381
- .As<Napi::Function>()
2382
- .Call(
2383
- js_promise,
2384
- {Napi::Function::New(
2385
- env,
2386
- [py_future](const Napi::CallbackInfo& info) {
2387
- bool result = info[0].As<Napi::Boolean>();
2388
- adaptor->ScheduleCallbackOnPythonEventLoop(
2389
- [py_future, result]() {
2390
- bool cancelled =
2391
- py_future->attr("cancelled")()
2392
- .cast<bool>();
2393
- if (!cancelled) {
2394
- py_future->attr("set_result")(
2395
- result);
2396
- }
2397
- delete py_future;
2398
- });
2399
- }),
2400
- Napi::Function::New(
2401
- env,
2402
- [py_future](const Napi::CallbackInfo& info) {
2403
- std::string message = message_from_js_error(
2404
- info[0].As<Napi::Object>());
2405
-
2406
- adaptor->ScheduleCallbackOnPythonEventLoop(
2407
- [py_future,
2408
- message = std::move(message)]() {
2409
- bool cancelled =
2410
- py_future->attr("cancelled")()
2411
- .cast<bool>();
2412
- if (!cancelled) {
2413
- py_future->attr("set_exception")(
2414
- py::module::import("builtins")
2415
- .attr("Exception")(
2416
- message));
2417
- }
2418
- delete py_future;
2419
- });
2420
- })});
2453
+ return PythonFutureFromNodePromise(
2454
+ // NOTE: need a _copy_ of
2455
+ // `js_condition` here since
2456
+ // `py_condition` may be called more
2457
+ // than once!
2458
+ [js_condition](Napi::Env env) mutable {
2459
+ return js_condition
2460
+ .Value(env)
2461
+ .Call(env.Global(), {})
2462
+ .As<Napi::Object>();
2463
+ },
2464
+ [](Napi::Env, Napi::Value value) {
2465
+ return value.As<Napi::Boolean>().Value();
2421
2466
  });
2422
-
2423
- return py_future;
2424
2467
  });
2425
2468
 
2426
- py::object py_task =
2427
- py::module::import("reboot.nodejs.python")
2428
- .attr("create_task_with_context")(
2429
- (py::module::import("reboot.aio.contexts")
2430
- .attr("retry_reactively_until"))(
2431
- py_context,
2432
- py_condition),
2433
- py_context,
2434
- "name"_a = "retry_reactively_until(...) in nodejs");
2435
-
2436
- py_task.attr("add_done_callback")(py::cpp_function(
2437
- [deferred = std::move(deferred),
2438
- // NOTE: need to keep a reference to `py_task` so that it
2439
- // doesn't get destroyed before completing!
2440
- py_task = new py::object(py_task)](py::object py_future) {
2441
- py::object py_exception = py_future.attr("exception")();
2442
-
2443
- std::optional<std::string> exception;
2444
-
2445
- if (!py_exception.is_none()) {
2446
- py::str py_exception_string = py_exception_str(py_exception);
2447
-
2448
- exception.emplace(py_exception_string);
2449
- }
2450
-
2451
- adaptor->ScheduleCallbackOnNodeEventLoop(
2452
- [deferred = std::move(deferred),
2453
- exception = std::move(exception)](Napi::Env env) {
2454
- if (!exception.has_value()) {
2455
- deferred->Resolve(env.Null());
2456
- } else {
2457
- deferred->Reject(
2458
- Napi::Error::New(env, *exception).Value());
2459
- }
2460
- });
2461
-
2462
- delete py_task;
2463
- }));
2469
+ return py::module::import("reboot.aio.contexts")
2470
+ .attr("retry_reactively_until")(py_context, py_condition);
2464
2471
  });
2465
-
2466
- return promise;
2467
2472
  }
2468
2473
 
2469
2474
 
@@ -2482,132 +2487,45 @@ Napi::Value atLeastOrMostOnce(const Napi::CallbackInfo& info) {
2482
2487
 
2483
2488
  bool at_most_once = info[3].As<Napi::Boolean>();
2484
2489
 
2485
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
2486
-
2487
- auto promise = deferred->Promise();
2488
-
2489
- adaptor->ScheduleCallbackOnPythonEventLoop(
2490
+ return NodePromiseFromPythonTaskWithContext(
2491
+ info.Env(),
2492
+ "memoize(...) in nodejs",
2493
+ js_external_context,
2490
2494
  [js_external_context, // Ensures `py_context` remains valid.
2491
2495
  py_context,
2492
2496
  js_callable = std::move(js_callable),
2493
2497
  idempotency_alias = std::move(idempotency_alias),
2494
- at_most_once,
2495
- deferred = std::move(deferred)]() {
2498
+ at_most_once]() {
2496
2499
  py::object py_callable = py::cpp_function(
2497
2500
  [js_callable = std::move(js_callable)]() mutable {
2498
- py::object py_future =
2499
- py::module::import("asyncio").attr("Future")();
2500
-
2501
- adaptor->ScheduleCallbackOnNodeEventLoop(
2502
- [js_callable, // NOTE: need a _copy_ of
2503
- // `js_callable` here since
2504
- // `py_callable` may be called more
2505
- // than once!
2506
- py_future = new py::object(py_future)](
2507
- Napi::Env env) mutable {
2508
- Napi::Object js_promise =
2509
- js_callable
2510
- .Value(env)
2511
- .Call(env.Global(), {})
2512
- .As<Napi::Object>();
2513
-
2514
- js_promise
2515
- .Get("then")
2516
- .As<Napi::Function>()
2517
- .Call(
2518
- js_promise,
2519
- {Napi::Function::New(
2520
- env,
2521
- [py_future](const Napi::CallbackInfo& info) {
2522
- std::string result =
2523
- info[0].As<Napi::String>().Utf8Value();
2524
- adaptor->ScheduleCallbackOnPythonEventLoop(
2525
- [py_future, result]() {
2526
- bool cancelled =
2527
- py_future->attr("cancelled")()
2528
- .cast<bool>();
2529
- if (!cancelled) {
2530
- py_future->attr("set_result")(
2531
- result);
2532
- }
2533
- delete py_future;
2534
- });
2535
- }),
2536
- Napi::Function::New(
2537
- env,
2538
- [py_future](const Napi::CallbackInfo& info) {
2539
- std::string message = message_from_js_error(
2540
- info[0].As<Napi::Object>());
2541
-
2542
- adaptor->ScheduleCallbackOnPythonEventLoop(
2543
- [py_future,
2544
- message = std::move(message)]() {
2545
- bool cancelled =
2546
- py_future->attr("cancelled")()
2547
- .cast<bool>();
2548
- if (!cancelled) {
2549
- py_future->attr("set_exception")(
2550
- py::module::import("builtins")
2551
- .attr("Exception")(
2552
- message));
2553
- }
2554
- delete py_future;
2555
- });
2556
- })});
2501
+ return PythonFutureFromNodePromise(
2502
+ // NOTE: need a _copy_ of `js_callable` here since
2503
+ // `py_callable` may be called more than once!
2504
+ [js_callable](Napi::Env env) mutable {
2505
+ return js_callable
2506
+ .Value(env)
2507
+ .Call(env.Global(), {})
2508
+ .As<Napi::Object>();
2509
+ },
2510
+ [](Napi::Env, Napi::Value value) {
2511
+ return std::string(value.As<Napi::String>().Utf8Value());
2557
2512
  });
2558
-
2559
- return py_future;
2560
2513
  });
2561
2514
 
2562
- py::object py_task =
2563
- py::module::import("reboot.nodejs.python")
2564
- .attr("create_task_with_context")(
2565
- (py::module::import("reboot.aio.memoize").attr("memoize"))(
2566
- py::str(idempotency_alias),
2567
- py_context,
2568
- py_callable,
2569
- "type_t"_a = py::eval("str"),
2570
- "at_most_once"_a = at_most_once),
2571
- py_context,
2572
- "name"_a = "memoize(...) in nodejs");
2573
-
2574
- py_task.attr("add_done_callback")(py::cpp_function(
2575
- [deferred = std::move(deferred),
2576
- // NOTE: need to keep a reference to `py_task` so that it
2577
- // doesn't get destroyed before completing!
2578
- py_task = new py::object(py_task)](py::object py_future) {
2579
- py::object py_exception = py_future.attr("exception")();
2580
-
2581
- std::optional<std::string> json;
2582
- std::optional<std::string> exception;
2583
-
2584
- if (py_exception.is_none()) {
2585
- py::str py_json = py_future.attr("result")();
2586
- json.emplace(py_json);
2587
- } else {
2588
- py::str py_exception_string = py_exception_str(py_exception);
2589
-
2590
- exception.emplace(py_exception_string);
2591
- }
2592
-
2593
- adaptor->ScheduleCallbackOnNodeEventLoop(
2594
- [deferred = std::move(deferred),
2595
- json = std::move(json),
2596
- exception = std::move(exception)](Napi::Env env) {
2597
- if (json.has_value()) {
2598
- deferred->Resolve(
2599
- Napi::String::New(env, *json));
2600
- } else {
2601
- deferred->Reject(
2602
- Napi::Error::New(env, *exception).Value());
2603
- }
2604
- });
2605
-
2606
- delete py_task;
2607
- }));
2515
+ return py::module::import("reboot.aio.memoize")
2516
+ .attr("memoize")(
2517
+ py::str(idempotency_alias),
2518
+ py_context,
2519
+ py_callable,
2520
+ "type_t"_a = py::eval("str"),
2521
+ "at_most_once"_a = at_most_once);
2522
+ },
2523
+ [](py::object py_json) {
2524
+ return py_json.cast<std::string>();
2525
+ },
2526
+ [](Napi::Env env, std::string&& json) {
2527
+ return Napi::String::New(env, json);
2608
2528
  });
2609
-
2610
- return promise;
2611
2529
  }
2612
2530
 
2613
2531
 
@@ -2626,60 +2544,22 @@ Napi::Value Servicer_read(const Napi::CallbackInfo& info) {
2626
2544
 
2627
2545
  py::object* py_context = js_external_context.Value(info.Env()).Data();
2628
2546
 
2629
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
2630
-
2631
- auto promise = deferred->Promise();
2632
-
2633
- adaptor->ScheduleCallbackOnPythonEventLoop(
2547
+ return NodePromiseFromPythonTaskWithContext(
2548
+ info.Env(),
2549
+ "servicer._read(...) in nodejs",
2550
+ js_external_context,
2634
2551
  [js_external_servicer, // Ensures `py_servicer` remains valid.
2635
2552
  py_servicer,
2636
2553
  js_external_context, // Ensures `py_context` remains valid.
2637
- py_context,
2638
- deferred = std::move(deferred)]() {
2639
- py::object py_task =
2640
- py::module::import("reboot.nodejs.python")
2641
- .attr("create_task_with_context")(
2642
- py_servicer->attr("_read")(py_context),
2643
- py_context,
2644
- "name"_a = "servicer._read(...) in nodejs");
2645
-
2646
- py_task.attr("add_done_callback")(py::cpp_function(
2647
- [deferred = std::move(deferred),
2648
- // NOTE: need to keep a reference to `py_task` so that it
2649
- // doesn't get destroyed before completing!
2650
- py_task = new py::object(py_task)](py::object py_future) {
2651
- py::object py_exception = py_future.attr("exception")();
2652
-
2653
- std::optional<std::string> json;
2654
- std::optional<std::string> exception;
2655
-
2656
- if (py_exception.is_none()) {
2657
- py::str py_json = py_future.attr("result")();
2658
- json.emplace(py_json);
2659
- } else {
2660
- py::str py_exception_string = py_exception_str(py_exception);
2661
-
2662
- exception.emplace(py_exception_string);
2663
- }
2664
-
2665
- adaptor->ScheduleCallbackOnNodeEventLoop(
2666
- [deferred = std::move(deferred),
2667
- json = std::move(json),
2668
- exception = std::move(exception)](Napi::Env env) {
2669
- if (json.has_value()) {
2670
- deferred->Resolve(
2671
- Napi::String::New(env, *json));
2672
- } else {
2673
- deferred->Reject(
2674
- Napi::Error::New(env, *exception).Value());
2675
- }
2676
- });
2677
-
2678
- delete py_task;
2679
- }));
2554
+ py_context]() {
2555
+ return py_servicer->attr("_read")(py_context);
2556
+ },
2557
+ [](py::object py_json) {
2558
+ return py_json.cast<std::string>();
2559
+ },
2560
+ [](Napi::Env env, std::string&& json) {
2561
+ return Napi::String::New(env, json);
2680
2562
  });
2681
-
2682
- return promise;
2683
2563
  }
2684
2564
 
2685
2565
 
@@ -2703,135 +2583,48 @@ Napi::Value Servicer_write(const Napi::CallbackInfo& info) {
2703
2583
 
2704
2584
  std::string json_options = info[3].As<Napi::String>().Utf8Value();
2705
2585
 
2706
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
2707
-
2708
- auto promise = deferred->Promise();
2709
-
2710
- adaptor->ScheduleCallbackOnPythonEventLoop(
2586
+ return NodePromiseFromPythonTaskWithContext(
2587
+ info.Env(),
2588
+ "servicer._write(...) in nodejs",
2589
+ js_external_context,
2711
2590
  [js_external_servicer, // Ensures `py_servicer` remains valid.
2712
2591
  py_servicer,
2713
2592
  js_external_context, // Ensures `py_context` remains valid.
2714
2593
  py_context,
2715
2594
  js_writer = std::move(js_writer),
2716
- json_options = std::move(json_options),
2717
- deferred = std::move(deferred)]() {
2595
+ json_options = std::move(json_options)]() {
2718
2596
  py::object py_writer = py::cpp_function(
2719
2597
  [js_writer = std::move(js_writer)](
2720
2598
  std::string state_json) mutable {
2721
- py::object py_future =
2722
- py::module::import("asyncio").attr("Future")();
2723
-
2724
- adaptor->ScheduleCallbackOnNodeEventLoop(
2599
+ return PythonFutureFromNodePromise(
2725
2600
  [js_writer, // NOTE: need a _copy_ of
2726
2601
  // `js_writer` here since
2727
2602
  // `py_writer` may be called more
2728
2603
  // than once!
2729
- state_json,
2730
- py_future = new py::object(py_future)](
2731
- Napi::Env env) mutable {
2732
- Napi::Object js_promise =
2733
- js_writer
2734
- .Value(env)
2735
- .Call(
2736
- env.Global(),
2737
- {Napi::String::New(env, state_json)})
2738
- .As<Napi::Object>();
2739
-
2740
- js_promise
2741
- .Get("then")
2742
- .As<Napi::Function>()
2604
+ state_json](Napi::Env env) mutable {
2605
+ return js_writer
2606
+ .Value(env)
2743
2607
  .Call(
2744
- js_promise,
2745
- {Napi::Function::New(
2746
- env,
2747
- [py_future](const Napi::CallbackInfo& info) {
2748
- std::string result =
2749
- info[0].As<Napi::String>().Utf8Value();
2750
- adaptor->ScheduleCallbackOnPythonEventLoop(
2751
- [py_future, result]() {
2752
- bool cancelled =
2753
- py_future->attr("cancelled")()
2754
- .cast<bool>();
2755
- if (!cancelled) {
2756
- py_future->attr("set_result")(
2757
- result);
2758
- }
2759
- delete py_future;
2760
- });
2761
- }),
2762
- Napi::Function::New(
2763
- env,
2764
- [py_future](const Napi::CallbackInfo& info) {
2765
- std::string message = message_from_js_error(
2766
- info[0].As<Napi::Object>());
2767
-
2768
- adaptor->ScheduleCallbackOnPythonEventLoop(
2769
- [py_future,
2770
- message = std::move(message)]() {
2771
- bool cancelled =
2772
- py_future->attr("cancelled")()
2773
- .cast<bool>();
2774
- if (!cancelled) {
2775
- py_future->attr("set_exception")(
2776
- py::module::import("builtins")
2777
- .attr("Exception")(
2778
- message));
2779
- }
2780
- delete py_future;
2781
- });
2782
- })});
2608
+ env.Global(),
2609
+ {Napi::String::New(env, state_json)})
2610
+ .As<Napi::Object>();
2611
+ },
2612
+ [](Napi::Env env, Napi::Value value) {
2613
+ return std::string(value.As<Napi::String>().Utf8Value());
2783
2614
  });
2784
-
2785
- return py_future;
2786
2615
  });
2787
2616
 
2788
- py::object py_task =
2789
- py::module::import("reboot.nodejs.python")
2790
- .attr("create_task_with_context")(
2791
- py_servicer->attr("_write")(
2792
- py_context,
2793
- py_writer,
2794
- json_options),
2795
- py_context,
2796
- "name"_a = "servicer._write(...) in nodejs");
2797
-
2798
- py_task.attr("add_done_callback")(py::cpp_function(
2799
- [deferred = std::move(deferred),
2800
- // NOTE: need to keep a reference to `py_task` so that it
2801
- // doesn't get destroyed before completing!
2802
- py_task = new py::object(py_task)](py::object py_future) {
2803
- py::object py_exception = py_future.attr("exception")();
2804
-
2805
- std::optional<std::string> result;
2806
- std::optional<std::string> exception;
2807
-
2808
- if (py_exception.is_none()) {
2809
- py::str py_result = py_future.attr("result")();
2810
- result.emplace(py_result);
2811
- } else {
2812
- py::str py_exception_string = py_exception_str(py_exception);
2813
-
2814
- exception.emplace(py_exception_string);
2815
- }
2816
-
2817
- adaptor->ScheduleCallbackOnNodeEventLoop(
2818
- [deferred = std::move(deferred),
2819
- result = std::move(result),
2820
- exception = std::move(exception)](Napi::Env env) {
2821
- if (result.has_value()) {
2822
- deferred->Resolve(
2823
- Napi::String::New(env, *result));
2824
- } else {
2825
- deferred->Reject(
2826
- Napi::Error::New(env, *exception).Value());
2827
- }
2828
- });
2829
-
2830
- delete py_task;
2831
- }));
2617
+ return py_servicer->attr("_write")(
2618
+ py_context,
2619
+ py_writer,
2620
+ json_options);
2621
+ },
2622
+ [](py::object py_result) {
2623
+ return py_result.cast<std::string>();
2624
+ },
2625
+ [](Napi::Env env, std::string&& result) {
2626
+ return Napi::String::New(env, result);
2832
2627
  });
2833
-
2834
- return promise;
2835
2628
  }
2836
2629
 
2837
2630
 
@@ -2901,6 +2694,10 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
2901
2694
  Napi::String::New(env, "Context_cookie"),
2902
2695
  Napi::Function::New<Context_cookie>(env));
2903
2696
 
2697
+ exports.Set(
2698
+ Napi::String::New(env, "Context_appInternal"),
2699
+ Napi::Function::New<Context_appInternal>(env));
2700
+
2904
2701
  exports.Set(
2905
2702
  Napi::String::New(env, "Context_generateIdempotentStateId"),
2906
2703
  Napi::Function::New<Context_generateIdempotentStateId>(env));