Skip to content

GH-55605: [R] Don't abort when comparing R extension types off the R thread - #55621

Open
csarushan1729 wants to merge 1 commit into
apache:mainfrom
csarushan1729:GH-55605
Open

csarushan1729 wants to merge 1 commit into
apache:mainfrom
csarushan1729:GH-55605

Conversation

@csarushan1729

@csarushan1729 csarushan1729 commented Nov 15, 2026 •

Copy link
Copy Markdown

Rationale for this change

RExtensionType::ExtensionEquals() compares the extension name and the serialized metadata in C++. If those don't settle the comparison, it calls the R6 ExtensionEquals() method through SafeCallIntoR(). When that runs on a worker thread outside RunWithCapturedR(), SafeCallIntoR() returns an error status. ExtensionEquals() then throws it as a std::runtime_error, nothing on that thread catches it, and std::terminate() ends the R process.

This happens when scanning a dataset whose parts use the same R extension type with different metadata, through both dplyr::collect() and Scanner$create(ds)$ToTable():

terminate called after throwing an instance of 'std::runtime_error'
  what():  Call to R (RExtensionType$ExtensionEquals()) from a non-R thread from an unsupported context

What changes are included in this PR?

When R can't be called, ExtensionEquals() now returns false instead of throwing. This is what the default R ExtensionEquals() method would return in that case, because the serialized metadata differs. The scan then fails with the same ordinary R error as the single-threaded paths:

Type error: Casting from 'extension<example.unit>' to different extension type 'extension<example.unit>' not permitted. One can first cast to the storage type, then to the extension type.

Notes:

  • On the R thread, nothing changes. There, SafeCallIntoR() runs the R method directly, so this branch is never reached.
  • Inside RunWithCapturedR(), the R method still runs. If it raises an R error, RunWithCapturedR() re-raises that error as before.
  • One limitation: off the R thread, a subclass that overrides ExtensionEquals() can't be consulted, so two types with different serialized metadata compare as unequal there. Before this change, the same situation aborted the process. RExtensionType::ToString() already falls back the same way when R can't be called.

Are these changes tested?

Yes. I added a test to test-extension.R. It writes two IPC files whose column has the same extension type with different metadata, then checks that collect() and Scanner$create(ds)$ToTable() raise an R error. Without the fix, this test aborts the R process (exit status 134).

Locally on Linux (Ubuntu 26.04 under WSL), against a dev build of libarrow with Acero, Dataset and Parquet: all of test-extension.R passes (FAIL 0 | WARN 0 | SKIP 0 | PASS 52). test-dataset.R and test-safe-call-into-r.R give the same results with and without the change.

Are there any user-facing changes?

Yes. Scanning such a dataset now raises an R error instead of ending the R session.

This PR contains a "Critical Fix". It fixes a crash (the R process is aborted) that occurs with documented API usage.

Was AI used for this PR?

In accordance to the AI generation guidelines, please disclose below whether and how AI was used in this PR.

PR code and description written by:

  • Human
  • AI

Reviewed before submission by:

  • Human
  • AI
  • Not reviewed

@github-actions

Copy link
Copy Markdown

⚠️ GitHub issue #55605 has been automatically assigned in GitHub to PR creator.

@github-actions

Copy link
Copy Markdown

⚠️ GitHub issue #55605 has no components, please add labels for components.

@thisisnic

Copy link
Copy Markdown
Member

I'm not entirely convinced by the answers to the "Was AI used for this PR?" section here

@thisisnic thisisnic closed this Nov 15, 2026
@thisisnic thisisnic reopened this Nov 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants