galaaz 0.5.0 → 2.0.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.
Files changed (359) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/LICENSE +0 -0
  4. data/README.md +1360 -636
  5. data/Rakefile +61 -41
  6. data/bin/galaaz-bootstrap +137 -0
  7. data/bin/galaaz-jruby +14 -0
  8. data/bin/galaaz_jruby_env.inc.sh +6 -0
  9. data/bin/gbookdown +64 -0
  10. data/bin/gknit +84 -13
  11. data/bin/gknit-draft.rb +0 -0
  12. data/bin/gstudio +5 -3
  13. data/bin/gstudio_irb.rb +0 -0
  14. data/bin/gstudio_pry.rb +0 -0
  15. data/bin/install-tinytex +6 -0
  16. data/bin/run_all_rspec +43 -0
  17. data/bin/run_example +14 -0
  18. data/bin/run_old_rspec +19 -0
  19. data/bin/run_rspec +23 -0
  20. data/bin/run_rspec_subset +38 -0
  21. data/bin/run_slow_rspec +19 -0
  22. data/blogs/R-on-Rails-Planning-Document.md +940 -0
  23. data/blogs/README.md +100 -0
  24. data/blogs/galaaz_ggplot/galaaz_ggplot.Rmd +38 -66
  25. data/blogs/galaaz_ggplot/galaaz_ggplot.log +754 -0
  26. data/blogs/galaaz_ggplot/galaaz_ggplot.md +115 -155
  27. data/blogs/galaaz_ggplot/galaaz_ggplot.tex +607 -0
  28. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-html/midwest_rb.png +0 -0
  29. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-html/scatter_plot_rb.png +0 -0
  30. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-markdown_github/midwest_rb.png +0 -0
  31. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-markdown_github/scatter_plot_rb.png +0 -0
  32. data/blogs/galaaz_ggplot/midwest.Rmd +3 -3
  33. data/blogs/galaaz_ggplot/midwest_external_png +0 -0
  34. data/blogs/gknit/gknit.Rmd +47 -52
  35. data/blogs/gknit/gknit.md +1430 -0
  36. data/blogs/gknit/gknit_files/figure-html/bubble-1.png +0 -0
  37. data/blogs/gknit/gknit_files/figure-html/diverging_bar.png +0 -0
  38. data/blogs/gknit/lst.rds +0 -0
  39. data/blogs/gknit/model.rb +1 -1
  40. data/blogs/gknit/stats.bib +0 -0
  41. data/blogs/manual/include_model_local_repro.Rmd +14 -0
  42. data/blogs/manual/include_model_local_repro.md +75 -0
  43. data/blogs/manual/lst.rds +0 -0
  44. data/blogs/manual/manual.Rmd +852 -239
  45. data/blogs/manual/manual.log +1786 -0
  46. data/blogs/manual/manual.md +1360 -636
  47. data/blogs/manual/manual.tex +1883 -1161
  48. data/blogs/manual/manual_files/figure-html/bubble-1.png +0 -0
  49. data/blogs/manual/manual_files/figure-html/diverging_bar.png +0 -0
  50. data/blogs/manual/manual_files/figure-latex/bubble-1.png +0 -0
  51. data/blogs/manual/model.rb +1 -1
  52. data/blogs/nse_dplyr/nse_dplyr.Rmd +84 -111
  53. data/blogs/nse_dplyr/nse_dplyr.log +928 -0
  54. data/blogs/nse_dplyr/nse_dplyr.md +198 -229
  55. data/blogs/oh_my/not_so.rb +0 -0
  56. data/blogs/oh_my/oh_my.Rmd +1234 -25
  57. data/blogs/oh_my/oh_my.log +804 -0
  58. data/blogs/oh_my/oh_my.md +1663 -86
  59. data/blogs/oh_my/oh_my.tex +821 -0
  60. data/blogs/oh_my/old.Rmd +15 -14
  61. data/blogs/ruby_plot/ruby_plot.Rmd +58 -82
  62. data/blogs/ruby_plot/ruby_plot.log +885 -0
  63. data/blogs/ruby_plot/ruby_plot.md +71 -102
  64. data/blogs/ruby_plot/ruby_plot.tex +940 -0
  65. data/blogs/ruby_plot/ruby_plot_files/figure-html/dose_len.png +0 -0
  66. data/blogs/ruby_plot/ruby_plot_files/figure-html/facet_by_delivery.png +0 -0
  67. data/blogs/ruby_plot/ruby_plot_files/figure-html/facet_by_dose.png +0 -0
  68. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_by_delivery_color.png +0 -0
  69. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_by_delivery_color2.png +0 -0
  70. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_decorations.png +0 -0
  71. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_jitter.png +0 -0
  72. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_points.png +0 -0
  73. data/blogs/ruby_plot/ruby_plot_files/figure-html/final_box_plot.png +0 -0
  74. data/blogs/ruby_plot/ruby_plot_files/figure-html/final_violin_plot.png +0 -0
  75. data/blogs/ruby_plot/ruby_plot_files/figure-html/violin_with_jitter.png +0 -0
  76. data/blogs/ruby_plot/ruby_plot_files/figure-latex/dose_len.png +0 -0
  77. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facet_by_delivery.png +0 -0
  78. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facet_by_dose.png +0 -0
  79. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_by_delivery_color.png +0 -0
  80. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_by_delivery_color2.png +0 -0
  81. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_decorations.png +0 -0
  82. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_jitter.png +0 -0
  83. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_points.png +0 -0
  84. data/blogs/ruby_plot/ruby_plot_files/figure-latex/final_box_plot.png +0 -0
  85. data/blogs/ruby_plot/ruby_plot_files/figure-latex/final_violin_plot.png +0 -0
  86. data/blogs/ruby_plot/ruby_plot_files/figure-latex/violin_with_jitter.png +0 -0
  87. data/blogs/test/test.Rmd +14 -0
  88. data/examples/50Plots_MasterList/Images/midwest-scatterplot.PNG +0 -0
  89. data/examples/50Plots_MasterList/ScatterPlot.rb +0 -0
  90. data/examples/50Plots_MasterList/scatter_plot.rb +0 -0
  91. data/examples/Bibliography/master.bib +0 -0
  92. data/examples/Bibliography/stats.bib +0 -0
  93. data/examples/R/calc.R +0 -0
  94. data/examples/R/java_interop.R +0 -0
  95. data/examples/bioconductor_deseq2_airway/Documentation/DESeq2-airway-walkthrough.md +56 -0
  96. data/examples/bioconductor_deseq2_airway/bench_galaaz_three_same_process.rb +53 -0
  97. data/examples/bioconductor_deseq2_airway/bench_r_three_same_process.R +34 -0
  98. data/examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb +33 -0
  99. data/examples/bioconductor_deseq2_airway/deseq2_airway_galaaz_optimized.rb +34 -0
  100. data/examples/bioconductor_deseq2_airway/deseq2_airway_minimal.R +30 -0
  101. data/examples/bioconductor_deseq2_airway/deseq2_airway_pipeline_for_bench.R +36 -0
  102. data/examples/islr/all.rb +13 -0
  103. data/examples/islr/ch2.spec.rb +37 -7
  104. data/examples/islr/ch3.spec.rb +11 -2
  105. data/examples/islr/ch3_boston.rb +27 -0
  106. data/examples/islr/ch3_multiple_regression.rb +0 -0
  107. data/examples/islr/ch6.spec.rb +24 -1
  108. data/examples/islr/x_y_rnorm.jpg +0 -0
  109. data/examples/latex_templates/Test-acm_article/acm_proc_article-sp.cls +0 -0
  110. data/examples/latex_templates/Test-acm_article/sigproc.bib +0 -0
  111. data/examples/latex_templates/Test-acs_article/acs-Test-acs_article.bib +0 -0
  112. data/examples/latex_templates/Test-acs_article/acs-my_output.bib +0 -0
  113. data/examples/latex_templates/Test-aea_article/BibFile.bib +0 -0
  114. data/examples/latex_templates/Test-aea_article/Test-aea_article.Rmd +0 -0
  115. data/examples/latex_templates/Test-aea_article/references.bib +0 -0
  116. data/examples/latex_templates/Test-amq_article/Test-amq_article.Rmd +0 -0
  117. data/examples/latex_templates/Test-amq_article/Test-amq_article.pdfsync +0 -0
  118. data/examples/latex_templates/Test-ieee_article/IEEEtran.bst +0 -0
  119. data/examples/latex_templates/Test-ieee_article/mybibfile.bib +0 -0
  120. data/examples/latex_templates/Test-rjournal_article/RJournal.sty +0 -0
  121. data/examples/latex_templates/Test-rjournal_article/RJreferences.bib +0 -0
  122. data/examples/latex_templates/Test-rjournal_article/Test-rjournal_article.Rmd +0 -0
  123. data/examples/misc/baseball.csv +0 -0
  124. data/examples/misc/ggplot.rb +3 -2
  125. data/examples/misc/moneyball.rb +0 -0
  126. data/examples/misc/subsetting.rb +0 -0
  127. data/examples/multithread_shards_to_r/shards_to_r.rb +67 -0
  128. data/examples/rmarkdown/svm-rmarkdown-anon-ms-example/svm-rmarkdown-anon-ms-example.Rmd +0 -0
  129. data/examples/rmarkdown/svm-rmarkdown-article-example/svm-rmarkdown-article-example.Rmd +0 -0
  130. data/examples/rmarkdown/svm-rmarkdown-beamer-example/svm-rmarkdown-beamer-example.Rmd +0 -0
  131. data/examples/rmarkdown/svm-rmarkdown-cv/svm-rmarkdown-cv.Rmd +0 -0
  132. data/examples/rmarkdown/svm-rmarkdown-syllabus-example/attend-grade-relationships.csv +0 -0
  133. data/examples/rmarkdown/svm-rmarkdown-syllabus-example/svm-rmarkdown-syllabus-example.Rmd +0 -0
  134. data/examples/rmarkdown/svm-xaringan-example/svm-xaringan-example.Rmd +0 -0
  135. data/examples/sthda_ggplot/README.md +0 -0
  136. data/examples/sthda_ggplot/RUN.md +41 -0
  137. data/examples/sthda_ggplot/all.rb +0 -0
  138. data/examples/sthda_ggplot/one_variable_continuous/density_gg.rb +0 -0
  139. data/examples/sthda_ggplot/one_variable_continuous/geom_area.rb +0 -0
  140. data/examples/sthda_ggplot/one_variable_continuous/geom_density.rb +2 -0
  141. data/examples/sthda_ggplot/one_variable_continuous/geom_dotplot.rb +0 -0
  142. data/examples/sthda_ggplot/one_variable_continuous/geom_freqpoly.rb +0 -0
  143. data/examples/sthda_ggplot/one_variable_continuous/geom_histogram.rb +0 -0
  144. data/examples/sthda_ggplot/one_variable_continuous/histogram_density.rb +0 -0
  145. data/examples/sthda_ggplot/one_variable_continuous/stat.rb +0 -0
  146. data/examples/sthda_ggplot/one_variable_discrete/bar.rb +0 -0
  147. data/examples/sthda_ggplot/qplots/box_violin_dot.rb +0 -0
  148. data/examples/sthda_ggplot/qplots/scatter_plots.rb +0 -0
  149. data/examples/sthda_ggplot/scatter_gg.rb +0 -0
  150. data/examples/sthda_ggplot/two_variables_cont_bivariate/geom_bin2d.rb +0 -0
  151. data/examples/sthda_ggplot/two_variables_cont_bivariate/geom_density2d.rb +0 -0
  152. data/examples/sthda_ggplot/two_variables_cont_bivariate/geom_hex.rb +0 -0
  153. data/examples/sthda_ggplot/two_variables_cont_cont/geom_point.rb +0 -0
  154. data/examples/sthda_ggplot/two_variables_cont_cont/geom_smooth.rb +0 -0
  155. data/examples/sthda_ggplot/two_variables_cont_cont/misc.rb +0 -0
  156. data/examples/sthda_ggplot/two_variables_cont_function/geom_area.rb +4 -3
  157. data/examples/sthda_ggplot/two_variables_disc_cont/geom_bar.rb +0 -0
  158. data/examples/sthda_ggplot/two_variables_disc_cont/geom_boxplot.rb +0 -0
  159. data/examples/sthda_ggplot/two_variables_disc_cont/geom_dotplot.rb +0 -0
  160. data/examples/sthda_ggplot/two_variables_disc_cont/geom_jitter.rb +0 -0
  161. data/examples/sthda_ggplot/two_variables_disc_cont/geom_line.rb +0 -0
  162. data/examples/sthda_ggplot/two_variables_disc_cont/geom_violin.rb +0 -0
  163. data/examples/sthda_ggplot/two_variables_disc_disc/geom_jitter.rb +0 -0
  164. data/examples/sthda_ggplot/two_variables_error/geom_crossbar.rb +0 -0
  165. data/ext/new_bridge/Makefile +46 -0
  166. data/ext/new_bridge/galaaz_gatekeeper_phase0.cpp +12 -0
  167. data/ext/new_bridge/galaaz_gatekeeper_phase1.cpp +1639 -0
  168. data/lib/R_interface/galaaz_device.R +20 -0
  169. data/lib/R_interface/include_engine.R +109 -0
  170. data/lib/R_interface/new_bridge_adapter.rb +824 -0
  171. data/lib/R_interface/r.rb +177 -25
  172. data/lib/R_interface/r_arrow.rb +113 -0
  173. data/lib/R_interface/r_libs.R +3 -3
  174. data/lib/R_interface/r_methods.rb +13 -126
  175. data/lib/R_interface/r_module_s.rb +0 -0
  176. data/lib/R_interface/rbinary_operators.rb +20 -2
  177. data/lib/R_interface/rclosure.rb +5 -1
  178. data/lib/R_interface/rdata_frame.rb +34 -70
  179. data/lib/R_interface/rdevice.rb +125 -0
  180. data/lib/R_interface/rdevices.R +0 -0
  181. data/lib/R_interface/renvironment.rb +10 -4
  182. data/lib/R_interface/rexpression.rb +5 -1
  183. data/lib/R_interface/rindexed_object.rb +41 -13
  184. data/lib/R_interface/rlanguage.rb +20 -62
  185. data/lib/R_interface/rlist.rb +115 -25
  186. data/lib/R_interface/rlogical_operators.rb +0 -0
  187. data/lib/R_interface/rmatrix.rb +2 -11
  188. data/lib/R_interface/rmd_indexed_object.rb +5 -1
  189. data/lib/R_interface/robject.rb +348 -290
  190. data/lib/R_interface/rpkg.rb +0 -0
  191. data/lib/R_interface/rsupport.rb +609 -328
  192. data/lib/R_interface/rsupport_scope.rb +2 -1
  193. data/lib/R_interface/rsymbol.rb +50 -0
  194. data/lib/R_interface/ruby_callback.rb +2 -3
  195. data/lib/R_interface/ruby_extensions.rb +225 -175
  196. data/lib/R_interface/runary_operators.rb +0 -0
  197. data/lib/R_interface/rvector.rb +147 -31
  198. data/lib/galaaz.rb +0 -0
  199. data/lib/galaaz_jruby.rb +22 -0
  200. data/lib/gknit/diagnostics.rb +50 -0
  201. data/lib/gknit/draft.rb +23 -17
  202. data/lib/gknit/include_engine.rb +15 -7
  203. data/lib/gknit/knitr_engine.rb +223 -74
  204. data/lib/gknit/rb_engine.rb +3 -3
  205. data/lib/gknit/ruby_engine.rb +0 -0
  206. data/lib/gknit.rb +1 -0
  207. data/lib/new_bridge/bootstrap/windows_bootstrap.rb +285 -0
  208. data/lib/new_bridge/envelope.rb +51 -0
  209. data/lib/new_bridge/eval_result.rb +26 -0
  210. data/lib/new_bridge/framing.rb +39 -0
  211. data/lib/new_bridge/instance_pool_client.rb +38 -0
  212. data/lib/new_bridge/r_instance_manager.rb +404 -0
  213. data/lib/new_bridge/session_client.rb +530 -0
  214. data/lib/new_bridge/tcp_framed.rb +44 -0
  215. data/lib/new_bridge.rb +9 -0
  216. data/lib/util/exec_ruby.rb +95 -20
  217. data/lib/util/inline_file.rb +35 -30
  218. data/new_bridge_specs/benchmark_phase5_5_unboxing_spec.rb +96 -0
  219. data/new_bridge_specs/eval_r_async_spec.rb +113 -0
  220. data/new_bridge_specs/integration_phase5_1_concurrent_spec.rb +50 -0
  221. data/new_bridge_specs/integration_phase5_1_eval_spec.rb +16 -0
  222. data/new_bridge_specs/integration_phase5_1_r_api_spec.rb +25 -0
  223. data/new_bridge_specs/integration_phase5_1_smoke_spec.rb +31 -0
  224. data/new_bridge_specs/integration_phase5_2_dataframe_unboxing_spec.rb +19 -0
  225. data/new_bridge_specs/integration_phase5_2_handle_eval_unboxing_spec.rb +25 -0
  226. data/new_bridge_specs/integration_phase5_3_callback_args_spec.rb +28 -0
  227. data/new_bridge_specs/integration_phase5_3_callback_error_spec.rb +22 -0
  228. data/new_bridge_specs/integration_phase5_3_callback_timeout_spec.rb +28 -0
  229. data/new_bridge_specs/integration_phase5_3_callbacks_smoke_spec.rb +22 -0
  230. data/new_bridge_specs/integration_phase5_3_edge_cases_spec.rb +52 -0
  231. data/new_bridge_specs/integration_phase5_3_nested_spec.rb +30 -0
  232. data/new_bridge_specs/integration_phase5_4_concurrent_sessions_spec.rb +53 -0
  233. data/new_bridge_specs/integration_phase5_4_nested_session_callbacks_spec.rb +49 -0
  234. data/new_bridge_specs/integration_phase5_4_session_routing_spec.rb +38 -0
  235. data/new_bridge_specs/integration_phase5_5_stress_concurrency_spec.rb +52 -0
  236. data/new_bridge_specs/integration_phase5_5_unbox_walk_spec.rb +46 -0
  237. data/new_bridge_specs/phase0_protocol_spec.rb +96 -0
  238. data/new_bridge_specs/phase1_req_ret_spec.rb +66 -0
  239. data/new_bridge_specs/phase2_multi_instance_spec.rb +67 -0
  240. data/new_bridge_specs/phase3_callbacks_spec.rb +71 -0
  241. data/new_bridge_specs/phase4_2_hardening_spec.rb +252 -0
  242. data/new_bridge_specs/phase4_3_r_instance_manager_spec.rb +85 -0
  243. data/new_bridge_specs/phase4_nested_callbacks_spec.rb +123 -0
  244. data/r_requires/ggplot.rb +0 -0
  245. data/r_requires/knitr.rb +0 -0
  246. data/specs/all.rb +15 -11
  247. data/specs/arrow_from_ruby_batches_spec.rb +50 -0
  248. data/specs/arrow_semantics_spec.rb +64 -0
  249. data/specs/bridge_concurrent_spec.rb +46 -0
  250. data/specs/bridge_nested_spec.rb +25 -0
  251. data/specs/dataframe_semantics_spec.rb +122 -0
  252. data/specs/dataframe_single_index_logical_filter_spec.rb +21 -0
  253. data/specs/dispatch_probe_cache_spec.rb +38 -0
  254. data/specs/dispatch_probe_error_class_fallback_spec.rb +20 -0
  255. data/specs/dispatch_probe_fallback_spec.rb +18 -0
  256. data/specs/environment_semantics_spec.rb +89 -0
  257. data/specs/field_access_spec.rb +31 -0
  258. data/specs/figures/bg.jpeg +0 -0
  259. data/specs/figures/bg.png +0 -0
  260. data/specs/figures/bg.svg +168 -57
  261. data/specs/figures/dose_len.png +0 -0
  262. data/specs/figures/no_args.jpeg +0 -0
  263. data/specs/figures/no_args.png +0 -0
  264. data/specs/figures/no_args.svg +168 -57
  265. data/specs/figures/width_height.jpeg +0 -0
  266. data/specs/figures/width_height.png +0 -0
  267. data/specs/figures/width_height_units1.jpeg +0 -0
  268. data/specs/figures/width_height_units1.png +0 -0
  269. data/specs/figures/width_height_units2.jpeg +0 -0
  270. data/specs/figures/width_height_units2.png +0 -0
  271. data/specs/formula_semantics_spec.rb +81 -0
  272. data/specs/galaaz_util_exec_ruby_spec.rb +85 -0
  273. data/specs/galaaz_util_inline_file_spec.rb +54 -0
  274. data/specs/gknit_cli_option_permutation_spec.rb +24 -0
  275. data/specs/gknit_include_engine_spec.rb +72 -0
  276. data/specs/gknit_install_timeout_report_spec.rb +69 -0
  277. data/specs/gknit_internal_error_report_spec.rb +57 -0
  278. data/specs/gknit_vector_map_output_spec.rb +59 -0
  279. data/specs/globalenv_guardrail_spec.rb +52 -0
  280. data/specs/language_expression_semantics_spec.rb +145 -0
  281. data/specs/list_semantics_spec.rb +111 -0
  282. data/specs/new_bridge_bulk_dataframe_transfer_spec.rb +44 -0
  283. data/specs/new_bridge_bulk_vector_transfer_spec.rb +73 -0
  284. data/specs/new_bridge_callback_timeout_spec.rb +69 -0
  285. data/specs/new_bridge_eval_r_fallback_spec.rb +55 -0
  286. data/specs/nil_null_spec.rb +42 -0
  287. data/specs/object_build_phase2_spec.rb +53 -0
  288. data/specs/phase1_callback_bridge_spec.rb +84 -0
  289. data/specs/phase2_gknit_generic_rendering_guardrail_spec.rb +46 -0
  290. data/specs/phase2_gknit_no_raw_code_leakage_spec.rb +43 -0
  291. data/specs/phase3_gknit_generic_graphics_capture_spec.rb +71 -0
  292. data/specs/plot_device_semantics_spec.rb +28 -0
  293. data/specs/plot_snapshot_semantics_spec.rb +58 -0
  294. data/specs/protocol_result_spec.rb +236 -0
  295. data/specs/r_batch_fail_fast_spec.rb +47 -0
  296. data/specs/r_bridge_bootstrap_spec.rb +11 -0
  297. data/specs/r_devices.spec.rb +1 -1
  298. data/specs/r_eval.spec.rb +16 -18
  299. data/specs/r_function.spec.rb +1 -1
  300. data/specs/r_instance_manager_spec.rb +285 -0
  301. data/specs/r_list_apply.spec.rb +15 -15
  302. data/specs/r_matrix.spec.rb +0 -0
  303. data/specs/r_nse.spec.rb +5 -5
  304. data/specs/r_object_send_dispatch_spec.rb +13 -0
  305. data/specs/r_vector_comparator_spec.rb +8 -0
  306. data/specs/r_vector_creation.spec.rb +0 -0
  307. data/specs/r_vector_functions.spec.rb +0 -0
  308. data/specs/r_vector_object.spec.rb +0 -0
  309. data/specs/r_vector_operators.spec.rb +0 -0
  310. data/specs/r_vector_structured_scalar_reads_spec.rb +35 -0
  311. data/specs/r_vector_subsetting.spec.rb +0 -0
  312. data/specs/range_helper_spec.rb +21 -0
  313. data/specs/rsupport_scope_spec.rb +28 -0
  314. data/specs/rsupport_var_name_thread_safety_spec.rb +24 -0
  315. data/specs/scalar_character_spec.rb +44 -0
  316. data/specs/scoped_symbol_dsl_refinement_spec.rb +40 -0
  317. data/specs/session_env_bridge_spec.rb +25 -0
  318. data/specs/simplecov_bootstrap_spec.rb +10 -0
  319. data/specs/spec_helper.rb +10 -0
  320. data/specs/tmp.rb +0 -0
  321. data/specs/unboxing_recursion_regression_spec.rb +30 -0
  322. data/specs/unboxing_spec.rb +49 -0
  323. data/specs/verify_callbacks.rb +42 -0
  324. data/sty/galaaz.sty +0 -0
  325. data/version.rb +1 -1
  326. metadata +194 -64
  327. data/blogs/galaaz_ggplot/galaaz_ggplot.html +0 -520
  328. data/blogs/galaaz_ggplot/galaaz_ggplot.pdf +0 -0
  329. data/blogs/galaaz_ggplot/midwest.html +0 -188
  330. data/blogs/gknit/gknit.html +0 -2266
  331. data/blogs/gknit/gknit.pdf +0 -0
  332. data/blogs/manual/manual.html +0 -4638
  333. data/blogs/manual/manual.pdf +0 -0
  334. data/blogs/manual/manual_files/figure-latex/diverging_bar.pdf +0 -0
  335. data/blogs/nse_dplyr/nse_dplyr.html +0 -878
  336. data/blogs/nse_dplyr/nse_dplyr.pdf +0 -0
  337. data/blogs/oh_my/oh_my.html +0 -568
  338. data/blogs/ruby_plot/ruby_plot.html +0 -544
  339. data/blogs/ruby_plot/ruby_plot.pdf +0 -0
  340. data/examples/latex_templates/Test-acs_article/Test-acs_article.pdf +0 -0
  341. data/examples/latex_templates/Test-aea_article/Test-aea_article.pdf +0 -0
  342. data/examples/latex_templates/Test-amq_article/Test-amq_article.pdf +0 -0
  343. data/examples/latex_templates/Test-amq_article/pics/Figure2.pdf +0 -0
  344. data/examples/latex_templates/Test-asa_article/Test-asa_article.pdf +0 -0
  345. data/examples/latex_templates/Test-ieee_article/Test-ieee_article.pdf +0 -0
  346. data/examples/latex_templates/Test-rjournal_article/RJwrapper.pdf +0 -0
  347. data/examples/latex_templates/Test-springer_article/Test-springer_article.pdf +0 -0
  348. data/examples/rmarkdown/svm-rmarkdown-anon-ms-example/svm-rmarkdown-anon-ms-example.pdf +0 -0
  349. data/examples/rmarkdown/svm-rmarkdown-article-example/svm-rmarkdown-article-example.pdf +0 -0
  350. data/examples/rmarkdown/svm-rmarkdown-beamer-example/svm-rmarkdown-beamer-example.pdf +0 -0
  351. data/examples/rmarkdown/svm-rmarkdown-cv/svm-rmarkdown-cv.pdf +0 -0
  352. data/examples/rmarkdown/svm-rmarkdown-syllabus-example/svm-rmarkdown-syllabus-example.pdf +0 -0
  353. data/specs/r_dataframe.spec.rb +0 -379
  354. data/specs/r_environment.spec.rb +0 -140
  355. data/specs/r_formula.spec.rb +0 -232
  356. data/specs/r_language.spec.rb +0 -112
  357. data/specs/r_list.spec.rb +0 -293
  358. data/specs/r_plots.spec.rb +0 -72
  359. data/specs/ruby_expression.spec.rb +0 -316
