truffleruby icon indicating copy to clipboard operation
truffleruby copied to clipboard

Process.spawn does not raise an error when expected in TruffleRuby

Open jcouball opened this issue 8 months ago • 3 comments

In CRuby, when Process.spawn is given an invalid chdir: option (pointing to a nonexistent directory), it raises an Errno::ENOENT error. However, in TruffleRuby, Process.spawn does not raise an error. Instead, it returns a Process::Status object with exitstatus == 1.

This behavior creates ambiguity—it’s unclear whether the subprocess failed to start or if it started and exited with a failure code.

Expected Behavior (CRuby 3.4.1)

When Process.spawn is called with a nonexistent chdir: directory, it should raise an error:

$ ruby --version
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [arm64-darwin24]
$ irb
irb(main):001> Process.wait2(Process.spawn('pwd', chdir: 'does_not_exist'))
(irb):1:in 'Process.spawn': No such file or directory - does_not_exist (Errno::ENOENT)

Actual Behavior (TruffleRuby 24.1.2)

Instead of raising an error, TruffleRuby prints an error message but returns a Process::Status object:

$ ruby --version
truffleruby 24.1.2, like ruby 3.2.4, Oracle GraalVM Native [arm64-darwin20]
$ irb
irb(main):001:0> Process.wait2(Process.spawn('pwd', chdir: 'does_not_exist'))
chdir: No such file or directory
working_directory=does_not_exist
=> [69902, #<Process::Status: pid 69902 exit 1>]

Why This is a Problem

  1. Inconsistent with CRuby Behavior: TruffleRuby’s behavior deviates from the standard Ruby implementation.
  2. Ambiguous Error Handling: When Process::Status has exitstatus == 1, there’s no clear way to distinguish between:
    • A subprocess that failed to start (e.g., due to an invalid chdir:).
    • A subprocess that started but exited with failure.
  3. Cross-Ruby Compatibility: I maintain a Ruby gem that calls Process.spawn, and this difference in behavior makes it difficult to ensure consistent error handling across Ruby implementations.

Questions for the TruffleRuby Team

  • Is this behavior intentional?
  • If so, how should developers differentiate between a process that couldn’t start vs. one that started and failed?
  • Would it be possible to align TruffleRuby with CRuby by raising Errno::ENOENT in this case?

Thanks for your time and consideration!

jcouball avatar Apr 01 '25 23:04 jcouball

Thank you for the report, we'll look into it.

andrykonchin avatar Apr 02 '25 15:04 andrykonchin

Thank you @andrykonchin!

Upon reviewing my initial report, I believe I overlooked the possibility of additional errors that could occur and trigger a CRuby exception.

Generally, if there is an issue that prevents Ruby from executing the subprocess, it should raise an error. Returning a Process::Status should be reserved for reporting on the outcomes of the subprocess’s execution.

jcouball avatar Apr 02 '25 15:04 jcouball

We use posix_spawnp() which might not always tell us that difference. Similar issue when Process.spawn uses the shell, and this also happens on CRuby.

But for the chdir case reported here we can easily check it in the calling process with Dir.exist? so this should be an easy fix.

eregon avatar May 05 '25 12:05 eregon