@ledsun blog

無味の味は佳境に入らざればすなわち知れず

ruby.wasmのテストが失敗する

最近ruby.wasmのテストが失敗します。 ビルドはできてテストの実行時にエラーがおきます。 自分の環境だけかと思ったらGitHub Actionsでも失敗しています。 例えば Nightly release · ruby/ruby.wasm@8da2029 · GitHub です。

rake npm:ruby-head-wasm-wasi:checkを実行すると、次のエラーが起きます。

-- Control frame information -----------------------------------------------
c:0014 p:---- s:0071 e:000070 DUMMY  [FINISH]
c:0013 p:---- s:0068 e:000067 CFUNC  :require
c:0012 p:0023 s:0063 e:000062 METHOD <internal:/usr/local/lib/ruby/3.4.0+0/rubygems/core_ext/kernel_require.rb>:136
c:0011 p:0006 s:0057 e:000056 TOP    /bundle/gems/test-unit-3.6.2/lib/test/unit/collector/descendant.rb:1 [FINISH]
c:0010 p:---- s:0054 e:000053 CFUNC  :require
c:0009 p:0023 s:0049 e:000048 METHOD <internal:/usr/local/lib/ruby/3.4.0+0/rubygems/core_ext/kernel_require.rb>:136
c:0008 p:0006 s:0043 e:000042 BLOCK  /bundle/gems/test-unit-3.6.2/lib/test/unit/autorunner.rb:88
c:0007 p:0017 s:0038 e:000037 METHOD /bundle/gems/test-unit-3.6.2/lib/test/unit/autorunner.rb:454
c:0006 p:0057 s:0032 e:000031 METHOD /bundle/gems/test-unit-3.6.2/lib/test/unit/autorunner.rb:66
c:0005 p:0016 s:0023 E:0011ec EVAL   eval_async:4 [FINISH]
c:0004 p:---- s:0019 e:000018 CFUNC  :eval
c:0003 p:0014 s:0012 e:000010 BLOCK  /bundle/gems/js-2.6.2.dev/lib/js.rb:109
c:0002 p:0008 s:0008 e:000006 BLOCK  /bundle/gems/js-2.6.2.dev/lib/js.rb:120 [FINISH]
c:0001 p:---- s:0003 e:000002 DUMMY  [FINISH]

-- Ruby level backtrace information ----------------------------------------
/bundle/gems/js-2.6.2.dev/lib/js.rb:120:in 'block in __async'
/bundle/gems/js-2.6.2.dev/lib/js.rb:109:in 'block in __eval_async_rb'
/bundle/gems/js-2.6.2.dev/lib/js.rb:109:in 'eval'
eval_async:4:in '<compiled>'
/bundle/gems/test-unit-3.6.2/lib/test/unit/autorunner.rb:66:in 'run'
/bundle/gems/test-unit-3.6.2/lib/test/unit/autorunner.rb:454:in 'run'
/bundle/gems/test-unit-3.6.2/lib/test/unit/autorunner.rb:88:in 'block in <class:AutoRunner>'
<internal:/usr/local/lib/ruby/3.4.0+0/rubygems/core_ext/kernel_require.rb>:136:in 'require'
<internal:/usr/local/lib/ruby/3.4.0+0/rubygems/core_ext/kernel_require.rb>:136:in 'require'
/bundle/gems/test-unit-3.6.2/lib/test/unit/collector/descendant.rb:1:in '<top (required)>'
<internal:/usr/local/lib/ruby/3.4.0+0/rubygems/core_ext/kernel_require.rb>:136:in 'require'
<internal:/usr/local/lib/ruby/3.4.0+0/rubygems/core_ext/kernel_require.rb>:136:in 'require'

-- Threading information ---------------------------------------------------
Total ractor count: 1
Ruby thread count for this ractor: 1

-- Other runtime information -----------------------------------------------

* Loaded script: -e

* Loaded features:

    0 enumerator.so
    1 thread.rb
    2 fiber.so
    3 rational.so
    4 complex.so
    5 ruby2_keywords.rb
    6 encdb.so
    7 trans/transdb.so
    8 /usr/local/lib/ruby/3.4.0+0/wasm32-wasi/rbconfig.rb
    9 /usr/local/lib/ruby/3.4.0+0/rubygems/compatibility.rb
   10 /usr/local/lib/ruby/3.4.0+0/rubygems/defaults.rb
   11 /usr/local/lib/ruby/3.4.0+0/rubygems/deprecate.rb
   12 /usr/local/lib/ruby/3.4.0+0/rubygems/errors.rb
   13 /usr/local/lib/ruby/3.4.0+0/rubygems/target_rbconfig.rb
   14 /usr/local/lib/ruby/3.4.0+0/rubygems/unknown_command_spell_checker.rb
   15 /usr/local/lib/ruby/3.4.0+0/rubygems/exceptions.rb
   16 /usr/local/lib/ruby/3.4.0+0/rubygems/basic_specification.rb
   17 /usr/local/lib/ruby/3.4.0+0/rubygems/stub_specification.rb
   18 /usr/local/lib/ruby/3.4.0+0/rubygems/platform.rb
   19 /usr/local/lib/ruby/3.4.0+0/rubygems/specification_record.rb
   20 /usr/local/lib/ruby/3.4.0+0/rubygems/util/list.rb
   21 /usr/local/lib/ruby/3.4.0+0/rubygems/version.rb
   22 /usr/local/lib/ruby/3.4.0+0/rubygems/requirement.rb
   23 /usr/local/lib/ruby/3.4.0+0/rubygems/specification.rb
   24 /usr/local/lib/ruby/3.4.0+0/rubygems/util.rb
   25 /usr/local/lib/ruby/3.4.0+0/rubygems/dependency.rb
   26 /usr/local/lib/ruby/3.4.0+0/rubygems/core_ext/kernel_gem.rb
   27 monitor.so
   28 /usr/local/lib/ruby/3.4.0+0/monitor.rb
   29 /usr/local/lib/ruby/3.4.0+0/rubygems.rb
   30 /usr/local/lib/ruby/3.4.0+0/bundled_gems.rb
   31 /usr/local/lib/ruby/3.4.0+0/rubygems/path_support.rb
   32 /usr/local/lib/ruby/3.4.0+0/error_highlight/version.rb
   33 /usr/local/lib/ruby/3.4.0+0/error_highlight/base.rb
   34 /usr/local/lib/ruby/3.4.0+0/error_highlight/formatter.rb
   35 /usr/local/lib/ruby/3.4.0+0/error_highlight/core_ext.rb
   36 /usr/local/lib/ruby/3.4.0+0/error_highlight.rb
   37 /usr/local/lib/ruby/3.4.0+0/did_you_mean/version.rb
   38 /usr/local/lib/ruby/3.4.0+0/did_you_mean/core_ext/name_error.rb
   39 /usr/local/lib/ruby/3.4.0+0/did_you_mean/levenshtein.rb
   40 /usr/local/lib/ruby/3.4.0+0/did_you_mean/jaro_winkler.rb
   41 /usr/local/lib/ruby/3.4.0+0/did_you_mean/spell_checker.rb
   42 /usr/local/lib/ruby/3.4.0+0/did_you_mean/spell_checkers/name_error_checkers/class_name_checker.rb
   43 /usr/local/lib/ruby/3.4.0+0/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb
   44 /usr/local/lib/ruby/3.4.0+0/did_you_mean/spell_checkers/name_error_checkers.rb
   45 /usr/local/lib/ruby/3.4.0+0/did_you_mean/spell_checkers/method_name_checker.rb
   46 /usr/local/lib/ruby/3.4.0+0/did_you_mean/spell_checkers/key_error_checker.rb
   47 /usr/local/lib/ruby/3.4.0+0/did_you_mean/spell_checkers/null_checker.rb
   48 /usr/local/lib/ruby/3.4.0+0/did_you_mean/tree_spell_checker.rb
   49 /usr/local/lib/ruby/3.4.0+0/did_you_mean/spell_checkers/require_path_checker.rb
   50 /usr/local/lib/ruby/3.4.0+0/did_you_mean/spell_checkers/pattern_key_name_checker.rb
   51 /usr/local/lib/ruby/3.4.0+0/did_you_mean/formatter.rb
   52 /usr/local/lib/ruby/3.4.0+0/did_you_mean.rb
   53 /usr/local/lib/ruby/3.4.0+0/syntax_suggest/core_ext.rb
   54 /bundle/setup.rb
   55 js.so
   56 /bundle/gems/js-2.6.2.dev/lib/js/hash.rb
   57 /bundle/gems/js-2.6.2.dev/lib/js/array.rb
   58 /bundle/gems/js-2.6.2.dev/lib/js/nil_class.rb
   59 /bundle/gems/js-2.6.2.dev/lib/js.rb
   60 /bundle/gems/test-unit-3.6.2/lib/test/unit/warning.rb
   61 /bundle/gems/test-unit-3.6.2/lib/test/unit/attribute.rb
   62 /bundle/gems/test-unit-3.6.2/lib/test/unit/fixture.rb
   63 /bundle/gems/test-unit-3.6.2/lib/test/unit/exception-handler.rb
   64 /bundle/gems/test-unit-3.6.2/lib/test/unit/assertion-failed-error.rb
   65 /bundle/gems/power_assert-2.0.3/lib/power_assert/configuration.rb
   66 /bundle/gems/power_assert-2.0.3/lib/power_assert/enable_tracepoint_events.rb
   67 /bundle/gems/power_assert-2.0.3/lib/power_assert/inspector.rb
   68 ripper.so
   69 /usr/local/lib/ruby/3.4.0+0/ripper/core.rb
   70 /usr/local/lib/ruby/3.4.0+0/ripper/lexer.rb
   71 /usr/local/lib/ruby/3.4.0+0/ripper/filter.rb
   72 /usr/local/lib/ruby/3.4.0+0/ripper/sexp.rb
   73 /usr/local/lib/ruby/3.4.0+0/ripper.rb
   74 /bundle/gems/power_assert-2.0.3/lib/power_assert/parser.rb
   75 /bundle/gems/power_assert-2.0.3/lib/power_assert/context.rb
   76 /bundle/gems/power_assert-2.0.3/lib/power_assert/version.rb
   77 /bundle/gems/power_assert-2.0.3/lib/power_assert.rb
   78 /bundle/gems/test-unit-3.6.2/lib/test/unit/util/backtracefilter.rb
   79 /bundle/gems/test-unit-3.6.2/lib/test/unit/util/memory-usage.rb
   80 /bundle/gems/test-unit-3.6.2/lib/test/unit/util/method-owner-finder.rb
   81 /bundle/gems/test-unit-3.6.2/lib/test/unit/diff.rb
   82 /bundle/gems/test-unit-3.6.2/lib/test/unit/assertions.rb
   83 /bundle/gems/test-unit-3.6.2/lib/test/unit/failure.rb
   84 /bundle/gems/test-unit-3.6.2/lib/test/unit/error.rb
   85 /bundle/gems/test-unit-3.6.2/lib/test/unit/pending.rb
   86 /bundle/gems/test-unit-3.6.2/lib/test/unit/omission.rb
   87 /bundle/gems/test-unit-3.6.2/lib/test/unit/notification.rb
   88 /bundle/gems/test-unit-3.6.2/lib/test/unit/priority.rb
   89 /bundle/gems/test-unit-3.6.2/lib/test/unit/data-sets.rb
   90 /bundle/gems/test-unit-3.6.2/lib/test/unit/data.rb
   91 /bundle/gems/test-unit-3.6.2/lib/test/unit/testsuite.rb
   92 /bundle/gems/test-unit-3.6.2/lib/test/unit/test-suite-creator.rb
   93 /bundle/gems/test-unit-3.6.2/lib/test/unit/auto-runner-loader.rb
   94 /bundle/gems/test-unit-3.6.2/lib/test/unit/util/output.rb
   95 /bundle/gems/test-unit-3.6.2/lib/test/unit/testcase.rb
   96 /usr/local/lib/ruby/3.4.0+0/English.rb
   97 /usr/local/lib/ruby/3.4.0+0/optparse.rb
   98 /bundle/gems/test-unit-3.6.2/lib/test/unit/color.rb
   99 /bundle/gems/test-unit-3.6.2/lib/test/unit/color-scheme.rb
  100 /bundle/gems/test-unit-3.6.2/lib/test/unit/attribute-matcher.rb
  101 /bundle/gems/test-unit-3.6.2/lib/test/unit/runner/console.rb
  102 /bundle/gems/test-unit-3.6.2/lib/test/unit/runner/emacs.rb
  103 /bundle/gems/test-unit-3.6.2/lib/test/unit/runner/xml.rb
  104 /bundle/gems/test-unit-3.6.2/lib/test/unit/autorunner.rb
  105 /bundle/gems/test-unit-3.6.2/lib/test/unit.rb
  106 /bundle/gems/test-unit-3.6.2/lib/test-unit.rb
  107 /__root__/test/unit/test_js.rb
  108 /__root__/test/unit/test_object.rb
  109 /__root__/test/unit/test_object_wrap.rb
  110 /__root__/test/unit/test_error.rb
  111 /__root__/test/unit/test_async.rb
  112 /__root__/test/unit/test_float.rb
  113 /__root__/test/unit/test_proc.rb
  114 /__root__/test/unit/test_array.rb
  115 /__root__/test/unit/test_hash.rb
  116 /__root__/test/unit/test_nil_class.rb
  117 /usr/local/lib/ruby/3.4.0+0/singleton.rb
  118 /bundle/gems/js-2.6.2.dev/lib/js/require_remote/url_resolver.rb
  119 /bundle/gems/js-2.6.2.dev/lib/js/require_remote/evaluator.rb
  120 /bundle/gems/js-2.6.2.dev/lib/js/require_remote.rb
  121 /__root__/test/unit/require_remote/url_resolver.rb
  122 /__root__/test/test_unit.rb
  123 /bundle/gems/test-unit-3.6.2/lib/test/unit/ui/console/outputlevel.rb

wasm://wasm/0ba08026:1


RangeError: Maximum call stack size exceeded
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x37cbf4)
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x37d6a9)
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x38e034)
    at pm_compile_scope_node (wasm://wasm/0ba08026:wasm-function[4969]:0x3b350c)
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x38d579)
    at pm_iseq_compile_node (wasm://wasm/0ba08026:wasm-function[4911]:0x37cb18)
    at pm_iseq_new_with_opt (wasm://wasm/0ba08026:wasm-function[5154]:0x404880)
    at pm_compile_call (wasm://wasm/0ba08026:wasm-function[4973]:0x3b7588)
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x38f8d2)
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x388672)
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x38dfce)
    at pm_compile_scope_node (wasm://wasm/0ba08026:wasm-function[4969]:0x3b3dd3)
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x38d579)
    at pm_iseq_compile_node (wasm://wasm/0ba08026:wasm-function[4911]:0x37cb18)
    at pm_iseq_new_with_opt (wasm://wasm/0ba08026:wasm-function[5154]:0x404880)
    at pm_new_child_iseq (wasm://wasm/0ba08026:wasm-function[4938]:0x3a2b0a)
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x3829f7)
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x38dfce)
    at pm_compile_scope_node (wasm://wasm/0ba08026:wasm-function[4969]:0x3b492f)
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x38d579)
    at pm_iseq_compile_node (wasm://wasm/0ba08026:wasm-function[4911]:0x37cb18)
    at pm_iseq_new_with_opt (wasm://wasm/0ba08026:wasm-function[5154]:0x404880)
    at pm_new_child_iseq (wasm://wasm/0ba08026:wasm-function[4938]:0x3a2b0a)
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x389f21)
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x38e034)
    at pm_compile_scope_node (wasm://wasm/0ba08026:wasm-function[4969]:0x3b492f)
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x38d579)
    at pm_iseq_compile_node (wasm://wasm/0ba08026:wasm-function[4911]:0x37cb18)
    at pm_iseq_new_with_opt (wasm://wasm/0ba08026:wasm-function[5154]:0x404880)
    at pm_new_child_iseq (wasm://wasm/0ba08026:wasm-function[4938]:0x3a2b0a)
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x389f21)
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x38e034)
    at pm_compile_scope_node (wasm://wasm/0ba08026:wasm-function[4969]:0x3b492f)
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x38d579)
    at pm_iseq_compile_node (wasm://wasm/0ba08026:wasm-function[4911]:0x37cb18)
    at pm_iseq_new_with_opt (wasm://wasm/0ba08026:wasm-function[5154]:0x404880)
    at pm_new_child_iseq (wasm://wasm/0ba08026:wasm-function[4938]:0x3a2b0a)
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x389f21)
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x38e034)
    at pm_compile_scope_node (wasm://wasm/0ba08026:wasm-function[4969]:0x3b492f)
    at pm_compile_node (wasm://wasm/0ba08026:wasm-function[4912]:0x38d579)
    at pm_iseq_compile_node (wasm://wasm/0ba08026:wasm-function[4911]:0x37ca89)
    at pm_iseq_new_with_opt (wasm://wasm/0ba08026:wasm-function[5154]:0x404880)
    at pm_iseq_new_top (wasm://wasm/0ba08026:wasm-function[5153]:0x4045b3)
    at load_iseq_eval (wasm://wasm/0ba08026:wasm-function[5249]:0x419302)
    at require_internal (wasm://wasm/0ba08026:wasm-function[5253]:0x41a8d4)
    at rb_require_string (wasm://wasm/0ba08026:wasm-function[5252]:0x4199d2)
    at rb_f_require (wasm://wasm/0ba08026:wasm-function[5251]:0x41988e)
    at ractor_safe_call_cfunc_1 (wasm://wasm/0ba08026:wasm-function[8720]:0x6b16d4)
    at vm_call_cfunc_with_frame_ (wasm://wasm/0ba08026:wasm-function[8678]:0x6ac453)
    at vm_call_cfunc_other (wasm://wasm/0ba08026:wasm-function[8623]:0x6a3174)
    at vm_call_cfunc (wasm://wasm/0ba08026:wasm-function[8606]:0x69f11d)
    at vm_call_method_each_type (wasm://wasm/0ba08026:wasm-function[8602]:0x69d3bb)
    at vm_call_alias (wasm://wasm/0ba08026:wasm-function[8617]:0x6a1766)
    at vm_exec_core (wasm://wasm/0ba08026:wasm-function[8312]:0x676769)
    at vm_exec_bottom_main (wasm://wasm/0ba08026:wasm-function[8301]:0x669ef2)
    at rb_wasm_try_catch_loop_run (wasm://wasm/0ba08026:wasm-function[9626]:0x78169a)
    at rb_vm_exec (wasm://wasm/0ba08026:wasm-function[8286]:0x667ba8)
    at rb_iseq_eval (wasm://wasm/0ba08026:wasm-function[8546]:0x6967d8)
    at load_iseq_eval (wasm://wasm/0ba08026:wasm-function[5249]:0x419662)
    at require_internal (wasm://wasm/0ba08026:wasm-function[5253]:0x41a8d4)
    at rb_require_string (wasm://wasm/0ba08026:wasm-function[5252]:0x4199d2)
    at rb_f_require (wasm://wasm/0ba08026:wasm-function[5251]:0x41988e)
    at ractor_safe_call_cfunc_1 (wasm://wasm/0ba08026:wasm-function[8720]:0x6b16d4)
    at vm_call_cfunc_with_frame_ (wasm://wasm/0ba08026:wasm-function[8678]:0x6ac453)
    at vm_call_cfunc_other (wasm://wasm/0ba08026:wasm-function[8623]:0x6a3174)
    at vm_call_cfunc (wasm://wasm/0ba08026:wasm-function[8606]:0x69f11d)
    at vm_call_method_each_type (wasm://wasm/0ba08026:wasm-function[8602]:0x69d3bb)
    at vm_call_method_each_type (wasm://wasm/0ba08026:wasm-function[8602]:0x69e542)
    at vm_call_method (wasm://wasm/0ba08026:wasm-function[8276]:0x665525)
    at vm_call_general (wasm://wasm/0ba08026:wasm-function[8266]:0x663b52)
    at vm_exec_core (wasm://wasm/0ba08026:wasm-function[8312]:0x676769)
    at vm_exec_loop (wasm://wasm/0ba08026:wasm-function[8545]:0x696359)
    at vm_exec_bottom_rescue (wasm://wasm/0ba08026:wasm-function[8302]:0x66a09e)
    at rb_wasm_try_catch_loop_run (wasm://wasm/0ba08026:wasm-function[9626]:0x78169a)
    at rb_vm_exec (wasm://wasm/0ba08026:wasm-function[8286]:0x667ba8)
    at rb_f_eval (wasm://wasm/0ba08026:wasm-function[8462]:0x689f95)
    at ractor_safe_call_cfunc_m1 (wasm://wasm/0ba08026:wasm-function[8345]:0x67b051)
    at vm_call_cfunc_with_frame_ (wasm://wasm/0ba08026:wasm-function[8678]:0x6ac453)
    at vm_call_cfunc_other (wasm://wasm/0ba08026:wasm-function[8623]:0x6a3174)
    at vm_call_cfunc (wasm://wasm/0ba08026:wasm-function[8606]:0x69f11d)
    at vm_call_method_each_type (wasm://wasm/0ba08026:wasm-function[8602]:0x69d3bb)
    at vm_call_method (wasm://wasm/0ba08026:wasm-function[8276]:0x6652cb)
    at vm_call_general (wasm://wasm/0ba08026:wasm-function[8266]:0x663b52)
    at vm_exec_core (wasm://wasm/0ba08026:wasm-function[8312]:0x676769)
    at vm_exec_bottom_main (wasm://wasm/0ba08026:wasm-function[8301]:0x669ef2)
    at rb_wasm_try_catch_loop_run (wasm://wasm/0ba08026:wasm-function[9626]:0x78169a)
    at rb_vm_exec (wasm://wasm/0ba08026:wasm-function[8286]:0x667ba8)
    at vm_invoke_proc (wasm://wasm/0ba08026:wasm-function[8523]:0x6932ca)
    at rb_vm_invoke_proc (wasm://wasm/0ba08026:wasm-function[8521]:0x6924c8)
    at rb_fiber_start (wasm://wasm/0ba08026:wasm-function[2987]:0x23a19a)
    at fiber_entry (wasm://wasm/0ba08026:wasm-function[2998]:0x23c05c)
    at coroutine_trampoline (wasm://wasm/0ba08026:wasm-function[9565]:0x77ca49)
    at rb_abi_guest_rb_funcallv_protect (wasm://wasm/0ba08026:wasm-function[108]:0xe806)
    at __wasm_export_rb_abi_guest_rb_funcallv_protect (wasm://wasm/0ba08026:wasm-function[150]:0xfaa6)
    at LegacyBinding.rbFuncallvProtect (file:///home/ledsun/ruby.wasm/packages/npm-packages/ruby-wasm-wasi/dist/esm/bindgen/legacy/rb-abi-guest.js:91:171)
    at exports.<computed> [as rbFuncallvProtect] (file:///home/ledsun/ruby.wasm/packages/npm-packages/ruby-wasm-wasi/dist/esm/vm.js:148:44)
    at file:///home/ledsun/ruby.wasm/packages/npm-packages/ruby-wasm-wasi/dist/esm/vm.js:773:42
    at wrapRbOperation (file:///home/ledsun/ruby.wasm/packages/npm-packages/ruby-wasm-wasi/dist/esm/vm.js:746:16)
    at callRbMethod (file:///home/ledsun/ruby.wasm/packages/npm-packages/ruby-wasm-wasi/dist/esm/vm.js:772:12)
    at RbValue.call (file:///home/ledsun/ruby.wasm/packages/npm-packages/ruby-wasm-wasi/dist/esm/vm.js:555:28)
    at file:///home/ledsun/ruby.wasm/packages/npm-packages/ruby-wasm-wasi/dist/esm/vm.js:460:16
    at file:///home/ledsun/ruby.wasm/packages/npm-packages/ruby-wasm-wasi/dist/esm/vm.js:794:9
    at new Promise (<anonymous>)
    at newRbPromise (file:///home/ledsun/ruby.wasm/packages/npm-packages/ruby-wasm-wasi/dist/esm/vm.js:786:12)
    at RubyVM.evalAsync (file:///home/ledsun/ruby.wasm/packages/npm-packages/ruby-wasm-wasi/dist/esm/vm.js:459:16)
    at test (file:///home/ledsun/ruby.wasm/packages/npm-packages/ruby-wasm-wasi/tools/run-test-unit.mjs:171:12)
    at async main (file:///home/ledsun/ruby.wasm/packages/npm-packages/ruby-wasm-wasi/tools/run-test-unit.mjs:184:5)

Node.js v18.18.2
npm ERR! Lifecycle script `test:unit` failed with error:
npm ERR! Error: command failed
npm ERR!   in workspace: @ruby/wasm-wasi@2.6.2
npm ERR!   at location: /home/ledsun/ruby.wasm/packages/npm-packages/ruby-wasm-wasi
npm ERR! Lifecycle script `test` failed with error:
npm ERR! Error: command failed
npm ERR!   in workspace: @ruby/head-wasm-wasi@2.6.2
npm ERR!   at location: /home/ledsun/ruby.wasm/packages/npm-packages/ruby-head-wasm-wasi
rake aborted!
Command failed with status (1): [npm test]
/home/ledsun/ruby.wasm/rakelib/packaging.rake:144:in `block (4 levels) in <top (required)>'
Tasks: TOP => npm:ruby-head-wasm-wasi:check
(See full trace by running task with --trace)

テストが失敗したり、Rubyスクリプトが例外を起こしたりするわけではありません。 RubyVMがエラーで落ちています。 エラーの読み方がわかりません。

わかる範囲でメッセージを拾い読みしてみます。

RangeError: Maximum call stack size exceeded

コールスタックが枯渇しています。 想定を超える回数の再帰呼び出しが起きているようです。

    at ruby.pm_compile_node (wasm://wasm/ruby-05380122:wasm-function[2795]:0x215877)
    at ruby.pm_compile_node (wasm://wasm/ruby-05380122:wasm-function[2795]:0x21fb6f)
    at ruby.pm_compile_node (wasm://wasm/ruby-05380122:wasm-function[2795]:0x227953)
    at ruby.pm_compile_scope_node (wasm://wasm/ruby-05380122:wasm-function[2852]:0x24f55a)

Rubyスクリプトをパースしてたノードのコンパイル処理が再帰しているようです。 なるほど「抽象構文木をたどるときに再帰する」はありそうです。

先週Ruby内部のデフォルトパーサーがPrismに変更されました。 Switch the default parser from parse.y to Prism by kddnewton · Pull Request #11497 · ruby/ruby · GitHub これが関係するのでしょうか? 当てずっぽうです。

パーサーを変えたら動きは変わるのでしょうか? テストの実行時オプションを指定してみます。

diff --git a/packages/npm-packages/ruby-wasm-wasi/tools/run-test-unit.mjs b/packages/npm-packages/ruby-wasm-wasi/tools/run-test-unit.mjs
index 70d8ec0..5fbda64 100755
--- a/packages/npm-packages/ruby-wasm-wasi/tools/run-test-unit.mjs
+++ b/packages/npm-packages/ruby-wasm-wasi/tools/run-test-unit.mjs
@@ -71,6 +71,7 @@ const instantiateNodeWasi = async (rootTestFile) => {

   const { vm } = await RubyVM.instantiateModule({
     module: rubyModule, wasip1: wasi,
+    args: ["ruby.wasm", "-EUTF-8", "-e_=0", '--parser=parse.y']
   })
   return { vm, wasi };
 };

途中まで動くようになりました。

npm test

> @ruby/head-wasm-wasi@2.6.2 test
> RUBY_NPM_PACKAGE_ROOT=../ruby-head-wasm-wasi npm -C ../ruby-wasm-wasi run test:run


> @ruby/wasm-wasi@2.6.2 test:run
> npm run test:unit && npm run test:vitest -- --run && npm run test:e2e


> @ruby/wasm-wasi@2.6.2 test:unit
> ./tools/run-test-unit.mjs

(node:112273) ExperimentalWarning: WASI is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
Loaded suite -e
Started
Finished in 0.23249 seconds.
-------------------------------------------------------------------------------
64 tests, 217 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
-------------------------------------------------------------------------------
275.28 tests/s, 933.37 assertions/s
-- Control frame information -----------------------------------------------
c:0026 p:---- s:0122 e:000121 DUMMY  [FINISH]
c:0025 p:---- s:0119 e:000118 CFUNC  :require

本当にパーサー変更の影響がありそうです。

RubyでBasicObjectを継承しつつ、Objectの一部のメソッドをつかうには

RubyではほとんどのクラスはObjectクラスを継承しています。 極まれにObjectクラスの機能が多すぎることがあります。 そういうときはBasicObjectを継承します。 しかし、Objectの一部の便利なメソッドだけは使いたい。 そういうわがままな要望があります。

そんなときにActiveRecord::Promiseの実装が参考になります。

https://github.com/rails/rails/blob/5c0b7496ab32c25c80f6d1bdc8b32ec6f75ce1e4/activerecord/lib/active_record/promise.rb#L40-L42

    [:class, :respond_to?, :is_a?].each do |method|
      define_method(method, ::Object.instance_method(method))
    end

おまけ

「なんでActiveRecord::PromiseはBasicObjectを継承しているのだろう?」と思ってコミット履歴を見てみました。

PoC: Active Record API for general async queries · rails/rails@03e435b · GitHub

byrootさんが最初にコミットした時点でBasicObjectを継承していました。 なにそれ?すごい。

Async::WebSocket gemの素振り

ruby.wasmからのWebSocket - @ledsun blog では、faye-websocket-rubyを使いました。 このWebSocketサーバーを GitHub - socketry/async-websocket: Asynchronous WebSocket client and server, supporting HTTP/1 and HTTP/2 for Ruby. に変えてみましょう。

環境構築

bundle init
bundle add falcon async-websocket

WebSocketサーバーの作成

https://github.com/socketry/async-websocket/blob/534cad73595292b82b88de3b66d10773106c254d/examples/rack/config.ru にあるサンプルを参考にします。

#!/usr/bin/env -S falcon serve --bind http://127.0.0.1:7070 --count 1 -c
# frozen_string_literal: true

require 'async/websocket/adapters/rack'

app = lambda do |env|
    response = Async::WebSocket::Adapters::Rack.open(env) do |connection|
        while message = connection.read
            connection.write message
        end
    end or [404, {}, []]
end

run app

ruby.wasmのWebSocketクライアントを含んだindex.htmlを返せるようにします。

require 'async/websocket/adapters/rack'

app = lambda do |env|
  response = Async::WebSocket::Adapters::Rack.open(env) do |connection|
    while message = connection.read
       connection.write message
     end
  end or [200, {'Content-Type' => 'text/html'}, [File.read('index.html')]]
end

run app

index.htmlは前回使った物をそのまま使います。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<script src="https://cdn.jsdelivr.net/npm/@ruby/3.3-wasm-wasi@2.6.2/dist/browser.script.iife.js"></script>
<script type="text/ruby">
  require "js"

  ws = JS.global[:WebSocket].new("ws://localhost:9292")
  ws[:onopen] = -> (event) {
    ws.call(:send, ["Hello, world! from Ruby"])
  }
  ws[:onmessage] = ->(event) {
    p event[:data]
  }
</script>

</html>

動作確認

bundle exec falcon serve --bind "http://localhost:9292"コマンドで、WebSocketサーバーを起動します。

ledsun@MSI:~/d/async_websocket►bundle exec falcon serve --bind "http://localhost:9292"
  0.0s     info: Falcon::Command::Serve [oid=0xf50] [ec=0xf78] [pid=135904] [2024-08-28 16:22:36 +0900]
               | Falcon v0.47.10 taking flight! Using Async::Container::Forked {:count=>8, :restart=>true}.
               | - Binding to: #<Falcon::Endpoint http://localhost:9292/ {}>
               | - To terminate: Ctrl-C or kill 135904
               | - To reload configuration: kill -HUP 135904
 0.03s     info: Falcon::Service::Server [oid=0xfc8] [ec=0xf78] [pid=135904] [2024-08-28 16:22:36 +0900]
               | Starting server on #<Falcon::Endpoint http://localhost:9292/ {}>

ブラウザで http://localhost:9292/ を開きます。 開発コンソールでWebSocketの通信を確認します。

WebSocketでメッセージを送受信している内容を確認してるスクリーンショット

成功しています。 async-websocketの素振りをした上に、Falconデビューを果たしました。

おまけ

ブラウザを閉じるとWebSocketサーバーで次のようなエラーが表示されます。

    4m     warn: Async::Task: Reading HTTP/1.1 requests for Async::HTTP::Protocol::HTTP1::Server. [oid=0x1180] [ec=0x11a8] [pid=136177] [2024-08-28 16:50:08 +0900]
               | Task may have ended with unhandled exception.
               |   Protocol::WebSocket::ClosedError: Protocol::WebSocket::ClosedError
               |   → /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/gems/3.4.0+0/gems/protocol-websocket-0.15.0/lib/protocol/websocket/connection.rb:165 in 'Protocol::WebSocket::Connection#receive_close'
               |     /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/gems/3.4.0+0/gems/protocol-websocket-0.15.0/lib/protocol/websocket/close_frame.rb:64 in 'Protocol::WebSocket::CloseFrame#apply'
               |     /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/gems/3.4.0+0/gems/protocol-websocket-0.15.0/lib/protocol/websocket/connection.rb:111 in 'Protocol::WebSocket::Connection#read_frame'
               |     /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/gems/3.4.0+0/gems/protocol-websocket-0.15.0/lib/protocol/websocket/connection.rb:272 in 'Protocol::WebSocket::Connection#read'
               |     config.ru:8 in 'block (4 levels) in <top (required)>'
               |     /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/gems/3.4.0+0/gems/async-websocket-0.28.0/lib/async/websocket/adapters/http.rb:41 in 'block in Async::WebSocket::Adapters::HTTP.open'
               |     /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/gems/3.4.0+0/gems/async-http-0.71.0/lib/async/http/body/hijack.rb:39 in 'Async::HTTP::Body::Hijack#call'
               |     /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/gems/3.4.0+0/gems/protocol-rack-0.6.0/lib/protocol/rack/body/streaming.rb:56 in 'Method#call'
               |     /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/gems/3.4.0+0/gems/protocol-rack-0.6.0/lib/protocol/rack/body/streaming.rb:56 in 'Protocol::Rack::Body::Streaming#call'
               |     /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/gems/3.4.0+0/gems/async-http-0.71.0/lib/async/http/protocol/http1/server.rb:72 in 'block in Async::HTTP::Protocol::HTTP1::Server#each'
               |     /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/gems/3.4.0+0/gems/async-2.16.1/lib/async/task.rb:318 in 'Async::Task#defer_stop'
               |     /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/gems/3.4.0+0/gems/async-http-0.71.0/lib/async/http/protocol/http1/server.rb:57 in 'Async::HTTP::Protocol::HTTP1::Server#each'
               |     /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/gems/3.4.0+0/gems/async-http-0.71.0/lib/async/http/server.rb:50 in 'Async::HTTP::Server#accept'
               |     /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/gems/3.4.0+0/gems/io-endpoint-0.13.1/lib/io/endpoint/wrapper.rb:182 in 'block (2 levels) in IO::Endpoint::Wrapper#accept'
               |     /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/gems/3.4.0+0/gems/async-2.16.1/lib/async/task.rb:197 in 'block in Async::Task#run'
               |     /home/ledsun/.rbenv/versions/3.4-dev/lib/ruby/gems/3.4.0+0/gems/async-2.16.1/lib/async/task.rb:422 in 'block in Async::Task#schedule'

WebSocketコネクションのcloseイベントをハンドリングしないと行けなさそうです。

PyCallでsendメソッドを直接呼び出せるのか?

ruby.wasmからのWebSocket - @ledsun blog にてruby.wasmからJavaScriptWebSocket.sendを直接呼び出せませんでした。

Rubyから他言語のオブジェクトのメソッドを呼び出す先輩にはGitHub - mrkn/pycall.rb: Calling Python functions from the Ruby languageがあります。 PyCallではどのような仕様なっているのでしょうか? PyCallからPythonのオブジェクトのsendメソッドを直接呼び出せるのでしょうか?

結論。直接は呼び出せません。呼び出す時はPyCall.getattrを使います。

sendメソッドを直接呼び出すとmethod_missing

実行環境を準備します。

bundle init
bundle add pycall

次のRubyスクリプトを用意します。

require 'pycall'
client = PyCall.import_module 'http.client'
conn = client.HTTPConnection('www.example.com')
conn.send('GET / HTTP/1.0\n\n')

Pythonの標準ライブラリーに含まれる http.client --- HTTP プロトコルクライアント — Python 3.12.5 ドキュメント にsendメソッドがあります。 これで試してみます。

実行します。

> ruby test_send.rb
/home/ledsun/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/pycall-1.5.2/lib/pycall/pyobject_wrapper.rb:54:in `method_missing': undefined method `GET / HTTP/1.0\n\n' for class <class 'http.client.HTTPConnection'> (NoMethodError)
        from test_send.rb:5:in `<main>'

method_missing': undefined methodGET / HTTP/1.0\n\n'

これはRubyObject#send メソッドが呼ばれた時のエラーです。 エラーが起きた箇所のソースコードを見てみます。

https://github.com/mrkn/pycall.rb/blob/master/lib/pycall/pyobject_wrapper.rb#L35-L53

def method_missing(name, *args)
  name_str = name.to_s if name.kind_of?(Symbol)
  name_str.chop! if name_str.end_with?('=')
  case name
  when *OPERATOR_METHOD_NAMES.keys
    op_name = OPERATOR_METHOD_NAMES[name]
    if LibPython::Helpers.hasattr?(__pyptr__, op_name)
      LibPython::Helpers.define_wrapper_method(self, op_name)
      singleton_class.__send__(:alias_method, name, op_name)
      return self.__send__(name, *args)
    end
  else
    if LibPython::Helpers.hasattr?(__pyptr__, name_str)
      LibPython::Helpers.define_wrapper_method(self, name)
      return self.__send__(name, *args)
    end
  end
  super # << ここでエラーが起きています。
end

method_missingが呼ばれています。 これはObject#sendの引数で'GET / HTTP/1.0\n\n'が実行されて起きています。

試しにmethod_missingの先頭にデバッグ出力を足してみます。

def method_missing(name, *args)
  p "method_missing: #{name}"
  name_str = name.to_s if name.kind_of?(Symbol)
  ...

動作確認用のRubyスクリプトを実行します。

►ruby test_send.rb
"method_missing: path"
"method_missing: append"
"method_missing: dict"
"method_missing: list"
"method_missing: slice"
"method_missing: HTTPConnection"
"method_missing: GET / HTTP/1.0\\n\\n"
"method_missing: repr"
/home/ledsun/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/pycall-1.5.2/lib/pycall/pyobject_wrapper.rb:53:in `method_missing': undefined method `GET / HTTP/1.0\n\n' for class <class 'http.client.HTTPConnection'> (NoMethodError)
        from test_send.rb:5:in `<main>'

やはり、引数にsendは来ていません。

追記:sendを呼び出すにはPyCall.getattrを使う

コメント欄にて id:mrkn さんからsendメソッドの呼び出し方を教えてもらいました。 さらに、他の間違いも教えてもらっています。 修正したスクリプトが次です。

require 'pycall'
client = PyCall.import_module 'http.client'
conn = client.HTTPConnection.new('www.example.com')
PyCall.getattr(conn, 'send').("GET / HTTP/1.0\r\n\r\n".b)

これを実行するとエラーは起きません。

►ruby test_send.rb

HTTPConnection.sendの戻り値は特に処理していないので、表示は何もされません。

ActiveRecordでsendカラムは使えるのか?

ActiveRecordで動的に生成されるsendメソッドとObject#sendは衝突したらどうなるのか?という話です。 結論は、つかえます。

実際に試してみます。 Railsアプリケーションを準備します。

rbenv local 3.3.4
bundle init
bundle add rails
bundle exec rails new .
bundle update
bin/rails g model WebSocket send:string
bin/rails db:migrate

動かしてみましょう。

ledsun@MSI:~/rails_7_1►bin/rails c
Loading development environment (Rails 7.2.0)
rails71(dev)> WebSocket.new.send
=> nil
rails71(dev)> WebSocket.new.send(:send)
(rails71):2:in `<main>': wrong number of arguments (given 1, expected 0) (ArgumentError)

sendという引数0個のメソッドがあります。 sendカラムに対応したメソッドです。 Object#sendは呼べません。

せっかくなので、sendメソッドを定義してる場所を探しましょう。

rails71(dev)> WebSocket.new.method(:send).source_location
=> ["/home/ledsun/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activemodel-7.2.0/lib/active_model/attribute_methods.rb", 269]

rails/activemodel/lib/active_model/attribute_methods.rb at main · rails/rails · GitHub

こんな感じの関数が定義されています。

def define_attribute_methods(*attr_names)
  ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
    attr_names.flatten.each do |attr_name|
      define_attribute_method(attr_name, _owner: owner)
      aliases_by_attribute_name[attr_name.to_s].each do |aliased_name|
        generate_alias_attribute_methods owner, aliased_name, attr_name
      end
    end
  end
end

binding.breakをいれて止めてみましょう。

ledsun@MSI:~/rails_7_1►bin/rails c
Loading development environment (Rails 7.2.0)
rails71(dev)> WebSocket.new
[263, 272] in ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activemodel-7.2.0/lib/active_model/attribute_methods.rb
   263|       #       def clear_attribute(attr)
   264|       #         send("#{attr}=", nil)
   265|       #       end
   266|       #   end
   267|       def define_attribute_methods(*attr_names)
=> 268|         binding.break
   269|         ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
   270|           attr_names.flatten.each do |attr_name|
   271|             define_attribute_method(attr_name, _owner: owner)
   272|             aliases_by_attribute_name[attr_name.to_s].each do |aliased_name|
=>#0    ActiveModel::AttributeMethods::ClassMethods#define_attribute_methods(attr_names=[["id", "send", "created_at", "updated_at...) at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activemodel-7.2.0/lib/active_model/attribute_methods.rb:268
  #1    block in define_attribute_methods at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/attribute_methods.rb:115
  # and 46 frames (use `bt' command for all frames)

btコマンドでバックトレースを見てみます。

(rdbg) bt    # backtrace command
=>#0    ActiveModel::AttributeMethods::ClassMethods#define_attribute_methods(attr_names=[["id", "send", "created_at", "updated_at...) at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activemodel-7.2.0/lib/active_model/attribute_methods.rb:268
  #1    block in define_attribute_methods at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/attribute_methods.rb:115
  #2    [C] Monitor#synchronize at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/attribute_methods.rb:108
  #3    ActiveRecord::AttributeMethods::ClassMethods#define_attribute_methods at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/attribute_methods.rb:108
  #4    ActiveRecord::Core#init_internals at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/core.rb:789
  #5    ActiveRecord::Persistence#init_internals at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/persistence.rb:817
  #6    ActiveModel::Dirty#init_internals at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activemodel-7.2.0/lib/active_model/dirty.rb:366
  #7    ActiveRecord::AttributeMethods::Dirty#init_internals at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/attribute_methods/dirty.rb:197
  #8    ActiveRecord::Timestamp#init_internals at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/timestamp.rb:103
  #9    ActiveRecord::Associations#init_internals at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/associations.rb:76
  #10   ActiveRecord::AutosaveAssociation#init_internals at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/autosave_association.rb:279
  #11   ActiveRecord::Transactions#init_internals at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/transactions.rb:434
  #12   ActiveRecord::TouchLater#init_internals at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/touch_later.rb:50
  #13   ActiveRecord::Core#initialize(attributes=nil) at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/core.rb:455
  #14   [C] Class#new at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/inheritance.rb:76
  #15   ActiveRecord::Inheritance::ClassMethods#new(attributes=nil, block=nil) at ~/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/activerecord-7.2.0/lib/active_record/inheritance.rb:76

newメソッドまで辿れます。 ActiveRecordを継承したクラスを最初にnewしたときにsendメソッドが定義されるようです。

ここまでシュッと試せるRubyRailsの開発者サポートの充実っぷりがすごいと思いました。

ruby.wasmからのWebSocket

WebSocketを使いたい理由

ruby.wasmでdRubyをやりたいです。 dRubyではサーバーとクライアントで双方からリクエストをおくります。 HTTPはクライアントからサーバーへの一方向のリクエストを想定しているので不十分です。

Server Sent Eventsを使う手もあります。 WebSocketの方がServer Sent Eventsより新しいので、WebSocketを使います。 なんとなくWebSocketの方が効率が良さそうとか、APIがこなれてそうとか思っていますが、Server Sent Eventsを使った経験がないので憶測です。

faye-websocket-rubyを使ってWebSocketサーバーを用意する

まずWebSocketのサーバーを用意します。 RubyでWebSocketを使うには GitHub - faye/faye-websocket-ruby: Standards-compliant WebSocket client and server が使えます。 GitHub - socketry/async-websocket: Asynchronous WebSocket client and server, supporting HTTP/1 and HTTP/2 for Ruby.もあります。 なんとなく新しい方がいいなら、async-websocketの方が良い気もします。 今回は、過去に使ったことがあるfaye-websocket-rubyを使います。

faye-websocket-rubyをWebSocketサーバーとして使う #Ruby - Qiita を読むとWebSocketサーバーの起動方法が書いてあります。 サーバーはこのサンプルをそのまま使います。

thin start -R config.ru -p 9292

で起動します。

const ws = new WebSocket('ws://localhost:9292')
ws.onmessage = console.log
ws.send('hello')

でブラウザからサーバーにリクエストを送ります。 今回はこれをruby.wasmで書き換えます。

WebSocketサーバーからindex.htmlを返す

ruby.wasmの動作確認をしやすくするために、index.hmtlを返すように改造します。

    # Normal HTTP request
    [200, {'Content-Type' => 'text/html'}, [File.read('index.html')]]

index.html

次のようなHTMLを用意すると、ruby.wasmでWebSocketリクエストを送信できることが確認できます。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<script src="https://cdn.jsdelivr.net/npm/@ruby/3.3-wasm-wasi@2.6.2/dist/browser.script.iife.js"></script>
<script type="text/ruby">
  require "js"

  class JS::Object
    undef_method :send
  end

  ws = JS.global[:WebSocket].new("ws://localhost:9292")
  ws[:onopen] = -> (event) {
    ws.send("Hello, world! from Ruby")
  }
  ws[:onmessage] = ->(event) {
    p event[:data]
  }
</script>

</html>

WebSocketサーバーを起動して http://localhost:9292 を開くと開発コンソールに次のような表示がされます。

開発コンソールにWebSocketリクエストをおくったログが表示されたスクリーンショット

動作説明

このWebSocketサーバーは送られてきたメッセージをエコーバックしてきます。 ws.send("Hello, world! from Ruby")で送ったメッセージをエコーバックします。

  ws[:onmessage] = ->(event) {
    p event[:data]
  }

でサーバーから送り返されたメッセージをコンソールに出力しています。

ソースコード解説

ruby.wasmでWebSocketリクエストを送るときに興味深い点があります。

  class JS::Object
    undef_method :send
  end

です。 これがないと次のようなエラーがおきます。

JS::Objectのsendメソッドが定義されているときに起きるエラーのスクリーンショット

Error: /bundle/gems/js-2.6.2/lib/js.rb:184:in method_missing': undefined methodHello, world! from Ruby' for an instance of JS::Object (NoMethodError)

Hello, world! from Rubyメソッドが無いので怒られています。 そんなメソッドあるわけないので、それはそうです。

ではなぜ、Hello, world! from Rubyという変わった名前のメソッドが実行されるのでしょうか? WebSocket: send() メソッド - Web API | MDN の代わりに Object#send (Ruby 3.3 リファレンスマニュアル) が呼び出されています。

JS::ObjectJavaScriptメソッドの呼び出しは、 BasicObject#method_missing (Ruby 3.3 リファレンスマニュアル) で実装されています。 JS::Objectsendメソッドが定義されていたらRubysendメソッドが呼び出されます。 全てのRubyオブジェクトにはsendメソッドが定義されています。 Object#send (Ruby 3.3 リファレンスマニュアル) です。 当然、このsendメソッドが呼び出されます。

というわけでWebSocketのsendメソッドを呼び出すには

  class JS::Object
    undef_method :send
  end

が、必要なのでした。

感想

JavaScriptでWebSocketを書いたときはsendメソッドで違和感がありませんでした。 Rubyistは、特にライブラリーの時は、本能的にsendメソッドを避けるのです。 それでRuby脳でもsendに違和感を感じませんでした。

ruby.wasmにすると名前が衝突します。 JavaScript脳でもRuby脳でも、まったく気がつかない現象なので、面白かったです。

dRuby in the browserのネタ集め

ruby.wasmでRuby in the browserが可能になりました。 次にブラウザとサーバーで通信したいです。 「fetch APIを使って、REST APIなサーバーと通信する」じゃ面白くないですよね。 せっかくのRubyなので、drubyを使って分散Rubyしたいです。

GitHub - ruby/drb: Distributed object system for Ruby です。 ところがdrbがデフォルトでサポートしているトランスポートプロトコルTCPUNIXソケットです。 ブラウザから直接呼べません。

HTTPがサポートされていたら、楽ちんです。 eos-india/misc/drb_http.rb at 4a3f9413461b62b221a812bfdfc42071941b40fa · abhisek/eos-india · GitHub に、それぽいコードがあります。 残念ながら、open_serverメソッドが実装されていません。

ていうかdRubyってサーバーからクライアントにPushしているような気がします。 HTTPで実装できるのでしょうか?

n月刊ラムダノート Vol.2, No.1(2020)(電子書籍のみ) – 技術書出版と販売のラムダノートdRubyとSSE(Server Sent Events)との組み合わせが乗っているようです。 そうですね、SSEを使えばサーバーからPushできます。

読んでみると、dRubyで書き換えたサーバーの情報をブラウザに伝えるためにSSEを使っています。 そうではないのです。 僕がやりたいのは、SSEの上にdRubyを乗せたいのです。

ちょっと視点を変えてみます。 Ruby in the browserには先輩がいます。 Opalです。 youchanさんによるdRuby in the browserがあります。 GitHub - youchan/opal-drb: A dRuby implementation for Opal. とりあずこれをruby.wasmに置き換えるのが、最初の一歩に良さそうです。

トランスポートプロトコルには、WebSocketを使っているようです。 GitHub - youchan/drb-websocket: A druby protocol of WebSocket はfaye-websocketを使っているようです。 もし新しく作り直すならGitHub - socketry/async-websocket: Asynchronous WebSocket client and server, supporting HTTP/1 and HTTP/2 for Ruby. を、使う方がかっこいいかもしれません。

大人数のデイリーミーティング

今、チームのデイリーミーティングの参加者が14人です。 アジャイルソフトウェア開発の文脈の朝会としては人数が多めに感じます。 それもそのはず、4つの開発チームで合同でやっています。 このチームは発足当初は1開発チームでした。 売上の都合で、あれよあれよと分裂しました。 現在は4開発チームです。

開発チームが分かれたとき、デイリーミーティングをやめる選択もありました。 やめませんでした。 その理由は「咳さんのチームは10人越えててもデイリーミーティングが上手く回っている」と聞いていたからです。 実際、人数が多いので「ちょっと発言しにくいかな?」と感じるときもありますが、おおむね上手く回っているように感じます。

その後もずっと続けています。 なんで続けているのか上手く言語化出来ていませんでした。 リードについて - @m_seki の を見て、なぜなのか思い出しました。

メンバーの誰かがチームを変化させることがあります。いつもの 1 日 のちょっとした場面で、チームの安定を壊すような「言いにくいこと」を言うのです。 

たまに大きく変化するより、毎日ちょっとずつ変化してる方が安定します。 「人間が直立しているとき、動いていないつもりで揺れている」のに似ています。 開発チームがわかれていると、僕からはチームと開発チームの隙間にある何が見えません。 リーダー役の一部の代表者だけで「言いにくいこと」に、すばやく気がつくのは難しいです。 リーダーが「言いにくいこと」気がついてから変化させようと変化が大きくなります。 直立でなくて、ステップを踏みだします。

あらためて、僕のチーム運営のロールモデルは「咳さんのチーム」なのでした。

いつも使っているディレクトリでもruby.wasmをビルドする

ruby.wasmがビルドできる - @ledsun blog で得た知見を元に、いつもの開発環境でビルドできるようできるでしょうか?

Rubyのバージョンを安定版に

rbenv local 3.3.2

submodule を recursiveに初期化

git submodule update --init --recursive

多分これが重要です。

依存パッケージを最新に

bin/setup
rustup update
rustup target add wasm32-wasi

ruby_wasm をリビルド

bundle exec rake clean
bundle exec rake compile

ruby.wasm をリビルド

rake build:clean
rake build:download_prebuilt
rake npm:ruby-head-wasm-wasi

成功しました!

ruby.wasmがビルドできる

ruby.wasmがビルドできない - @ledsun blogをリベンジします。 cloneからやり直してみます。

git clone git@github.com:ruby/ruby.wasm.git --recursive ruby.wasm_2
cd ruby.wasm_2
bin/setup

ruby_wasmをビルドします。

rustup update
rustup target add wasm32-wasi
bundle exec rake compile

ここで失敗します。

warning: build failed, waiting for other jobs to finish...
gmake: *** [Makefile:569: /home/ledsun/ruby.wasm_2/target/release/libruby_wasm.so] Error 101
rake aborted!
Command failed with status (2): [/usr/bin/gmake]
/home/ledsun/.rbenv/versions/3.4-dev/bin/bundle:25:in 'Kernel#load'
/home/ledsun/.rbenv/versions/3.4-dev/bin/bundle:25:in '<main>'
Tasks: TOP => compile => compile:x86_64-linux => compile:ruby_wasm:x86_64-linux => copy:ruby_wasm:x86_64-linux:3.4.0 => tmp/x86_64-linux/ruby_wasm/3.4.0/ruby_wasm.so
(See full trace by running task with --trace)

そういえばglobalに設定してるRubyのバージョンが安定版じゃないのでした。

rbenv local 3.3.2
bin/setup
bundle exec rake compile

成功しました。

rake build:download_prebuilt
rake npm:ruby-head-wasm-wasi

成功しました!

rake npm:ruby-head-wasm-wasi:check

も成功します。

アーキテクトの教科書

大吉祥寺.pm - connpass にて著者の米久保 剛さんの講演を見ました。 何かの縁だと思って買いました。

どんな本?

これからアーキテクトを目指す人向けに「アーキテクトにはこういう分野のスキルを身につける必要がある」と示してくれる本でした。 ITエンジニアにとっての基本情報技術者試験のような存在です。 基本情報技術者試験との違いは、上流工程に重点を置いて抽象度の高い内容を扱っています。 例えば、2進数やコンピュータの仕組みのようなローレベルな話は出てきません。

読むと何が得られそう?

全体を概観する本です。 個々のスキルの獲得と支援する本ではありません。 若手が読めば、これから伸ばして行きたいスキルが見つかるかもしれません。 ベテランが読めば、自分が気づいていなかった、あるいは軽視していた視点が見つかるかもしれません。

アーキテクチャ設計では「非機能要求を機能要求より重視する」そうです。 この視点は私になかったものです。 どちらかというと、機能要求が主で、非機能要求は添え物と考えていました。 「非機能要求は添え物ではあるけど、見落とすとあとで痛め見るから忘れないように気をつけるもの」として意識していました。 これはおそらく僕の意識が、アーキテクチャ設計よりアプリケーション設計に偏っているために起きているように思います。 このように、普段の開発の中で知らず知らずのうちに軽視しているものに気がつけるかもしれません。

個人的な面白ポイント

あと、個人的に面白かったのは 4+1 architectural view model - Wikipedia の名前です。 アーキテクチャを見る視点を次の5つで使い分けるアイデアです。

  1. 論理ビュー
  2. プロセスビュー
  3. 開発ビュー
  4. 物理ビュー
  5. シナリオ

なんとなくやっていた行為にちゃんと名前がついていること。 それが1995年に提唱されていたことが面白かったです。

お隣の書評

ruby.wasmがビルドできない

ruby.wasmのビルドが通らなくなったので、環境を最新にします。

cd vendor/jco/
git reset --hard
cd ../..
git submodule update

します。 この手順が要るかはよくわかっていません。 雰囲気でやっています。

bin/setup に成功するまで

bin/setupを実行すると次のエラーが出ます。

error[E0463]: can't find crate for `core`
  |
  = note: the `wasm32-wasip1` target may not be installed
  = help: consider downloading the target with `rustup target add wasm32-wasip1`

For more information about this error, try `rustc --explain E0463`.
error: could not compile `cfg-if` (lib) due to 1 previous error
warning: build failed, waiting for other jobs to finish...
Error: command exited with non-zero code `cargo build --workspace --target wasm32-wasip1`: 101

次のコマンドを実行します。

rustup update
rustup target add wasm32-wasip1`

bin/setupが成功するようになります。

rake npm:ruby-head-wasm-wasi には成功しない

rake npm:ruby-head-wasm-wasiを実行すると失敗します。

Rubyのビルド済み環境にリセットします。 rake build:download_prebuiltを実行します。

rake npm:ruby-head-wasm-wasiを実行すると失敗します。 次のようなエラーが出ます。

  ==> make -j8 install DESTDIR\=/home/ledsun/ruby.wasm/build/wasm32-unknown-wasip1-pic/ruby-head-wasm32-unknown-wasip1-pic-full/install
clang-16clang-16clang-16: : : warning: warning: warning: argument unused during compilation: '-shared' [-Wunused-command-line-argument]
argument unused during compilation: '-shared' [-Wunused-command-line-argument]argument unused during compilation: '-shared' [-Wunused-command-line-argument]

clang-16: warning: argument unused during compilation: '-shared' [-Wunused-command-line-argument]
wasm-ld: error: enc/cp949.o: undefined symbol: rb_enc_register
...
rake aborted!
Command failed with status (1): [RUBY_WASM_ROOT=/home/ledsun/ruby.wasm RUBY_WASM_EXPERIMENTAL_DYNAMIC_LINKING=1 bundle exec /home/ledsun/ruby.wasm/exe/rbwasm build --ruby-version head --target wasm32-unknown-wasip1 --build-profile full -o /home/ledsun/ruby.wasm/packages/npm-packages/ruby-head-wasm-wasi/tmp/ruby.component.wasm]
/home/ledsun/ruby.wasm/rakelib/packaging.rake:83:in 'block (5 levels) in <top (required)>'
/home/ledsun/ruby.wasm/rakelib/packaging.rake:69:in 'Dir.chdir'
/home/ledsun/ruby.wasm/rakelib/packaging.rake:69:in 'block (4 levels) in <top (required)>'
/home/ledsun/ruby.wasm/rakelib/packaging.rake:123:in 'block (3 levels) in <top (required)>'
Tasks: TOP => npm:ruby-head-wasm-wasi:build => npm:ruby-head-wasm-wasi:ruby
(See full trace by running task with --trace)

どうも RUBY_WASM_ROOT=/home/ledsun/ruby.wasm RUBY_WASM_EXPERIMENTAL_DYNAMIC_LINKING=1 bundle exec /home/ledsun/ruby.wasm/exe/rbwasm build --ruby-version head --target wasm32-unknown-wasip1 --build-profile full -o /home/ledsun/ruby.wasm/packages/npm-packages/ruby-head-wasm-wasi/tmp/ruby.component.wasm を実行した時にエラーが起きているようです。

ruby.component.wasmを作ろうとしてリンクに失敗しているように思います。

vender/jcoをリセット

jcoのupdateが良くないのかもしれません。 念のため初期状態に戻します。

rm -rf venber/jco
git submodule update --init

bin/setupを実行します。

`RUBY_WASM_ROOT=/home/ledsun/ruby.wasm RUBY_WASM_EXPERIMENTAL_DYNAMIC_LINKING=1 bundle exec /home/ledsun/ruby.wasm/exe/rbwasm build --ruby-version head --target wasm32-unknown-wasip1 --build-profile full -o /home/ledsun/ruby.wasm/packages/npm-packages/ruby-head-wasm-wasi/tmp/ruby.component.wasm に成功しない

RUBY_WASM_ROOT=/home/ledsun/ruby.wasm RUBY_WASM_EXPERIMENTAL_DYNAMIC_LINKING=1 bundle exec /home/ledsun/ruby.wasm/exe/rbwasm build --ruby-version head --target wasm32-unknown-wasip1 --build-profile full -o /home/ledsun/ruby.wasm/packages/npm-packages/ruby-head-wasm-wasi/tmp/ruby.component.wasm を実行します。 エラーが起きます。

make[1]: Leaving directory '/home/ledsun/ruby.wasm/build/wasm32-unknown-wasip1-pic/ruby-head-wasm32-unknown-wasip1-pic-full'
transdb.h unchanged
clang-16clang-16clang-16: : : warning: warning: warning: argument unused during compilation: '-shared' [-Wunused-command-line-argument]
argument unused during compilation: '-shared' [-Wunused-command-line-argument]
argument unused during compilation: '-shared' [-Wunused-command-line-argument]
clang-16: warning: argument unused during compilation: '-shared' [-Wunused-command-line-argument]
wasm-ld: error: enc/cesu_8.o: undefined symbol: rb_enc_register
wasm-ld: error: enc/cesu_8.o: undefined symbol: OnigEncAsciiToLowerCaseTable
...
clang-16: error: linker command failed with exit code 1 (use -v to see invocation)
make[1]: *** [enc.mk:407: .ext/wasm32-wasi/enc/emacs_mule.so] Error 1
make: *** [uncommon.mk:1041: enc] Error 2
make: *** Waiting for unfinished jobs....
Try running with `rake --verbose` for more complete output.
bundler: failed to load command: /home/ledsun/ruby.wasm/exe/rbwasm (/home/ledsun/ruby.wasm/exe/rbwasm)
/home/ledsun/ruby.wasm/lib/ruby_wasm/build/executor.rb:77:in 'RubyWasm::BuildExecutor#system': Command failed with status (2): 'make' '-j8' 'install' 'DESTDIR=/home/ledsun/ruby.wasm/build/wasm32-unknown-wasip1-pic/ruby-head-wasm32-unknown-wasip1-pic-full/install' (RuntimeError)
        from /home/ledsun/ruby.wasm/lib/ruby_wasm/build/product/crossruby.rb:225:in 'RubyWasm::CrossRubyProduct#build'

エラーは大きくは変わっていません。

makeで失敗している

次のコマンドでエラーが再現できるようです。

cd build/wasm32-unknown-wasip1-pic/ruby-head-wasm32-unknown-wasip1-pic-full
make install

もう少し絞れそうです。

cd /home/ledsun/ruby.wasm/build/wasm32-unknown-wasip1-pic/ruby-head-wasm32-unknown-wasip1-pic-full/ext/cgi/escape
make
linking shared-object cgi/escape.so
clang-16: warning: argument unused during compilation: '-shared' [-Wunused-command-line-argument]
wasm-ld: error: escape.o: undefined symbol: rb_ext_ractor_safe
wasm-ld: error: escape.o: undefined symbol: rb_intern2
wasm-ld: error: escape.o: undefined symbol: rb_cObject
wasm-ld: error: escape.o: undefined symbol: rb_define_class
wasm-ld: error: escape.o: undefined symbol: rb_define_module_under
wasm-ld: error: escape.o: undefined symbol: rb_define_module_under
wasm-ld: error: escape.o: undefined symbol: rb_define_alias
wasm-ld: error: escape.o: undefined symbol: rb_define_alias
wasm-ld: error: escape.o: undefined symbol: rb_prepend_module
wasm-ld: error: escape.o: undefined symbol: rb_extend_object
wasm-ld: error: escape.o: undefined symbol: rb_string_value
wasm-ld: error: escape.o: undefined symbol: rb_enc_get
wasm-ld: error: escape.o: undefined symbol: rb_enc_dummy_p
wasm-ld: error: escape.o: undefined symbol: rb_call_super
wasm-ld: error: escape.o: undefined symbol: rb_string_value
wasm-ld: error: escape.o: undefined symbol: rb_enc_get
wasm-ld: error: escape.o: undefined symbol: rb_enc_dummy_p
wasm-ld: error: escape.o: undefined symbol: rb_enc_get
wasm-ld: error: escape.o: undefined symbol: ruby_scan_digits
wasm-ld: error: escape.o: undefined symbol: ruby_scan_digits
wasm-ld: error: too many errors emitted, stopping now (use -error-limit=0 to see all errors)
clang-16: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [Makefile:274: ../../../.ext/wasm32-wasi/cgi/escape.so] Error 1

soファイルをwasm上でダイナミックリンクするための変換処理が上手く行ってなさそうです。 しかし、どうすれば解消できるのかはわかりません。

docker composeコマンドのcompose spec準拠はcompose-goモジュールを使って実現されている

WARN[0000] /home/ledsun/pubdictionaries/docker-compose.yml: version is obsolete

というワーニングを追いかけたら 「docker composeコマンドのcompose spec準拠はcompose-goモジュールを使って実現されている」ことに気がつきました。 その記録です。

警告に出会う

docker compose コマンドを実行したら次のように警告がでました。

docker composeコマンドで警告が出ているスクリーンショット

WARN[0000] /home/ledsun/pubdictionaries/docker-compose.yml: version is obsolete

言っていることは簡単です。 対応は docker-compose .yml ファイルの version フィールドを消せばよいだけです。 でも、一次情報を確認しておきたいです。

長いissueコメントの謎

github.com

docker compose コマンドの v2.25.0 から出るようになったみたいです。 コメントのやりとりをみると

How will docker-compose.yml files for versions 2.x and 3.x be distinguished without the version tag?

Version 2と3を区別しなくていいの?

The current file format is more of a "descendant" of the 2.x series and the 3.x series is, again, only relevant for docker stack, where 2.x had no ... relevance.

docker stackしか関係なくて、docker stackはvesion 3しか扱わない。

なるほど、納得できます。 が、なんかコメントがすごく長く続いています。 飛ばし読みすると「急にログに警告が出てきてびっくりした」人がたくさん居るみたいです。 なぜでしょう?

リリースノートには記載がない

確かに、Release v2.25.0 · docker/compose · GitHub には、この警告がでることについて書いてありません。 それはびっくりする人もいそうです。 僕も、リリースノートに関連情報が書かれていないと「本当に消して良いのか」不安を感じます。

リリースノートには記載がない理由

前提として

Docker Compose V2で変わったdocker-compose.ymlの書き方

Docker Compose V2はCompose Spec[1]に準拠している

そうです。

2024年4月の version-patch by aevesdocker · Pull Request #489 · compose-spec/compose-spec · GitHub で、Composer Spec上で version フィールドが obsolete になりました。 docker comopse v2.25.0 は、3月にリリースされています。 未来の情報はリリースノートに書けません。

なぜdocker comopse v2.25.0に version フィールドの obsolete が反映されているのか?

https://github.com/docker/compose/compare/v2.24.7...v2.25.0#diff-33ef32bf6c23acb95f5902d7097b7a1d5128ca061167ec0716715b0b9eeaa5f6L9 を見ると github.com/compose-spec/compose-go/v2 v2.0.0-rc.8.0 を参照していたのが、github.com/compose-spec/compose-go/v2 v2.0.0に変わっています。 rcが外れています。

Release v2.0.0 · compose-spec/compose-go · GitHub を見ると

warn user version is obsolete by @ndeloof in #575

があります。 warn user `version` is obsolete by ndeloof · Pull Request #575 · compose-spec/compose-go · GitHub です。 この変更とリリースは3月に行われています。 面白いことに、compose-specより先にcompose-goが修正されていたようです。

まとめ

docker composeから見るとcopomse-goのRCを取っただけです。 以前からある「compose specに準拠する」ポリシーは変わっていません。 docker composeにとっての大きな変更ではなさそうです。

また、copomse-goの変更を全部みてリリースノートに書くのも不毛に感じます。

なるほど「compose specに準拠する」の実現方法が「copomse-goを使う」であることを知らないと、混乱しそうです。 僕の中のメンタルモデル(docker compose の理解)が更新されました。 1つ賢くなれたようです。

参考

ブラウザでruby.wasmのJS.evalしたときのスコープはなんなのか?

事の発端

tmtms さんが ruby-jp slack に面白いコードを投稿していました。

JS.eval <<~EOS
  A = 123
  class B { }
  console.log(A)  //=> 123
  console.log(B)  //=> class B {}
EOS

begin
  JS.eval <<~EOS
    console.log(A)  //=> 123
    console.log(B)
  EOS
rescue => e
  p e  #=> #<JS::Error: ReferenceError: B is not defined>
end

上段の JS.eval で定義されたAは下段の JS.eval から参照できます。 ところがBはできません。

このコードの面白い点は

A = 123
class B { }

この辺りがすごくRubyっぽくて、JavaScriptだってわかっていても、脳が勝手にRubyと解釈してしまうところです。 Rubyだと、AとBは、どちらもグローバルスコープに宣言されます。

コードの動き

Aはグローバル変数

冷静にJavaScript脳で解釈するとA = 1235グローバル変数*1の定義です。

var - JavaScript | MDN

厳格モードでない場合は、スコープチェインで宣言されている同名の変数がない場合は、グローバルオブジェクト上にその名前のプロパティを作成しようとしていると仮定して、非修飾識別子に代入することになります。

この説明も難しいです。 が、要するにA = 1235window.A = 1235と解釈されます。

Bはローカル変数

class B { }はクラス宣言です

class - JavaScript | MDN

クラス式と同様、クラス宣言の内部は厳格モードで実行されます。

こちらはグローバルスコープでは宣言されません。

つまり

Aは下段の JS.eval から参照できますが、Bはできません。

JS.eval のスコープは?

ということは、JS.eval はグローバルスコープではなくて、なんらかのスコープを持っています。 もしスコープをもっていなければ、Bもグローバル変数として宣言されます。 下段の JS.eval から参照できるはずです。

ruby.wasmのJS.evalソースコードを見てみましょう。

https://github.com/ruby/ruby.wasm/blob/8e3731b3bf901c9c05a16ce385d93c6a61ec3dbe/packages/npm-packages/ruby-wasm-wasi/src/vm.ts#L232-L234

evalJs: wrapTry((code) => {
    return toJSAbiValue(Function(code)());
}),

アロー関数のなかで、Function関数で関数を作成し実行しています。 アロー関数はスコープをつくります。 JS.evalは、グローバルスコープではなく、この関数のスコープで実行されます。 その結果、クラス宣言で作成されたローカル変数Bは、他のJS.evalから参照できません。

他のJS.evalから参照したければ、window.B = class {}のようにグローバル変数として宣言します。

おまけ

なぜJS.evalは、eval()関数ではなく、Function関数を使うのでしょうか? tompngさんに教えてもらいました。

function evalJS(code){eval(code)}
evalJS('console.log(code)') // => "console.log(code)"

evalを実行するコンテクストのローカル変数(ここではcode)が参照できてしまいます。

function evalJS(code){Function(code)()}
evalJS('console.log(code)') // => 正しく ReferenceError: code is not defined になる

(´・∀・`)ヘー

RubyJavaScriptの狭間の世界、楽しいです。

*1:雑にグローバル変数と呼んでいますが、より厳密にはwindowオブジェクトのプロパティです。

ベースの音の立ち上がりに関する所感

ここで言うベースはエレキベースとかウッドベースとか呼ばれる楽器のことです。 また、素人が適当に感想を述べているだけで、特に正確性がある内容ではありません。

ベースは音の立ち上がりが遅い楽器のようです。 弦の振動が安定するまでは、ベースに期待しているブンブーンという音に聞こえないのだと思います。 ベースの弦はギターより長くて太いです。 その分、弦の振動が安定するまで時間が掛かるのでしょう。

どれくらい遅いかというと100~200msだと思います。 根拠は「知覚はできるが明確には分離できない」くらいのふわっとしたものです。 数値は完全な与太話です。 太鼓は「叩く」イメージで叩くと、音とタイミングがズレます。 これは太鼓の音が出るのが、皮を押した時でなく、離した時だからと思います。 「押して離す」イメージで叩くとズレが減ります。 この遅れが300msくらいと考えています。 雑にその半分くらいの感覚です。

この微妙な音の立ち上がりの遅さがベースの演奏を難しくしています。 ドラムの音に指やピックで弦を弾くタイミングで合わせると100~200msズレるのです。 ということは、100~200ms早く弾けばいいのですが、人間は指の運動としては、この精度では上手く制御できません。 どうするかと言うと、音を聞きます。なんかいい感じの音が出るタイミングで弾きます。 説明になっていませんがたぶんそうです。

これはプロのミュージシャンにとっても難しいようです。同じミュージシャンが同じ曲をやっても音が合ってるときと、合っていない時があります。音が合っている時は、腰でノれますが、合っていない時は音がバラバラでどの音にノればいいかわかりません。 現象として以前から観測していましたが「ベースの音ズレ問題」として認識したのは最近です。 緊張してたり、運指に意識が持ってかれていたりすると、ズレるようです。

音ズレ問題に関しては、バンドで名前を出しているベーシストより、バックバンドをやっているベーシストの方の方が得意なことが多いようです。1ステージ通してまったくズレなかったりして、恐るべき能力を感じます。

コーラスを入れるベーシストの方は音ズレ解消した演奏をしながら歌を歌っています。どういう脳みそをしているのか謎です。 歌は歌で息を吐くタイミングと音が出るタイミングはズレています。 つまり、二つの異なるズレを同時に解決しています。 脳みそがマルチコアなのでしょうか?

また、アップライトベースウッドベースは音の立ち上がりがさらに遅いようです。弦がさらに長くなるからではないかと思います。エレキベースのつもりでステージを見ていると、指で弾くタイミングよりさらに遅れて音が聞こえて、違和感を感じます。ただでさえ難しい音ズレ問題なのに、パラメーターが変わります。感覚で合わせてるのに、そのパラメーターが変わる。1ステージ中にエレキベースウッドベース持ち換えれるのヤバくないですか?

ピックと指でも変わるようです。ピックの方が遅く感じます。 これは見る側の感覚なのが演奏する側の感覚なのか、自分の中で、区別がついていません。 指は弦を引っ張って「離す」タイミング、ピックは弦を「押して」離すタイミングを見ているのかもしれません。 実際には視覚的に見えているわけでなく、周期運動を予測しています。 「見て」いる側が、予測する「ヒット」のタイミングを間違って設定しているのかもしれません。