@@ -0,0 +1,530 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require 'securerandom'
5
+ require 'socket'
6
+ require 'tmpdir'
7
+ require 'timeout'
8
+
9
+ require_relative 'envelope'
10
+ require_relative 'eval_result'
11
+ require_relative 'framing'
12
+
13
+ module NewBridge
14
+ # Pending async +eval_r_async+ entry: completion runs on a background thread (not the reader).
15
+ class AsyncPendingEntry
16
+ attr_reader :instance_id, :block
17
+ attr_accessor :timer_thread
18
+
19
+ def initialize(instance_id, block)
20
+ @instance_id = instance_id
21
+ @block = block
22
+ @timer_thread = nil
23
+ end
24
+
25
+ def cancel_timer!
26
+ t = @timer_thread
27
+ return unless t&.alive?
28
+ return if Thread.current.equal?(t)
29
+
30
+ t.kill
31
+ t.join(0.05)
32
+ rescue StandardError
33
+ nil
34
+ end
35
+ end
36
+
37
+ # NewBridge::SessionClient
38
+ #
39
+ # Responsibilities (Ruby side, inside one Ruby process):
40
+ # - Accept one connection from the R gatekeeper (Unix socket or TCP).
41
+ # - Serialize and send framed MsgPack envelopes of type `REQ`.
42
+ # - Wait for matching framed MsgPack envelopes of type `RET`.
43
+ # - Handle R -> Ruby callback requests (`CALL`) by invoking registered Ruby
44
+ # blocks in background threads and replying with `RET`.
45
+ #
46
+ # Protocol basics
47
+ # - Transport: Unix domain socket for local R (loopback), TCP when R connects to a non-local host
48
+ # (e.g. Docker). Override with ENV['GALAAZ_BRIDGE_TRANSPORT'] = unix|tcp. Same MsgPack framing.
49
+ # - Envelopes are MsgPack maps handled by `NewBridge::Envelope`.
50
+ # - Envelope fields used here:
51
+ # - `call_id`: UUID used to correlate one REQ with one RET
52
+ # - `type`: `"REQ"` / `"RET"` / `"CALL"`
53
+ # - `session_id`: logical session; R uses it to isolate per-session env state
54
+ # - `instance_id`: logical instance routing; used for multi-instance pools
55
+ # - `parent_id`: reserved for nested-call correlation (Phase 4+)
56
+ #
57
+ # Threading model
58
+ # - One reader thread (`reader_loop`) continuously reads frames and dispatches:
59
+ # - `RET` -> pushes into the per-call Queue (sync +eval_r+) or schedules async completion (+eval_r_async+)
60
+ # - `CALL` -> spawns a new thread to execute the Ruby callback
61
+ # - Callback threads send `RET` back to R while keeping the reader thread free,
62
+ # which is required to support nested REQ servicing while waiting for a callback.
63
+ #
64
+ # Failure behavior
65
+ # - If the socket closes or a read error happens, pending REQ calls are signaled
66
+ # as closed and subsequent eval calls raise.
67
+ # - Infrastructure failures (timeouts / broken pipes / connection reset) are
68
+ # handled at a higher level by `RInstanceManager` restart+retry logic.
69
+ class SessionClient
70
+ class Error < StandardError; end
71
+ class TimeoutError < Error; end
72
+ class RProcessError < Error; end
73
+
74
+ attr_reader :r_stderr, :r_exit_status
75
+
76
+ # Create a SessionClient for one R runtime instance.
77
+ #
78
+ # @param source_path [String] Host-side path to .cpp or .so for Rcpp `sourceCpp`/`dyn.load`.
79
+ # @param host [String] bind/listen address for Ruby TCP server.
80
+ # @param bridge_host [String,nil] host name/path R uses to connect to this Ruby listener (defaults to +host+).
81
+ # For containerized R, set to the host reachable from the container (e.g. host.docker.internal).
82
+ # @param runtime_source_path [String,nil] source path visible from runtime process/container.
83
+ # @param use_unix [Boolean,nil] force Unix (true) or TCP (false). Default nil: auto — Unix for local
84
+ # loopback targets, TCP for remote/non-loopback +bridge_host+.
85
+ # @param r_cmd [String, Array<String>] executable (or argv prefix ending with executable).
86
+ def initialize(source_path:, host: '127.0.0.1', bridge_host: nil, runtime_source_path: nil, r_cmd: 'R',
87
+ use_unix: nil)
88
+ @source_path = File.expand_path(source_path)
89
+ @use_precompiled = @source_path.end_with?('.so')
90
+ @host = host
91
+ @bridge_host = bridge_host || host
92
+ @runtime_source_path = runtime_source_path || @source_path
93
+ @r_cmd = r_cmd
94
+ @use_unix = if use_unix.nil?
95
+ self.class.default_use_unix?(bridge_target: @bridge_host)
96
+ else
97
+ use_unix
98
+ end
99
+ @unix_path = nil
100
+ @server = nil
101
+ @sock = nil
102
+ @write_mx = Mutex.new
103
+ @pending = {}
104
+ @pending_mx = Mutex.new
105
+ @callbacks = {}
106
+ @callbacks_mx = Mutex.new
107
+ @reader = nil
108
+ @r_stderr = +''
109
+ @r_stderr_mx = Mutex.new
110
+ @r_stderr_limit = 64 * 1024
111
+ @r_exit_status = nil
112
+ end
113
+
114
+ # Whether to use Unix domain sockets for this client when +use_unix+ is nil.
115
+ #
116
+ # Policy:
117
+ # - +GALAAZ_BRIDGE_TRANSPORT=tcp+ — always TCP (remote R, or debugging).
118
+ # - +GALAAZ_BRIDGE_TRANSPORT=unix+ — always Unix when supported.
119
+ # - Otherwise: Unix only if +bridge_target+ is a local loopback address/name (127.0.0.1, ::1, localhost)
120
+ # or a Unix socket path (+#+). Any other host (e.g. +host.docker.internal+, LAN IP) uses TCP so R can
121
+ # reach Ruby over the network.
122
+ #
123
+ # @param bridge_target [String] resolved +bridge_host+ passed to R (+galaaz_run_bridge+).
124
+ def self.default_use_unix?(bridge_target:)
125
+ return false unless defined?(UNIXServer)
126
+
127
+ case ENV['GALAAZ_BRIDGE_TRANSPORT'].to_s.downcase
128
+ when 'tcp'
129
+ false
130
+ when 'unix'
131
+ true
132
+ else
133
+ t = bridge_target.to_s.strip
134
+ if t.empty?
135
+ false
136
+ elsif t.start_with?('/')
137
+ true
138
+ else
139
+ u = t.downcase
140
+ %w[127.0.0.1 ::1 localhost].include?(u)
141
+ end
142
+ end
143
+ end
144
+
145
+ # Start listening for a single TCP connection from one R process and launch that process.
146
+ #
147
+ # - Binds to an ephemeral TCP port, or a Unix socket path when +use_unix+ is set.
148
+ # - Starts a thread that runs `r_cmd` and sources the gatekeeper C++ code inside R.
149
+ # - Accepts the runtime connection with `accept_timeout`.
150
+ #
151
+ # On startup failure (e.g. runtime exits before connecting), it surfaces R stderr and exit status.
152
+ def start(accept_timeout: 120)
153
+ use_u = @use_unix && defined?(UNIXServer)
154
+ if @use_unix && !defined?(UNIXServer)
155
+ warn '[NewBridge::SessionClient] Unix bridge transport selected but UNIXServer is unavailable; falling back to TCP'
156
+ use_u = false
157
+ end
158
+
159
+ if use_u
160
+ @unix_path = File.join(Dir.tmpdir, "galaaz_bridge_#{Process.pid}_#{SecureRandom.hex(6)}.sock")
161
+ File.unlink(@unix_path) if File.exist?(@unix_path)
162
+ @server = UNIXServer.new(@unix_path)
163
+ port = 0
164
+ bridge_for_r = @unix_path.gsub("'", "\\\\'")
165
+ else
166
+ @server = TCPServer.new(@host, 0)
167
+ port = @server.addr[1]
168
+ bridge_for_r = @bridge_host.gsub("'", "\\\\'")
169
+ end
170
+
171
+ # Use sourceCpp with a persistent cache directory so repeated runtime
172
+ # starts can reuse compiled shared objects when source is unchanged.
173
+ cpp_escaped = @runtime_source_path.gsub("'", "\\\\'")
174
+ rcpp_cache_dir = (ENV['GALAAZ_RCPP_CACHE_DIR'] || File.join(Dir.tmpdir, 'galaaz_rcpp_cache')).gsub("'", "\\\\'")
175
+ r_script = <<~R
176
+ bridge_host <- '#{bridge_for_r}'
177
+ port <- #{port}L
178
+ stopifnot(requireNamespace("Rcpp", quietly = TRUE))
179
+ library(Rcpp)
180
+ dir.create("#{rcpp_cache_dir}", recursive = TRUE, showWarnings = FALSE)
181
+ sourceCpp(file = "#{cpp_escaped}", rebuild = FALSE, cacheDir = "#{rcpp_cache_dir}")
182
+ galaaz_run_bridge(bridge_host, as.integer(port))
183
+ R
184
+
185
+ env = if use_u
186
+ { 'GALAAZ_BRIDGE_HOST' => @unix_path, 'GALAAZ_BRIDGE_PORT' => '0', 'GALAAZ_BRIDGE_UNIX' => '1' }
187
+ else
188
+ { 'GALAAZ_BRIDGE_HOST' => @bridge_host, 'GALAAZ_BRIDGE_PORT' => port.to_s }
189
+ end
190
+ @r_thr = Thread.new do
191
+ launch = @r_cmd.is_a?(Array) ? @r_cmd.dup : [@r_cmd]
192
+ _stdin, stdout_err, wait_thr = Open3.popen2e(env, *launch, '--slave', '--no-save', '-e', r_script)
193
+ # When the R process exits or during shutdown, the underlying pipe can
194
+ # close while this background thread is still blocked reading.
195
+ # Treat that as a normal shutdown and avoid JRuby "stream closed in
196
+ # another thread" warnings.
197
+ begin
198
+ stdout_err.each_line { |line| append_r_stderr(line) }
199
+ rescue IOError, Errno::EPIPE, Errno::ECONNRESET
200
+ # normal shutdown / pipe closure
201
+ end
202
+ @r_exit_status = wait_thr.value
203
+ end
204
+
205
+ @sock = Timeout.timeout(accept_timeout) { @server.accept }
206
+ @reader = Thread.new { reader_loop }
207
+ self
208
+ rescue Timeout::Error
209
+ # If the runtime process failed early (e.g. docker permission/network/source path),
210
+ # surface stderr/exit details instead of a generic accept timeout.
211
+ process_status = @r_exit_status&.exitstatus
212
+ details = current_r_stderr
213
+ msg = "failed to accept runtime connection within #{accept_timeout}s"
214
+ msg += " (runtime exit=#{process_status})" if process_status
215
+ unless details.empty?
216
+ tail = details.length > 2000 ? details[-2000, 2000] : details
217
+ msg += " stderr_tail=#{tail}"
218
+ end
219
+ raise RProcessError, msg
220
+ end
221
+
222
+ # Stop the client and attempt to join threads cleanly.
223
+ # Best-effort: socket/server are closed, reader thread is joined, runtime thread is joined.
224
+ def stop
225
+ @sock&.close rescue nil
226
+ @server&.close rescue nil
227
+ if @unix_path && File.socket?(@unix_path)
228
+ File.unlink(@unix_path) rescue nil
229
+ end
230
+ @unix_path = nil
231
+ @reader&.join(3)
232
+ @r_thr&.join(15)
233
+ end
234
+
235
+ # Evaluate one REQ on the connected R runtime and wait for the matching RET.
236
+ #
237
+ # @param code [String] R expression or snippet to be evaluated by the gatekeeper.
238
+ # @param session_id [String] session env isolation key (maps to R's `.galaaz_sessions`).
239
+ # @param instance_id [String] instance routing key (must match RInstanceManager routing).
240
+ # @param parent_id [String,nil] reserved for nested-call correlation (Phase 4+).
241
+ # @param timeout [Numeric] how long to wait for RET before raising TimeoutError.
242
+ #
243
+ # @return [Hash] decoded MsgPack payload map returned by gatekeeper on success.
244
+ def eval_r(code, session_id: 'default', instance_id: 'default', parent_id: nil, timeout: 60)
245
+ call_id = SecureRandom.uuid
246
+ q = Queue.new
247
+ @pending_mx.synchronize { @pending[call_id] = q }
248
+ req = Envelope.encode(
249
+ 'call_id' => call_id,
250
+ 'type' => 'REQ',
251
+ 'session_id' => session_id,
252
+ 'instance_id' => instance_id,
253
+ 'parent_id' => parent_id,
254
+ 'payload' => code
255
+ )
256
+ @write_mx.synchronize { Framing.write_frame(@sock, req) }
257
+
258
+ ret = wait_for_ret(q, call_id, timeout)
259
+ if ret['instance_id'] && ret['instance_id'] != instance_id
260
+ raise RProcessError, "instance_id mismatch: expected=#{instance_id} got=#{ret['instance_id']}"
261
+ end
262
+ status = ret['status']
263
+ payload = ret['payload']
264
+ unless payload.is_a?(Hash)
265
+ raise RProcessError, "invalid RET payload type=#{payload.class} (expected map)"
266
+ end
267
+ parsed = payload
268
+ raise RProcessError, parsed['message'] || parsed.inspect unless status == 'success'
269
+
270
+ parsed
271
+ end
272
+
273
+ # Schedule one REQ and invoke +block+ on a background thread when RET arrives (or on failure).
274
+ #
275
+ # Unlike +eval_r+, this method returns immediately with the +call_id+ String; it does not wait
276
+ # for R unless +timeout+ is nil (wait forever on the Ruby side for RET) or a Numeric (raise
277
+ # completion with {TimeoutError} if RET is late).
278
+ #
279
+ # @param timeout [nil, Numeric] +nil+ means no limit; a positive number starts a timer thread.
280
+ # @return [String] +call_id+ for correlation / debugging
281
+ # @raise [ArgumentError] if no block is given
282
+ def eval_r_async(code, session_id: 'default', instance_id: 'default', parent_id: nil, timeout: nil, &block)
283
+ raise ArgumentError, 'eval_r_async requires a block' unless block
284
+
285
+ call_id = SecureRandom.uuid
286
+ slot = AsyncPendingEntry.new(instance_id, block)
287
+ @pending_mx.synchronize { @pending[call_id] = slot }
288
+
289
+ req = Envelope.encode(
290
+ 'call_id' => call_id,
291
+ 'type' => 'REQ',
292
+ 'session_id' => session_id,
293
+ 'instance_id' => instance_id,
294
+ 'parent_id' => parent_id,
295
+ 'payload' => code
296
+ )
297
+ @write_mx.synchronize { Framing.write_frame(@sock, req) }
298
+
299
+ if timeout
300
+ slot.timer_thread = Thread.new { async_timeout_wait(call_id, slot, timeout) }
301
+ end
302
+
303
+ call_id
304
+ end
305
+
306
+ # Register a Ruby callback for R->Ruby `CALL` envelopes.
307
+ #
308
+ # The gatekeeper will invoke this callback when it receives a `CALL` with the
309
+ # corresponding `callback_call_id`, and this client will reply with a `RET`.
310
+ # The same `call_id` may be used many times (R stub function is reused); the block
311
+ # stays registered until the client stops — do not delete on first CALL.
312
+ def register_callback(&block)
313
+ callback_call_id = SecureRandom.uuid
314
+ @callbacks_mx.synchronize { @callbacks[callback_call_id] = block }
315
+ callback_call_id
316
+ end
317
+
318
+ private
319
+
320
+ # Gatekeeper parses RET success payload with strtod. Contract:
321
+ # - `register_callback_proc_stub` ends with nil → ACK "1" (semantic value staged in R).
322
+ # - Direct `register_callback` blocks may return a Numeric scalar; pass it through for legacy tests.
323
+ def self.callback_success_transport_payload(result)
324
+ case result
325
+ when nil
326
+ '1'
327
+ when Numeric
328
+ x = result.to_f
329
+ (x.nan? || x.infinite?) ? '0' : x.to_s
330
+ when true
331
+ '1'
332
+ when false
333
+ '0'
334
+ else
335
+ '1'
336
+ end
337
+ end
338
+
339
+ # Emit debug logs when GALAAZ_DEBUG is set.
340
+ def debug_log(msg)
341
+ return unless ENV['GALAAZ_DEBUG']
342
+ STDERR.puts("[NewBridge::SessionClient] #{msg}")
343
+ STDERR.flush
344
+ end
345
+
346
+ # Wait for RET for one `call_id` by popping from queue `q`.
347
+ # On timeout it removes the pending queue and raises TimeoutError.
348
+ def wait_for_ret(q, call_id, timeout)
349
+ started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
350
+ Timeout.timeout(timeout) do
351
+ v = q.pop
352
+ raise RProcessError, 'R connection closed' if v == :closed
353
+
354
+ v
355
+ end
356
+ rescue Timeout::Error
357
+ @pending_mx.synchronize { @pending.delete(call_id) }
358
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
359
+ raise TimeoutError, format('no RET for %<id>s (timeout=%<timeout>.3fs elapsed=%<elapsed>.3fs)', id: call_id, timeout: timeout.to_f, elapsed: elapsed)
360
+ end
361
+
362
+ # Reader loop running in a background thread:
363
+ # - reads framed envelopes
364
+ # - dispatches by `type`
365
+ # - shields the main eval path from socket read latency
366
+ def reader_loop
367
+ loop do
368
+ bytes = Framing.read_frame(@sock)
369
+ h = Envelope.decode(bytes)
370
+ debug_log("RX type=#{h['type']} call_id=#{h['call_id']} session_id=#{h['session_id']} instance_id=#{h['instance_id']}") if h.is_a?(Hash)
371
+ case h['type']
372
+ when 'RET'
373
+ entry = @pending_mx.synchronize { @pending.delete(h['call_id']) }
374
+ case entry
375
+ when Queue
376
+ entry.push(h)
377
+ when AsyncPendingEntry
378
+ entry.cancel_timer!
379
+ Thread.new { deliver_eval_r_async_completion(entry, h) }
380
+ when nil
381
+ debug_log("stray RET call_id=#{h['call_id']}")
382
+ else
383
+ debug_log("unexpected pending type=#{entry.class} call_id=#{h['call_id']}")
384
+ end
385
+ when 'CALL'
386
+ handle_call(h)
387
+ end
388
+ end
389
+ rescue Framing::TruncatedFrame, Framing::Error, IOError, Errno::ECONNRESET, Errno::EPIPE, Envelope::Error
390
+ signal_closed
391
+ end
392
+
393
+ # Handle one R->Ruby callback request:
394
+ # - look up the Ruby callback by `call_id`
395
+ # - execute it in a new thread
396
+ # - send RET back with status success/error
397
+ def handle_call(h)
398
+ call_id = h['call_id']
399
+ instance_id = h['instance_id'] || 'default'
400
+ payload = h['payload']
401
+ debug_log("CALL received call_id=#{call_id} instance_id=#{instance_id} payload=#{payload.inspect}")
402
+
403
+ callback = @callbacks_mx.synchronize { @callbacks[call_id] }
404
+ unless callback
405
+ send_ret(call_id: call_id, status: 'error', payload: "unknown callback #{call_id}", instance_id: instance_id)
406
+ return
407
+ end
408
+
409
+ # Phase 4: Run callback in separate thread so reader thread stays free
410
+ # for nested REQ/RET handling
411
+ Thread.new do
412
+ begin
413
+ result = callback.call(payload, call_id)
414
+ debug_log("CALL result call_id=#{call_id} result=#{result.inspect}")
415
+ payload_txt = self.class.callback_success_transport_payload(result)
416
+ send_ret(call_id: call_id, status: 'success', payload: payload_txt, instance_id: instance_id)
417
+ rescue IOError, Errno::EPIPE => e
418
+ # Socket closed - ignore, connection is shutting down
419
+ debug_log("CALL send failed (socket closed) call_id=#{call_id}: #{e.message}")
420
+ rescue => e
421
+ debug_log("CALL error call_id=#{call_id} error=#{e.class}: #{e.message}")
422
+ begin
423
+ send_ret(call_id: call_id, status: 'error', payload: e.message, instance_id: instance_id)
424
+ rescue IOError, Errno::EPIPE
425
+ # Socket closed - ignore
426
+ end
427
+ end
428
+ end
429
+ end
430
+
431
+ # Send RET envelope for a CALL.
432
+ # Payload is encoded as a simple string (`result.to_s` or exception message),
433
+ # and gatekeeper converts it back into its expected scalar type.
434
+ def send_ret(call_id:, status:, payload:, instance_id:)
435
+ debug_log("TX RET call_id=#{call_id} instance_id=#{instance_id} status=#{status} payload=#{payload.inspect}")
436
+ ret = Envelope.encode(
437
+ 'call_id' => call_id,
438
+ 'type' => 'RET',
439
+ 'status' => status,
440
+ 'payload' => payload,
441
+ 'instance_id' => instance_id,
442
+ 'parent_id' => nil
443
+ )
444
+ @write_mx.synchronize { Framing.write_frame(@sock, ret) }
445
+ end
446
+
447
+ # Signal all pending eval_r requests that the socket is closed,
448
+ # allowing waiting `eval_r` calls to raise promptly.
449
+ def signal_closed
450
+ entries = @pending_mx.synchronize do
451
+ e = @pending.dup
452
+ @pending.clear
453
+ e
454
+ end
455
+ entries.each do |_call_id, entry|
456
+ case entry
457
+ when Queue
458
+ entry.push(:closed)
459
+ when AsyncPendingEntry
460
+ entry.cancel_timer!
461
+ Thread.new { invoke_async_block_safe(entry, EvalResult.failure(RProcessError.new('R connection closed'))) }
462
+ else
463
+ debug_log("signal_closed: skip pending type=#{entry.class}")
464
+ end
465
+ end
466
+ end
467
+
468
+ def ret_hash_to_eval_result(ret, expected_instance_id)
469
+ unless ret.is_a?(Hash)
470
+ return EvalResult.failure(RProcessError.new("invalid RET payload type=#{ret.class}"))
471
+ end
472
+
473
+ if ret['instance_id'] && ret['instance_id'] != expected_instance_id
474
+ return EvalResult.failure(RProcessError.new("instance_id mismatch: expected=#{expected_instance_id} got=#{ret['instance_id']}"))
475
+ end
476
+
477
+ payload = ret['payload']
478
+ unless payload.is_a?(Hash)
479
+ return EvalResult.failure(RProcessError.new("invalid RET payload inner type=#{payload.class} (expected map)"))
480
+ end
481
+
482
+ return EvalResult.failure(RProcessError.new(payload['message'] || payload.inspect)) if ret['status'] != 'success'
483
+
484
+ EvalResult.success(payload)
485
+ end
486
+
487
+ def deliver_eval_r_async_completion(slot, ret_env)
488
+ result = ret_hash_to_eval_result(ret_env, slot.instance_id)
489
+ invoke_async_block_safe(slot, result)
490
+ end
491
+
492
+ def invoke_async_block_safe(slot, result)
493
+ slot.block.call(result)
494
+ rescue StandardError => e
495
+ debug_log("eval_r_async block raised #{e.class}: #{e.message}")
496
+ raise if ENV['GALAAZ_ASYNC_RAISE_COMPLETION']
497
+
498
+ nil
499
+ end
500
+
501
+ def async_timeout_wait(call_id, slot, timeout)
502
+ sleep(timeout.to_f)
503
+ timed_out = false
504
+ @pending_mx.synchronize do
505
+ if @pending[call_id].equal?(slot)
506
+ @pending.delete(call_id)
507
+ timed_out = true
508
+ end
509
+ end
510
+ return unless timed_out
511
+
512
+ invoke_async_block_safe(slot, EvalResult.failure(TimeoutError.new(
513
+ format('no RET for %<id>s (timeout=%<timeout>.3fs)', id: call_id, timeout: timeout.to_f)
514
+ )))
515
+ end
516
+
517
+ def append_r_stderr(text)
518
+ @r_stderr_mx.synchronize do
519
+ @r_stderr << text.to_s
520
+ if @r_stderr.bytesize > @r_stderr_limit
521
+ @r_stderr = @r_stderr.byteslice(-@r_stderr_limit, @r_stderr_limit) || +''
522
+ end
523
+ end
524
+ end
525
+
526
+ def current_r_stderr
527
+ @r_stderr_mx.synchronize { @r_stderr.to_s.dup.strip }
528
+ end
529
+ end
530
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require_relative 'framing'
5
+
6
+ module NewBridge
7
+ # Minimal TCP client/server for framed byte payloads (Phase 0).
8
+ module TcpFramed
9
+ class Error < StandardError; end
10
+
11
+ module_function
12
+
13
+ # Yields bound port; server echoes one frame then closes.
14
+ def with_echo_server(host: '127.0.0.1')
15
+ server = TCPServer.new(host, 0)
16
+ port = server.addr[1]
17
+ thread = Thread.new do
18
+ client = server.accept
19
+ begin
20
+ payload = Framing.read_frame(client)
21
+ Framing.write_frame(client, payload)
22
+ ensure
23
+ client.close rescue nil
24
+ end
25
+ ensure
26
+ server.close rescue nil
27
+ end
28
+ yield port
29
+ ensure
30
+ thread&.join(5)
31
+ server&.close rescue nil
32
+ end
33
+
34
+ def send_receive(host, port, payload)
35
+ sock = TCPSocket.new(host, port)
36
+ begin
37
+ Framing.write_frame(sock, payload)
38
+ Framing.read_frame(sock)
39
+ ensure
40
+ sock.close
41
+ end
42
+ end
43
+ end
44
+ end
data/lib/new_bridge.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'new_bridge/envelope'
4
+ require_relative 'new_bridge/framing'
5
+ require_relative 'new_bridge/eval_result'
6
+ require_relative 'new_bridge/tcp_framed'
7
+ require_relative 'new_bridge/session_client'
8
+ require_relative 'new_bridge/instance_pool_client'
9
+ require_relative 'new_bridge/r_instance_manager'