ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. … - #9778

Closed
ritchie46 wants to merge 12 commits into
apache:masterfrom
ritchie46:ffi_types
Closed

ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. …#9778
ritchie46 wants to merge 12 commits into
apache:masterfrom
ritchie46:ffi_types

Conversation

@ritchie46

Copy link
Copy Markdown
Contributor

This PR adds child data to Arrow's C FFI implementation and implements it for List and LargeList datatypes.

@ritchie46ritchie46 changed the title Add Child Data to Arrow's C FFI implementation. …ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. …Mar 23, 2021
@github-actions

Copy link
Copy Markdown

@apacheapache deleted a comment from github-actionsBotMar 24, 2021
@ritchie46
ritchie46force-pushed the ffi_types branch 2 times, most recently from 151be97 to b6fbf30CompareMarch 24, 2021 07:55
@alamb

Copy link
Copy Markdown
Contributor

I am not an expert in this level of code -- perhaps @jorgecarleitao has time to take a look at this PR?

@jorgecarleitaojorgecarleitao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this, @ritchie46 . it has the right ideas and looks great so far. 💯

I left some comments throughout the code.

My last general comment would be to add this type to the pyarrow-integration-tests crate, which contains real tests against pyarrow, which allow us to validate the behavior against the C++ implementation.

Comment threadrust/arrow/src/datatypes/field.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
@ritchie46

Copy link
Copy Markdown
ContributorAuthor

Thanks a lot for this, @ritchie46 . it has the right ideas and looks great so far. 100

I left some comments throughout the code.

My last general comment would be to add this type to the pyarrow-integration-tests crate, which contains real tests against pyarrow, which allow us to validate the behavior against the C++ implementation.

Cool! I think I've tackled all your comments @jorgecarleitao . I also added a test to the pyarrow-integration-tests-crate. That was a good one, because it did not succeed.

It turns out that we need to provide a name in the FFI_ArrowSchema when we provide child data. This was a null ptr, so I replaced this with an empty "" string, that seemed easiest to me. If you want to tackle that differently, I am open to suggestions.

@nevi-menevi-me left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments, I'm happy with the implementation though. Thanks @ritchie46 , and I apologise for taking long to review this.

Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow-pyarrow-integration-testing/tests/test_sql.py Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
@ritchie46

Copy link
Copy Markdown
ContributorAuthor

Some minor comments, I'm happy with the implementation though. Thanks @ritchie46 , and I apologise for taking long to review this.

No worries! 😄

@codecov-io

Copy link
Copy Markdown

Codecov Report

Merging #9778 (ba51b55) into master (29feea0) will decrease coverage by 0.16%.
The diff coverage is 77.71%.

Impacted file tree graph

@@ Coverage Diff @@## master #9778 +/- ##
==========================================
- Coverage 82.59% 82.43% -0.17% 
==========================================
Files 248 252 +4 Lines 58294 59024 +730 ==========================================
+ Hits 48149 48655 +506 - Misses 10145 10369 +224 
Impacted FilesCoverage Δ
rust/arrow-pyarrow-integration-testing/src/lib.rs0.00% <ø> (ø)
rust/arrow/src/compute/kernels/sort.rs94.37% <ø> (+0.80%)⬆️
rust/arrow/src/compute/kernels/substring.rs98.29% <ø> (ø)
rust/arrow/src/compute/kernels/take.rs96.06% <ø> (-0.01%)⬇️
rust/arrow/src/compute/kernels/window.rs100.00% <ø> (ø)
rust/arrow/src/compute/kernels/zip.rs82.14% <ø> (ø)
rust/arrow/src/compute/util.rs98.92% <ø> (ø)
rust/arrow/src/datatypes/field.rs55.47% <ø> (ø)
rust/arrow/src/ffi.rs80.23% <ø> (+4.00%)⬆️
rust/arrow/src/ipc/reader.rs84.36% <ø> (ø)
... and 90 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ee24399...ba51b55. Read the comment docs.

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@nevi-me there are some new clippy warnings due to new Rust version not related to this PR. Do they need to be fixed?

@nevi-me

Copy link
Copy Markdown
Contributor

@nevi-me there are some new clippy warnings due to new Rust version not related to this PR. Do they need to be fixed?

@ritchie46 I fixed them last night (depending on where in the world one is lol). CI's fine now. The integration failures are known issues at ARROW-12112

@nevi-menevi-me left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM from looking at what you're doing to construct the lists.

@jorgecarleitao can do the honours of the PR blessing approval :)

@nevi-me

Copy link
Copy Markdown
Contributor

It doesn't look like anything needs to be updated in docs/source/status.rst

Comment threadrust/arrow/src/ffi.rs Outdated
@alamb

alamb commented Apr 1, 2021

Copy link
Copy Markdown
Contributor

@ritchie46 / @jorgecarleitao / @nevi-me is this one ready to go? There is one smally clippy lint left which I can fixup but I didn't want to ram this PR through to keep the queue down if it wasn't actually ready

@jorgecarleitaojorgecarleitao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks a lot @ritchie46 for taking the extra mile to have the equality done right :) 💯

Also, thanks a lot @pitrou for your help here. 👍

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@alamb I fixed the clippy issue, so I think we're good to go. :)

@alamb

alamb commented Apr 2, 2021

Copy link
Copy Markdown
Contributor

Integration test failure seems due to https://github.com/apache/arrow/pull/9778/checks?check_run_id=2253660088 (not related to this PR):

-----------------------
/
/arrow/js /
npm WARN tar ENOSPC: no space left on device, write
npm WARN tar ENOSPC: no space left on device, write
npm ERR! cb() never called!

@alambalamb closed this in 2f3ed3aApr 2, 2021
@alamb

alamb commented Apr 2, 2021

Copy link
Copy Markdown
Contributor

Thanks again @ritchie46 👍

@ritchie46

ritchie46 commented Apr 4, 2021

Copy link
Copy Markdown
ContributorAuthor

Hmm.. This is sadly a bit too late. But the current implementation does an invalid read/write. I get a SIGILL if I run this test a 1000 times.

Current thread 0x00007f3bd56f4740 (most recent call first):
File "/home/ritchie46/code/arrow/rust/arrow-pyarrow-integration-testing/tests/test_sql.py", line 89 in test_list_array
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 633 in _callTestMethod
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 676 in run
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 736 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/unittest.py", line 207 in runtest
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 131 in pytest_runtest_call
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 207 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 234 in from_call
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 206 in call_runtest_hook
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 182 in call_and_report
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 96 in runtestprotocol
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 81 in pytest_runtest_protocol
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 270 in pytest_runtestloop
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 246 in _main
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 196 in wrap_session
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 239 in pytest_cmdline_main
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/config/__init__.py", line 91 in main
File "/opt/miniconda3/bin/pytest", line 8 in <module>
Illegal instruction (core dumped)

I hope I can find the location of this invalid read/write. If anybody has an idea of where did could occur that would be very much appreciated.

Some additional info/ thoughts:

  • I cannot reproduce this in the rust tests. Is there a way to interact with C++ arrow from rust? Using valgrind on python creates a lot of noise from the python VM.

  • the location of SIGILL, during the second traversal of create children (called by the children arrays), the Arc::clone of self.array leads to the SIGILL.

@ritchie46

ritchie46 commented Apr 5, 2021

Copy link
Copy Markdown
ContributorAuthor

If I prevent the drop in release array, this issue is resolved but we leak data.

TBH, I am stuck. @pitrou@jorgecarleitao have you got any idea how this can be resolved?

could this be related?

@jorgecarleitao

Copy link
Copy Markdown
Member

I am really sorry, this was sloppiness on my part: I should have checkout the code and go through it more carefully as FFI is always risky stuff. If you think it would take some pressure off, we can revert this PR until we find and fix this.

Regardless, could you run the memory-check to see if we find the problem in our internal roundtrips? Something like

cargo test --lib --feature memory-check -- --test-threads=1

on the rust crate. This counts every alloc/realloc/dealloc over all buffers over all tests and verifies that the sum is zero. The test-threads must be 1 so that tests run sequentially and the last test is the memory check.

I would try to run this before this PR's commit just to make sure that things work as expected since we do not run this as part of our CI. If it passes, then I would try again after this PR's commit.

@pitrou

Copy link
Copy Markdown
Member

@ritchie46 The guidelines for implementers of a release callback here. I would suggest following the example:
https://arrow.apache.org/docs/format/CDataInterface.html#release-callback-semantics-for-producers

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@ritchie46 The guidelines for implementers of a release callback here. I would suggest following the example:
https://arrow.apache.org/docs/format/CDataInterface.html#release-callback-semantics-for-producers

Yes, thank you. I will be going through that.

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

I am really sorry, this was sloppiness on my part: I should have checkout the code and go through it more carefully as FFI is always risky stuff. If you think it would take some pressure off, we can revert this PR until we find and fix this.

Yes, in that case we panic instead of UB, which cleary is better.

Regardless, could you run the memory-check to see if we find the problem in our internal roundtrips? Something like

Will do that.

Some extra info: I realize that we don't have any owned child data in private_data so, maybe the child_data is already dropped.

@pitroupitrou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this seems a bit delicate to get right, I think you should first add tests for roundtripping schemas:

  • roundtrip a schema as is, Rust->Python->Rust
  • roundtrip a schema as is, Python->Rust->Python
  • create a primitive type in Rust, return pa.list(primitive_type) from Python
  • create a list type in Rust, return list_type.value_type from Python

For each case, verify the expected result, also check for allocation/deallocation/leaks.

Once you got that right, you can tackle the array roundtrip issue.

.child_data()
.iter()
.map(|arr| {
let len = arr.len();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand: why isn't try_from called recursively?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because I otherwise cannot pass nullable: bool information from the parent. If should split this up in a function separate from try_from to make this more explicit.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, you'll need to handle recursive types more generally anyway. Think list(list(int8)).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that would not work like this. I will fix that.

a = pyarrow.array([[], None, [1, 2], [4, 5, 6]], pyarrow.list_(pyarrow.int64()))
b = arrow_pyarrow_integration_testing.round_trip(a)

b.validate(full=True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be interesting to add del a above this, to make sure that b keeps the data alive.

Comment threadrust/arrow/src/ffi.rs
FFI_ArrowSchema {
format: CString::new(format).unwrap().into_raw(),
name: std::ptr::null_mut(),
// For child data a non null string is expected and is called item

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only for lists, though. For more general nested types you'll have to take the actual field name.

Comment threadrust/arrow/src/ffi.rs
Comment threadrust/arrow/src/ffi.rs
// at that point the child data is not yet known, but it is also not required to determine
// the buffer length of the list arrays.
"+l" => {
let nullable = schema.flags == 2;

@pitroupitrouApr 5, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be (schema.flags & 2) != 0. Also I'm surprised you're sprinkling magic numbers in the code instead of defining a constant.

Comment threadrust/arrow/src/ffi.rs
// Safety
// Should be set as this is expected from the C FFI definition
debug_assert!(!schema.name.is_null());
let name = unsafe { CString::from_raw(schema.name as *mut c_char) }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem right. The doc for from_raw says:

This should only ever be called with a pointer that was earlier obtained by calling CString::into_raw. Other usage (e.g., trying to take ownership of a string that was allocated by foreign code) is likely to lead to undefined behavior or allocator corruption.

But we are exactly in the case where schema.name can have been allocated by C++ or Python or anything else. It seems instead you should use CStr instead: "Representation of a borrowed C string".

Comment threadrust/arrow/src/ffi.rs
(DataType::Utf8, 2) | (DataType::Binary, 2) => size_of::<u8>() * 8,
(DataType::Utf8, _) | (DataType::Binary, _) => {
(DataType::Utf8, 1) | (DataType::Binary, 1) | (DataType::List(_), 1) => size_of::<i32>() * 8,
(DataType::Utf8, 2) | (DataType::Binary, 2) | (DataType::List(_), 2) => size_of::<u8>() * 8,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lists don't have a buffer number 2.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. I thought it was a validity bitmap and an offset buffer and that the child data was counted differently.

What should be the correct number?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validity bitmap is buffer 0 and the offsets are buffer 1. You are defining a buffer 2 (of u8) which doesn't exist.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validity is the buffer 0, the offsets the buffer 1. The List has no buffer number two. If someone requests buffer 2 from a List Array, we should error instead.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, Thanks!

Comment threadrust/arrow/src/ffi.rs
Sign up for freeto 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.

6 participants

@ritchie46@alamb@codecov-io@nevi-me@jorgecarleitao@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. … - #9778

Closed
ritchie46 wants to merge 12 commits into
apache:masterfrom
ritchie46:ffi_types
Closed

ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. …#9778
ritchie46 wants to merge 12 commits into
apache:masterfrom
ritchie46:ffi_types

Conversation

@ritchie46

Copy link
Copy Markdown
Contributor

This PR adds child data to Arrow's C FFI implementation and implements it for List and LargeList datatypes.

@ritchie46ritchie46 changed the title Add Child Data to Arrow's C FFI implementation. …ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. …Mar 23, 2021
@github-actions

Copy link
Copy Markdown

@apacheapache deleted a comment from github-actionsBotMar 24, 2021
@ritchie46
ritchie46force-pushed the ffi_types branch 2 times, most recently from 151be97 to b6fbf30CompareMarch 24, 2021 07:55
@alamb

Copy link
Copy Markdown
Contributor

I am not an expert in this level of code -- perhaps @jorgecarleitao has time to take a look at this PR?

@jorgecarleitaojorgecarleitao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this, @ritchie46 . it has the right ideas and looks great so far. 💯

I left some comments throughout the code.

My last general comment would be to add this type to the pyarrow-integration-tests crate, which contains real tests against pyarrow, which allow us to validate the behavior against the C++ implementation.

Comment threadrust/arrow/src/datatypes/field.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
@ritchie46

Copy link
Copy Markdown
ContributorAuthor

Thanks a lot for this, @ritchie46 . it has the right ideas and looks great so far. 100

I left some comments throughout the code.

My last general comment would be to add this type to the pyarrow-integration-tests crate, which contains real tests against pyarrow, which allow us to validate the behavior against the C++ implementation.

Cool! I think I've tackled all your comments @jorgecarleitao . I also added a test to the pyarrow-integration-tests-crate. That was a good one, because it did not succeed.

It turns out that we need to provide a name in the FFI_ArrowSchema when we provide child data. This was a null ptr, so I replaced this with an empty "" string, that seemed easiest to me. If you want to tackle that differently, I am open to suggestions.

@nevi-menevi-me left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments, I'm happy with the implementation though. Thanks @ritchie46 , and I apologise for taking long to review this.

Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow-pyarrow-integration-testing/tests/test_sql.py Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
@ritchie46

Copy link
Copy Markdown
ContributorAuthor

Some minor comments, I'm happy with the implementation though. Thanks @ritchie46 , and I apologise for taking long to review this.

No worries! 😄

@codecov-io

Copy link
Copy Markdown

Codecov Report

Merging #9778 (ba51b55) into master (29feea0) will decrease coverage by 0.16%.
The diff coverage is 77.71%.

Impacted file tree graph

@@ Coverage Diff @@## master #9778 +/- ##
==========================================
- Coverage 82.59% 82.43% -0.17% 
==========================================
Files 248 252 +4 Lines 58294 59024 +730 ==========================================
+ Hits 48149 48655 +506 - Misses 10145 10369 +224 
Impacted FilesCoverage Δ
rust/arrow-pyarrow-integration-testing/src/lib.rs0.00% <ø> (ø)
rust/arrow/src/compute/kernels/sort.rs94.37% <ø> (+0.80%)⬆️
rust/arrow/src/compute/kernels/substring.rs98.29% <ø> (ø)
rust/arrow/src/compute/kernels/take.rs96.06% <ø> (-0.01%)⬇️
rust/arrow/src/compute/kernels/window.rs100.00% <ø> (ø)
rust/arrow/src/compute/kernels/zip.rs82.14% <ø> (ø)
rust/arrow/src/compute/util.rs98.92% <ø> (ø)
rust/arrow/src/datatypes/field.rs55.47% <ø> (ø)
rust/arrow/src/ffi.rs80.23% <ø> (+4.00%)⬆️
rust/arrow/src/ipc/reader.rs84.36% <ø> (ø)
... and 90 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ee24399...ba51b55. Read the comment docs.

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@nevi-me there are some new clippy warnings due to new Rust version not related to this PR. Do they need to be fixed?

@nevi-me

Copy link
Copy Markdown
Contributor

@nevi-me there are some new clippy warnings due to new Rust version not related to this PR. Do they need to be fixed?

@ritchie46 I fixed them last night (depending on where in the world one is lol). CI's fine now. The integration failures are known issues at ARROW-12112

@nevi-menevi-me left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM from looking at what you're doing to construct the lists.

@jorgecarleitao can do the honours of the PR blessing approval :)

@nevi-me

Copy link
Copy Markdown
Contributor

It doesn't look like anything needs to be updated in docs/source/status.rst

Comment threadrust/arrow/src/ffi.rs Outdated
@alamb

alamb commented Apr 1, 2021

Copy link
Copy Markdown
Contributor

@ritchie46 / @jorgecarleitao / @nevi-me is this one ready to go? There is one smally clippy lint left which I can fixup but I didn't want to ram this PR through to keep the queue down if it wasn't actually ready

@jorgecarleitaojorgecarleitao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks a lot @ritchie46 for taking the extra mile to have the equality done right :) 💯

Also, thanks a lot @pitrou for your help here. 👍

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@alamb I fixed the clippy issue, so I think we're good to go. :)

@alamb

alamb commented Apr 2, 2021

Copy link
Copy Markdown
Contributor

Integration test failure seems due to https://github.com/apache/arrow/pull/9778/checks?check_run_id=2253660088 (not related to this PR):

-----------------------
/
/arrow/js /
npm WARN tar ENOSPC: no space left on device, write
npm WARN tar ENOSPC: no space left on device, write
npm ERR! cb() never called!

@alambalamb closed this in 2f3ed3aApr 2, 2021
@alamb

alamb commented Apr 2, 2021

Copy link
Copy Markdown
Contributor

Thanks again @ritchie46 👍

@ritchie46

ritchie46 commented Apr 4, 2021

Copy link
Copy Markdown
ContributorAuthor

Hmm.. This is sadly a bit too late. But the current implementation does an invalid read/write. I get a SIGILL if I run this test a 1000 times.

Current thread 0x00007f3bd56f4740 (most recent call first):
File "/home/ritchie46/code/arrow/rust/arrow-pyarrow-integration-testing/tests/test_sql.py", line 89 in test_list_array
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 633 in _callTestMethod
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 676 in run
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 736 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/unittest.py", line 207 in runtest
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 131 in pytest_runtest_call
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 207 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 234 in from_call
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 206 in call_runtest_hook
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 182 in call_and_report
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 96 in runtestprotocol
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 81 in pytest_runtest_protocol
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 270 in pytest_runtestloop
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 246 in _main
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 196 in wrap_session
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 239 in pytest_cmdline_main
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/config/__init__.py", line 91 in main
File "/opt/miniconda3/bin/pytest", line 8 in <module>
Illegal instruction (core dumped)

I hope I can find the location of this invalid read/write. If anybody has an idea of where did could occur that would be very much appreciated.

Some additional info/ thoughts:

  • I cannot reproduce this in the rust tests. Is there a way to interact with C++ arrow from rust? Using valgrind on python creates a lot of noise from the python VM.

  • the location of SIGILL, during the second traversal of create children (called by the children arrays), the Arc::clone of self.array leads to the SIGILL.

@ritchie46

ritchie46 commented Apr 5, 2021

Copy link
Copy Markdown
ContributorAuthor

If I prevent the drop in release array, this issue is resolved but we leak data.

TBH, I am stuck. @pitrou@jorgecarleitao have you got any idea how this can be resolved?

could this be related?

@jorgecarleitao

Copy link
Copy Markdown
Member

I am really sorry, this was sloppiness on my part: I should have checkout the code and go through it more carefully as FFI is always risky stuff. If you think it would take some pressure off, we can revert this PR until we find and fix this.

Regardless, could you run the memory-check to see if we find the problem in our internal roundtrips? Something like

cargo test --lib --feature memory-check -- --test-threads=1

on the rust crate. This counts every alloc/realloc/dealloc over all buffers over all tests and verifies that the sum is zero. The test-threads must be 1 so that tests run sequentially and the last test is the memory check.

I would try to run this before this PR's commit just to make sure that things work as expected since we do not run this as part of our CI. If it passes, then I would try again after this PR's commit.

@pitrou

Copy link
Copy Markdown
Member

@ritchie46 The guidelines for implementers of a release callback here. I would suggest following the example:
https://arrow.apache.org/docs/format/CDataInterface.html#release-callback-semantics-for-producers

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@ritchie46 The guidelines for implementers of a release callback here. I would suggest following the example:
https://arrow.apache.org/docs/format/CDataInterface.html#release-callback-semantics-for-producers

Yes, thank you. I will be going through that.

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

I am really sorry, this was sloppiness on my part: I should have checkout the code and go through it more carefully as FFI is always risky stuff. If you think it would take some pressure off, we can revert this PR until we find and fix this.

Yes, in that case we panic instead of UB, which cleary is better.

Regardless, could you run the memory-check to see if we find the problem in our internal roundtrips? Something like

Will do that.

Some extra info: I realize that we don't have any owned child data in private_data so, maybe the child_data is already dropped.

@pitroupitrou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this seems a bit delicate to get right, I think you should first add tests for roundtripping schemas:

  • roundtrip a schema as is, Rust->Python->Rust
  • roundtrip a schema as is, Python->Rust->Python
  • create a primitive type in Rust, return pa.list(primitive_type) from Python
  • create a list type in Rust, return list_type.value_type from Python

For each case, verify the expected result, also check for allocation/deallocation/leaks.

Once you got that right, you can tackle the array roundtrip issue.

.child_data()
.iter()
.map(|arr| {
let len = arr.len();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand: why isn't try_from called recursively?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because I otherwise cannot pass nullable: bool information from the parent. If should split this up in a function separate from try_from to make this more explicit.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, you'll need to handle recursive types more generally anyway. Think list(list(int8)).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that would not work like this. I will fix that.

a = pyarrow.array([[], None, [1, 2], [4, 5, 6]], pyarrow.list_(pyarrow.int64()))
b = arrow_pyarrow_integration_testing.round_trip(a)

b.validate(full=True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be interesting to add del a above this, to make sure that b keeps the data alive.

Comment threadrust/arrow/src/ffi.rs
FFI_ArrowSchema {
format: CString::new(format).unwrap().into_raw(),
name: std::ptr::null_mut(),
// For child data a non null string is expected and is called item

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only for lists, though. For more general nested types you'll have to take the actual field name.

Comment threadrust/arrow/src/ffi.rs
Comment threadrust/arrow/src/ffi.rs
// at that point the child data is not yet known, but it is also not required to determine
// the buffer length of the list arrays.
"+l" => {
let nullable = schema.flags == 2;

@pitroupitrouApr 5, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be (schema.flags & 2) != 0. Also I'm surprised you're sprinkling magic numbers in the code instead of defining a constant.

Comment threadrust/arrow/src/ffi.rs
// Safety
// Should be set as this is expected from the C FFI definition
debug_assert!(!schema.name.is_null());
let name = unsafe { CString::from_raw(schema.name as *mut c_char) }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem right. The doc for from_raw says:

This should only ever be called with a pointer that was earlier obtained by calling CString::into_raw. Other usage (e.g., trying to take ownership of a string that was allocated by foreign code) is likely to lead to undefined behavior or allocator corruption.

But we are exactly in the case where schema.name can have been allocated by C++ or Python or anything else. It seems instead you should use CStr instead: "Representation of a borrowed C string".

Comment threadrust/arrow/src/ffi.rs
(DataType::Utf8, 2) | (DataType::Binary, 2) => size_of::<u8>() * 8,
(DataType::Utf8, _) | (DataType::Binary, _) => {
(DataType::Utf8, 1) | (DataType::Binary, 1) | (DataType::List(_), 1) => size_of::<i32>() * 8,
(DataType::Utf8, 2) | (DataType::Binary, 2) | (DataType::List(_), 2) => size_of::<u8>() * 8,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lists don't have a buffer number 2.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. I thought it was a validity bitmap and an offset buffer and that the child data was counted differently.

What should be the correct number?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validity bitmap is buffer 0 and the offsets are buffer 1. You are defining a buffer 2 (of u8) which doesn't exist.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validity is the buffer 0, the offsets the buffer 1. The List has no buffer number two. If someone requests buffer 2 from a List Array, we should error instead.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, Thanks!

Comment threadrust/arrow/src/ffi.rs
Sign up for freeto 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.

6 participants

@ritchie46@alamb@codecov-io@nevi-me@jorgecarleitao@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. … - #9778

Closed
ritchie46 wants to merge 12 commits into
apache:masterfrom
ritchie46:ffi_types
Closed

ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. …#9778
ritchie46 wants to merge 12 commits into
apache:masterfrom
ritchie46:ffi_types

Conversation

@ritchie46

Copy link
Copy Markdown
Contributor

This PR adds child data to Arrow's C FFI implementation and implements it for List and LargeList datatypes.

@ritchie46ritchie46 changed the title Add Child Data to Arrow's C FFI implementation. …ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. …Mar 23, 2021
@github-actions

Copy link
Copy Markdown

@apacheapache deleted a comment from github-actionsBotMar 24, 2021
@ritchie46
ritchie46force-pushed the ffi_types branch 2 times, most recently from 151be97 to b6fbf30CompareMarch 24, 2021 07:55
@alamb

Copy link
Copy Markdown
Contributor

I am not an expert in this level of code -- perhaps @jorgecarleitao has time to take a look at this PR?

@jorgecarleitaojorgecarleitao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this, @ritchie46 . it has the right ideas and looks great so far. 💯

I left some comments throughout the code.

My last general comment would be to add this type to the pyarrow-integration-tests crate, which contains real tests against pyarrow, which allow us to validate the behavior against the C++ implementation.

Comment threadrust/arrow/src/datatypes/field.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
@ritchie46

Copy link
Copy Markdown
ContributorAuthor

Thanks a lot for this, @ritchie46 . it has the right ideas and looks great so far. 100

I left some comments throughout the code.

My last general comment would be to add this type to the pyarrow-integration-tests crate, which contains real tests against pyarrow, which allow us to validate the behavior against the C++ implementation.

Cool! I think I've tackled all your comments @jorgecarleitao . I also added a test to the pyarrow-integration-tests-crate. That was a good one, because it did not succeed.

It turns out that we need to provide a name in the FFI_ArrowSchema when we provide child data. This was a null ptr, so I replaced this with an empty "" string, that seemed easiest to me. If you want to tackle that differently, I am open to suggestions.

@nevi-menevi-me left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments, I'm happy with the implementation though. Thanks @ritchie46 , and I apologise for taking long to review this.

Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow-pyarrow-integration-testing/tests/test_sql.py Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
@ritchie46

Copy link
Copy Markdown
ContributorAuthor

Some minor comments, I'm happy with the implementation though. Thanks @ritchie46 , and I apologise for taking long to review this.

No worries! 😄

@codecov-io

Copy link
Copy Markdown

Codecov Report

Merging #9778 (ba51b55) into master (29feea0) will decrease coverage by 0.16%.
The diff coverage is 77.71%.

Impacted file tree graph

@@ Coverage Diff @@## master #9778 +/- ##
==========================================
- Coverage 82.59% 82.43% -0.17% 
==========================================
Files 248 252 +4 Lines 58294 59024 +730 ==========================================
+ Hits 48149 48655 +506 - Misses 10145 10369 +224 
Impacted FilesCoverage Δ
rust/arrow-pyarrow-integration-testing/src/lib.rs0.00% <ø> (ø)
rust/arrow/src/compute/kernels/sort.rs94.37% <ø> (+0.80%)⬆️
rust/arrow/src/compute/kernels/substring.rs98.29% <ø> (ø)
rust/arrow/src/compute/kernels/take.rs96.06% <ø> (-0.01%)⬇️
rust/arrow/src/compute/kernels/window.rs100.00% <ø> (ø)
rust/arrow/src/compute/kernels/zip.rs82.14% <ø> (ø)
rust/arrow/src/compute/util.rs98.92% <ø> (ø)
rust/arrow/src/datatypes/field.rs55.47% <ø> (ø)
rust/arrow/src/ffi.rs80.23% <ø> (+4.00%)⬆️
rust/arrow/src/ipc/reader.rs84.36% <ø> (ø)
... and 90 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ee24399...ba51b55. Read the comment docs.

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@nevi-me there are some new clippy warnings due to new Rust version not related to this PR. Do they need to be fixed?

@nevi-me

Copy link
Copy Markdown
Contributor

@nevi-me there are some new clippy warnings due to new Rust version not related to this PR. Do they need to be fixed?

@ritchie46 I fixed them last night (depending on where in the world one is lol). CI's fine now. The integration failures are known issues at ARROW-12112

@nevi-menevi-me left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM from looking at what you're doing to construct the lists.

@jorgecarleitao can do the honours of the PR blessing approval :)

@nevi-me

Copy link
Copy Markdown
Contributor

It doesn't look like anything needs to be updated in docs/source/status.rst

Comment threadrust/arrow/src/ffi.rs Outdated
@alamb

alamb commented Apr 1, 2021

Copy link
Copy Markdown
Contributor

@ritchie46 / @jorgecarleitao / @nevi-me is this one ready to go? There is one smally clippy lint left which I can fixup but I didn't want to ram this PR through to keep the queue down if it wasn't actually ready

@jorgecarleitaojorgecarleitao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks a lot @ritchie46 for taking the extra mile to have the equality done right :) 💯

Also, thanks a lot @pitrou for your help here. 👍

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@alamb I fixed the clippy issue, so I think we're good to go. :)

@alamb

alamb commented Apr 2, 2021

Copy link
Copy Markdown
Contributor

Integration test failure seems due to https://github.com/apache/arrow/pull/9778/checks?check_run_id=2253660088 (not related to this PR):

-----------------------
/
/arrow/js /
npm WARN tar ENOSPC: no space left on device, write
npm WARN tar ENOSPC: no space left on device, write
npm ERR! cb() never called!

@alambalamb closed this in 2f3ed3aApr 2, 2021
@alamb

alamb commented Apr 2, 2021

Copy link
Copy Markdown
Contributor

Thanks again @ritchie46 👍

@ritchie46

ritchie46 commented Apr 4, 2021

Copy link
Copy Markdown
ContributorAuthor

Hmm.. This is sadly a bit too late. But the current implementation does an invalid read/write. I get a SIGILL if I run this test a 1000 times.

Current thread 0x00007f3bd56f4740 (most recent call first):
File "/home/ritchie46/code/arrow/rust/arrow-pyarrow-integration-testing/tests/test_sql.py", line 89 in test_list_array
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 633 in _callTestMethod
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 676 in run
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 736 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/unittest.py", line 207 in runtest
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 131 in pytest_runtest_call
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 207 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 234 in from_call
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 206 in call_runtest_hook
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 182 in call_and_report
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 96 in runtestprotocol
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 81 in pytest_runtest_protocol
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 270 in pytest_runtestloop
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 246 in _main
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 196 in wrap_session
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 239 in pytest_cmdline_main
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/config/__init__.py", line 91 in main
File "/opt/miniconda3/bin/pytest", line 8 in <module>
Illegal instruction (core dumped)

I hope I can find the location of this invalid read/write. If anybody has an idea of where did could occur that would be very much appreciated.

Some additional info/ thoughts:

  • I cannot reproduce this in the rust tests. Is there a way to interact with C++ arrow from rust? Using valgrind on python creates a lot of noise from the python VM.

  • the location of SIGILL, during the second traversal of create children (called by the children arrays), the Arc::clone of self.array leads to the SIGILL.

@ritchie46

ritchie46 commented Apr 5, 2021

Copy link
Copy Markdown
ContributorAuthor

If I prevent the drop in release array, this issue is resolved but we leak data.

TBH, I am stuck. @pitrou@jorgecarleitao have you got any idea how this can be resolved?

could this be related?

@jorgecarleitao

Copy link
Copy Markdown
Member

I am really sorry, this was sloppiness on my part: I should have checkout the code and go through it more carefully as FFI is always risky stuff. If you think it would take some pressure off, we can revert this PR until we find and fix this.

Regardless, could you run the memory-check to see if we find the problem in our internal roundtrips? Something like

cargo test --lib --feature memory-check -- --test-threads=1

on the rust crate. This counts every alloc/realloc/dealloc over all buffers over all tests and verifies that the sum is zero. The test-threads must be 1 so that tests run sequentially and the last test is the memory check.

I would try to run this before this PR's commit just to make sure that things work as expected since we do not run this as part of our CI. If it passes, then I would try again after this PR's commit.

@pitrou

Copy link
Copy Markdown
Member

@ritchie46 The guidelines for implementers of a release callback here. I would suggest following the example:
https://arrow.apache.org/docs/format/CDataInterface.html#release-callback-semantics-for-producers

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@ritchie46 The guidelines for implementers of a release callback here. I would suggest following the example:
https://arrow.apache.org/docs/format/CDataInterface.html#release-callback-semantics-for-producers

Yes, thank you. I will be going through that.

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

I am really sorry, this was sloppiness on my part: I should have checkout the code and go through it more carefully as FFI is always risky stuff. If you think it would take some pressure off, we can revert this PR until we find and fix this.

Yes, in that case we panic instead of UB, which cleary is better.

Regardless, could you run the memory-check to see if we find the problem in our internal roundtrips? Something like

Will do that.

Some extra info: I realize that we don't have any owned child data in private_data so, maybe the child_data is already dropped.

@pitroupitrou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this seems a bit delicate to get right, I think you should first add tests for roundtripping schemas:

  • roundtrip a schema as is, Rust->Python->Rust
  • roundtrip a schema as is, Python->Rust->Python
  • create a primitive type in Rust, return pa.list(primitive_type) from Python
  • create a list type in Rust, return list_type.value_type from Python

For each case, verify the expected result, also check for allocation/deallocation/leaks.

Once you got that right, you can tackle the array roundtrip issue.

.child_data()
.iter()
.map(|arr| {
let len = arr.len();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand: why isn't try_from called recursively?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because I otherwise cannot pass nullable: bool information from the parent. If should split this up in a function separate from try_from to make this more explicit.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, you'll need to handle recursive types more generally anyway. Think list(list(int8)).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that would not work like this. I will fix that.

a = pyarrow.array([[], None, [1, 2], [4, 5, 6]], pyarrow.list_(pyarrow.int64()))
b = arrow_pyarrow_integration_testing.round_trip(a)

b.validate(full=True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be interesting to add del a above this, to make sure that b keeps the data alive.

Comment threadrust/arrow/src/ffi.rs
FFI_ArrowSchema {
format: CString::new(format).unwrap().into_raw(),
name: std::ptr::null_mut(),
// For child data a non null string is expected and is called item

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only for lists, though. For more general nested types you'll have to take the actual field name.

Comment threadrust/arrow/src/ffi.rs
Comment threadrust/arrow/src/ffi.rs
// at that point the child data is not yet known, but it is also not required to determine
// the buffer length of the list arrays.
"+l" => {
let nullable = schema.flags == 2;

@pitroupitrouApr 5, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be (schema.flags & 2) != 0. Also I'm surprised you're sprinkling magic numbers in the code instead of defining a constant.

Comment threadrust/arrow/src/ffi.rs
// Safety
// Should be set as this is expected from the C FFI definition
debug_assert!(!schema.name.is_null());
let name = unsafe { CString::from_raw(schema.name as *mut c_char) }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem right. The doc for from_raw says:

This should only ever be called with a pointer that was earlier obtained by calling CString::into_raw. Other usage (e.g., trying to take ownership of a string that was allocated by foreign code) is likely to lead to undefined behavior or allocator corruption.

But we are exactly in the case where schema.name can have been allocated by C++ or Python or anything else. It seems instead you should use CStr instead: "Representation of a borrowed C string".

Comment threadrust/arrow/src/ffi.rs
(DataType::Utf8, 2) | (DataType::Binary, 2) => size_of::<u8>() * 8,
(DataType::Utf8, _) | (DataType::Binary, _) => {
(DataType::Utf8, 1) | (DataType::Binary, 1) | (DataType::List(_), 1) => size_of::<i32>() * 8,
(DataType::Utf8, 2) | (DataType::Binary, 2) | (DataType::List(_), 2) => size_of::<u8>() * 8,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lists don't have a buffer number 2.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. I thought it was a validity bitmap and an offset buffer and that the child data was counted differently.

What should be the correct number?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validity bitmap is buffer 0 and the offsets are buffer 1. You are defining a buffer 2 (of u8) which doesn't exist.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validity is the buffer 0, the offsets the buffer 1. The List has no buffer number two. If someone requests buffer 2 from a List Array, we should error instead.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, Thanks!

Comment threadrust/arrow/src/ffi.rs
Sign up for freeto 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.

6 participants

@ritchie46@alamb@codecov-io@nevi-me@jorgecarleitao@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. … - #9778

Closed
ritchie46 wants to merge 12 commits into
apache:masterfrom
ritchie46:ffi_types
Closed

ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. …#9778
ritchie46 wants to merge 12 commits into
apache:masterfrom
ritchie46:ffi_types

Conversation

@ritchie46

Copy link
Copy Markdown
Contributor

This PR adds child data to Arrow's C FFI implementation and implements it for List and LargeList datatypes.

@ritchie46ritchie46 changed the title Add Child Data to Arrow's C FFI implementation. …ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. …Mar 23, 2021
@github-actions

Copy link
Copy Markdown

@apacheapache deleted a comment from github-actionsBotMar 24, 2021
@ritchie46
ritchie46force-pushed the ffi_types branch 2 times, most recently from 151be97 to b6fbf30CompareMarch 24, 2021 07:55
@alamb

Copy link
Copy Markdown
Contributor

I am not an expert in this level of code -- perhaps @jorgecarleitao has time to take a look at this PR?

@jorgecarleitaojorgecarleitao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this, @ritchie46 . it has the right ideas and looks great so far. 💯

I left some comments throughout the code.

My last general comment would be to add this type to the pyarrow-integration-tests crate, which contains real tests against pyarrow, which allow us to validate the behavior against the C++ implementation.

Comment threadrust/arrow/src/datatypes/field.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
@ritchie46

Copy link
Copy Markdown
ContributorAuthor

Thanks a lot for this, @ritchie46 . it has the right ideas and looks great so far. 100

I left some comments throughout the code.

My last general comment would be to add this type to the pyarrow-integration-tests crate, which contains real tests against pyarrow, which allow us to validate the behavior against the C++ implementation.

Cool! I think I've tackled all your comments @jorgecarleitao . I also added a test to the pyarrow-integration-tests-crate. That was a good one, because it did not succeed.

It turns out that we need to provide a name in the FFI_ArrowSchema when we provide child data. This was a null ptr, so I replaced this with an empty "" string, that seemed easiest to me. If you want to tackle that differently, I am open to suggestions.

@nevi-menevi-me left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments, I'm happy with the implementation though. Thanks @ritchie46 , and I apologise for taking long to review this.

Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow-pyarrow-integration-testing/tests/test_sql.py Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
@ritchie46

Copy link
Copy Markdown
ContributorAuthor

Some minor comments, I'm happy with the implementation though. Thanks @ritchie46 , and I apologise for taking long to review this.

No worries! 😄

@codecov-io

Copy link
Copy Markdown

Codecov Report

Merging #9778 (ba51b55) into master (29feea0) will decrease coverage by 0.16%.
The diff coverage is 77.71%.

Impacted file tree graph

@@ Coverage Diff @@## master #9778 +/- ##
==========================================
- Coverage 82.59% 82.43% -0.17% 
==========================================
Files 248 252 +4 Lines 58294 59024 +730 ==========================================
+ Hits 48149 48655 +506 - Misses 10145 10369 +224 
Impacted FilesCoverage Δ
rust/arrow-pyarrow-integration-testing/src/lib.rs0.00% <ø> (ø)
rust/arrow/src/compute/kernels/sort.rs94.37% <ø> (+0.80%)⬆️
rust/arrow/src/compute/kernels/substring.rs98.29% <ø> (ø)
rust/arrow/src/compute/kernels/take.rs96.06% <ø> (-0.01%)⬇️
rust/arrow/src/compute/kernels/window.rs100.00% <ø> (ø)
rust/arrow/src/compute/kernels/zip.rs82.14% <ø> (ø)
rust/arrow/src/compute/util.rs98.92% <ø> (ø)
rust/arrow/src/datatypes/field.rs55.47% <ø> (ø)
rust/arrow/src/ffi.rs80.23% <ø> (+4.00%)⬆️
rust/arrow/src/ipc/reader.rs84.36% <ø> (ø)
... and 90 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ee24399...ba51b55. Read the comment docs.

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@nevi-me there are some new clippy warnings due to new Rust version not related to this PR. Do they need to be fixed?

@nevi-me

Copy link
Copy Markdown
Contributor

@nevi-me there are some new clippy warnings due to new Rust version not related to this PR. Do they need to be fixed?

@ritchie46 I fixed them last night (depending on where in the world one is lol). CI's fine now. The integration failures are known issues at ARROW-12112

@nevi-menevi-me left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM from looking at what you're doing to construct the lists.

@jorgecarleitao can do the honours of the PR blessing approval :)

@nevi-me

Copy link
Copy Markdown
Contributor

It doesn't look like anything needs to be updated in docs/source/status.rst

Comment threadrust/arrow/src/ffi.rs Outdated
@alamb

alamb commented Apr 1, 2021

Copy link
Copy Markdown
Contributor

@ritchie46 / @jorgecarleitao / @nevi-me is this one ready to go? There is one smally clippy lint left which I can fixup but I didn't want to ram this PR through to keep the queue down if it wasn't actually ready

@jorgecarleitaojorgecarleitao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks a lot @ritchie46 for taking the extra mile to have the equality done right :) 💯

Also, thanks a lot @pitrou for your help here. 👍

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@alamb I fixed the clippy issue, so I think we're good to go. :)

@alamb

alamb commented Apr 2, 2021

Copy link
Copy Markdown
Contributor

Integration test failure seems due to https://github.com/apache/arrow/pull/9778/checks?check_run_id=2253660088 (not related to this PR):

-----------------------
/
/arrow/js /
npm WARN tar ENOSPC: no space left on device, write
npm WARN tar ENOSPC: no space left on device, write
npm ERR! cb() never called!

@alambalamb closed this in 2f3ed3aApr 2, 2021
@alamb

alamb commented Apr 2, 2021

Copy link
Copy Markdown
Contributor

Thanks again @ritchie46 👍

@ritchie46

ritchie46 commented Apr 4, 2021

Copy link
Copy Markdown
ContributorAuthor

Hmm.. This is sadly a bit too late. But the current implementation does an invalid read/write. I get a SIGILL if I run this test a 1000 times.

Current thread 0x00007f3bd56f4740 (most recent call first):
File "/home/ritchie46/code/arrow/rust/arrow-pyarrow-integration-testing/tests/test_sql.py", line 89 in test_list_array
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 633 in _callTestMethod
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 676 in run
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 736 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/unittest.py", line 207 in runtest
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 131 in pytest_runtest_call
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 207 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 234 in from_call
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 206 in call_runtest_hook
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 182 in call_and_report
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 96 in runtestprotocol
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 81 in pytest_runtest_protocol
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 270 in pytest_runtestloop
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 246 in _main
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 196 in wrap_session
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 239 in pytest_cmdline_main
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/config/__init__.py", line 91 in main
File "/opt/miniconda3/bin/pytest", line 8 in <module>
Illegal instruction (core dumped)

I hope I can find the location of this invalid read/write. If anybody has an idea of where did could occur that would be very much appreciated.

Some additional info/ thoughts:

  • I cannot reproduce this in the rust tests. Is there a way to interact with C++ arrow from rust? Using valgrind on python creates a lot of noise from the python VM.

  • the location of SIGILL, during the second traversal of create children (called by the children arrays), the Arc::clone of self.array leads to the SIGILL.

@ritchie46

ritchie46 commented Apr 5, 2021

Copy link
Copy Markdown
ContributorAuthor

If I prevent the drop in release array, this issue is resolved but we leak data.

TBH, I am stuck. @pitrou@jorgecarleitao have you got any idea how this can be resolved?

could this be related?

@jorgecarleitao

Copy link
Copy Markdown
Member

I am really sorry, this was sloppiness on my part: I should have checkout the code and go through it more carefully as FFI is always risky stuff. If you think it would take some pressure off, we can revert this PR until we find and fix this.

Regardless, could you run the memory-check to see if we find the problem in our internal roundtrips? Something like

cargo test --lib --feature memory-check -- --test-threads=1

on the rust crate. This counts every alloc/realloc/dealloc over all buffers over all tests and verifies that the sum is zero. The test-threads must be 1 so that tests run sequentially and the last test is the memory check.

I would try to run this before this PR's commit just to make sure that things work as expected since we do not run this as part of our CI. If it passes, then I would try again after this PR's commit.

@pitrou

Copy link
Copy Markdown
Member

@ritchie46 The guidelines for implementers of a release callback here. I would suggest following the example:
https://arrow.apache.org/docs/format/CDataInterface.html#release-callback-semantics-for-producers

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@ritchie46 The guidelines for implementers of a release callback here. I would suggest following the example:
https://arrow.apache.org/docs/format/CDataInterface.html#release-callback-semantics-for-producers

Yes, thank you. I will be going through that.

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

I am really sorry, this was sloppiness on my part: I should have checkout the code and go through it more carefully as FFI is always risky stuff. If you think it would take some pressure off, we can revert this PR until we find and fix this.

Yes, in that case we panic instead of UB, which cleary is better.

Regardless, could you run the memory-check to see if we find the problem in our internal roundtrips? Something like

Will do that.

Some extra info: I realize that we don't have any owned child data in private_data so, maybe the child_data is already dropped.

@pitroupitrou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this seems a bit delicate to get right, I think you should first add tests for roundtripping schemas:

  • roundtrip a schema as is, Rust->Python->Rust
  • roundtrip a schema as is, Python->Rust->Python
  • create a primitive type in Rust, return pa.list(primitive_type) from Python
  • create a list type in Rust, return list_type.value_type from Python

For each case, verify the expected result, also check for allocation/deallocation/leaks.

Once you got that right, you can tackle the array roundtrip issue.

.child_data()
.iter()
.map(|arr| {
let len = arr.len();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand: why isn't try_from called recursively?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because I otherwise cannot pass nullable: bool information from the parent. If should split this up in a function separate from try_from to make this more explicit.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, you'll need to handle recursive types more generally anyway. Think list(list(int8)).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that would not work like this. I will fix that.

a = pyarrow.array([[], None, [1, 2], [4, 5, 6]], pyarrow.list_(pyarrow.int64()))
b = arrow_pyarrow_integration_testing.round_trip(a)

b.validate(full=True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be interesting to add del a above this, to make sure that b keeps the data alive.

Comment threadrust/arrow/src/ffi.rs
FFI_ArrowSchema {
format: CString::new(format).unwrap().into_raw(),
name: std::ptr::null_mut(),
// For child data a non null string is expected and is called item

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only for lists, though. For more general nested types you'll have to take the actual field name.

Comment threadrust/arrow/src/ffi.rs
Comment threadrust/arrow/src/ffi.rs
// at that point the child data is not yet known, but it is also not required to determine
// the buffer length of the list arrays.
"+l" => {
let nullable = schema.flags == 2;

@pitroupitrouApr 5, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be (schema.flags & 2) != 0. Also I'm surprised you're sprinkling magic numbers in the code instead of defining a constant.

Comment threadrust/arrow/src/ffi.rs
// Safety
// Should be set as this is expected from the C FFI definition
debug_assert!(!schema.name.is_null());
let name = unsafe { CString::from_raw(schema.name as *mut c_char) }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem right. The doc for from_raw says:

This should only ever be called with a pointer that was earlier obtained by calling CString::into_raw. Other usage (e.g., trying to take ownership of a string that was allocated by foreign code) is likely to lead to undefined behavior or allocator corruption.

But we are exactly in the case where schema.name can have been allocated by C++ or Python or anything else. It seems instead you should use CStr instead: "Representation of a borrowed C string".

Comment threadrust/arrow/src/ffi.rs
(DataType::Utf8, 2) | (DataType::Binary, 2) => size_of::<u8>() * 8,
(DataType::Utf8, _) | (DataType::Binary, _) => {
(DataType::Utf8, 1) | (DataType::Binary, 1) | (DataType::List(_), 1) => size_of::<i32>() * 8,
(DataType::Utf8, 2) | (DataType::Binary, 2) | (DataType::List(_), 2) => size_of::<u8>() * 8,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lists don't have a buffer number 2.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. I thought it was a validity bitmap and an offset buffer and that the child data was counted differently.

What should be the correct number?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validity bitmap is buffer 0 and the offsets are buffer 1. You are defining a buffer 2 (of u8) which doesn't exist.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validity is the buffer 0, the offsets the buffer 1. The List has no buffer number two. If someone requests buffer 2 from a List Array, we should error instead.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, Thanks!

Comment threadrust/arrow/src/ffi.rs
Sign up for freeto 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.

6 participants

@ritchie46@alamb@codecov-io@nevi-me@jorgecarleitao@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. … - #9778

Closed
ritchie46 wants to merge 12 commits into
apache:masterfrom
ritchie46:ffi_types
Closed

ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. …#9778
ritchie46 wants to merge 12 commits into
apache:masterfrom
ritchie46:ffi_types

Conversation

@ritchie46

Copy link
Copy Markdown
Contributor

This PR adds child data to Arrow's C FFI implementation and implements it for List and LargeList datatypes.

@ritchie46ritchie46 changed the title Add Child Data to Arrow's C FFI implementation. …ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. …Mar 23, 2021
@github-actions

Copy link
Copy Markdown

@apacheapache deleted a comment from github-actionsBotMar 24, 2021
@ritchie46
ritchie46force-pushed the ffi_types branch 2 times, most recently from 151be97 to b6fbf30CompareMarch 24, 2021 07:55
@alamb

Copy link
Copy Markdown
Contributor

I am not an expert in this level of code -- perhaps @jorgecarleitao has time to take a look at this PR?

@jorgecarleitaojorgecarleitao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this, @ritchie46 . it has the right ideas and looks great so far. 💯

I left some comments throughout the code.

My last general comment would be to add this type to the pyarrow-integration-tests crate, which contains real tests against pyarrow, which allow us to validate the behavior against the C++ implementation.

Comment threadrust/arrow/src/datatypes/field.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
@ritchie46

Copy link
Copy Markdown
ContributorAuthor

Thanks a lot for this, @ritchie46 . it has the right ideas and looks great so far. 100

I left some comments throughout the code.

My last general comment would be to add this type to the pyarrow-integration-tests crate, which contains real tests against pyarrow, which allow us to validate the behavior against the C++ implementation.

Cool! I think I've tackled all your comments @jorgecarleitao . I also added a test to the pyarrow-integration-tests-crate. That was a good one, because it did not succeed.

It turns out that we need to provide a name in the FFI_ArrowSchema when we provide child data. This was a null ptr, so I replaced this with an empty "" string, that seemed easiest to me. If you want to tackle that differently, I am open to suggestions.

@nevi-menevi-me left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments, I'm happy with the implementation though. Thanks @ritchie46 , and I apologise for taking long to review this.

Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow-pyarrow-integration-testing/tests/test_sql.py Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
@ritchie46

Copy link
Copy Markdown
ContributorAuthor

Some minor comments, I'm happy with the implementation though. Thanks @ritchie46 , and I apologise for taking long to review this.

No worries! 😄

@codecov-io

Copy link
Copy Markdown

Codecov Report

Merging #9778 (ba51b55) into master (29feea0) will decrease coverage by 0.16%.
The diff coverage is 77.71%.

Impacted file tree graph

@@ Coverage Diff @@## master #9778 +/- ##
==========================================
- Coverage 82.59% 82.43% -0.17% 
==========================================
Files 248 252 +4 Lines 58294 59024 +730 ==========================================
+ Hits 48149 48655 +506 - Misses 10145 10369 +224 
Impacted FilesCoverage Δ
rust/arrow-pyarrow-integration-testing/src/lib.rs0.00% <ø> (ø)
rust/arrow/src/compute/kernels/sort.rs94.37% <ø> (+0.80%)⬆️
rust/arrow/src/compute/kernels/substring.rs98.29% <ø> (ø)
rust/arrow/src/compute/kernels/take.rs96.06% <ø> (-0.01%)⬇️
rust/arrow/src/compute/kernels/window.rs100.00% <ø> (ø)
rust/arrow/src/compute/kernels/zip.rs82.14% <ø> (ø)
rust/arrow/src/compute/util.rs98.92% <ø> (ø)
rust/arrow/src/datatypes/field.rs55.47% <ø> (ø)
rust/arrow/src/ffi.rs80.23% <ø> (+4.00%)⬆️
rust/arrow/src/ipc/reader.rs84.36% <ø> (ø)
... and 90 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ee24399...ba51b55. Read the comment docs.

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@nevi-me there are some new clippy warnings due to new Rust version not related to this PR. Do they need to be fixed?

@nevi-me

Copy link
Copy Markdown
Contributor

@nevi-me there are some new clippy warnings due to new Rust version not related to this PR. Do they need to be fixed?

@ritchie46 I fixed them last night (depending on where in the world one is lol). CI's fine now. The integration failures are known issues at ARROW-12112

@nevi-menevi-me left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM from looking at what you're doing to construct the lists.

@jorgecarleitao can do the honours of the PR blessing approval :)

@nevi-me

Copy link
Copy Markdown
Contributor

It doesn't look like anything needs to be updated in docs/source/status.rst

Comment threadrust/arrow/src/ffi.rs Outdated
@alamb

alamb commented Apr 1, 2021

Copy link
Copy Markdown
Contributor

@ritchie46 / @jorgecarleitao / @nevi-me is this one ready to go? There is one smally clippy lint left which I can fixup but I didn't want to ram this PR through to keep the queue down if it wasn't actually ready

@jorgecarleitaojorgecarleitao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks a lot @ritchie46 for taking the extra mile to have the equality done right :) 💯

Also, thanks a lot @pitrou for your help here. 👍

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@alamb I fixed the clippy issue, so I think we're good to go. :)

@alamb

alamb commented Apr 2, 2021

Copy link
Copy Markdown
Contributor

Integration test failure seems due to https://github.com/apache/arrow/pull/9778/checks?check_run_id=2253660088 (not related to this PR):

-----------------------
/
/arrow/js /
npm WARN tar ENOSPC: no space left on device, write
npm WARN tar ENOSPC: no space left on device, write
npm ERR! cb() never called!

@alambalamb closed this in 2f3ed3aApr 2, 2021
@alamb

alamb commented Apr 2, 2021

Copy link
Copy Markdown
Contributor

Thanks again @ritchie46 👍

@ritchie46

ritchie46 commented Apr 4, 2021

Copy link
Copy Markdown
ContributorAuthor

Hmm.. This is sadly a bit too late. But the current implementation does an invalid read/write. I get a SIGILL if I run this test a 1000 times.

Current thread 0x00007f3bd56f4740 (most recent call first):
File "/home/ritchie46/code/arrow/rust/arrow-pyarrow-integration-testing/tests/test_sql.py", line 89 in test_list_array
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 633 in _callTestMethod
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 676 in run
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 736 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/unittest.py", line 207 in runtest
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 131 in pytest_runtest_call
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 207 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 234 in from_call
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 206 in call_runtest_hook
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 182 in call_and_report
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 96 in runtestprotocol
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 81 in pytest_runtest_protocol
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 270 in pytest_runtestloop
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 246 in _main
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 196 in wrap_session
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 239 in pytest_cmdline_main
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/config/__init__.py", line 91 in main
File "/opt/miniconda3/bin/pytest", line 8 in <module>
Illegal instruction (core dumped)

I hope I can find the location of this invalid read/write. If anybody has an idea of where did could occur that would be very much appreciated.

Some additional info/ thoughts:

  • I cannot reproduce this in the rust tests. Is there a way to interact with C++ arrow from rust? Using valgrind on python creates a lot of noise from the python VM.

  • the location of SIGILL, during the second traversal of create children (called by the children arrays), the Arc::clone of self.array leads to the SIGILL.

@ritchie46

ritchie46 commented Apr 5, 2021

Copy link
Copy Markdown
ContributorAuthor

If I prevent the drop in release array, this issue is resolved but we leak data.

TBH, I am stuck. @pitrou@jorgecarleitao have you got any idea how this can be resolved?

could this be related?

@jorgecarleitao

Copy link
Copy Markdown
Member

I am really sorry, this was sloppiness on my part: I should have checkout the code and go through it more carefully as FFI is always risky stuff. If you think it would take some pressure off, we can revert this PR until we find and fix this.

Regardless, could you run the memory-check to see if we find the problem in our internal roundtrips? Something like

cargo test --lib --feature memory-check -- --test-threads=1

on the rust crate. This counts every alloc/realloc/dealloc over all buffers over all tests and verifies that the sum is zero. The test-threads must be 1 so that tests run sequentially and the last test is the memory check.

I would try to run this before this PR's commit just to make sure that things work as expected since we do not run this as part of our CI. If it passes, then I would try again after this PR's commit.

@pitrou

Copy link
Copy Markdown
Member

@ritchie46 The guidelines for implementers of a release callback here. I would suggest following the example:
https://arrow.apache.org/docs/format/CDataInterface.html#release-callback-semantics-for-producers

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@ritchie46 The guidelines for implementers of a release callback here. I would suggest following the example:
https://arrow.apache.org/docs/format/CDataInterface.html#release-callback-semantics-for-producers

Yes, thank you. I will be going through that.

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

I am really sorry, this was sloppiness on my part: I should have checkout the code and go through it more carefully as FFI is always risky stuff. If you think it would take some pressure off, we can revert this PR until we find and fix this.

Yes, in that case we panic instead of UB, which cleary is better.

Regardless, could you run the memory-check to see if we find the problem in our internal roundtrips? Something like

Will do that.

Some extra info: I realize that we don't have any owned child data in private_data so, maybe the child_data is already dropped.

@pitroupitrou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this seems a bit delicate to get right, I think you should first add tests for roundtripping schemas:

  • roundtrip a schema as is, Rust->Python->Rust
  • roundtrip a schema as is, Python->Rust->Python
  • create a primitive type in Rust, return pa.list(primitive_type) from Python
  • create a list type in Rust, return list_type.value_type from Python

For each case, verify the expected result, also check for allocation/deallocation/leaks.

Once you got that right, you can tackle the array roundtrip issue.

.child_data()
.iter()
.map(|arr| {
let len = arr.len();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand: why isn't try_from called recursively?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because I otherwise cannot pass nullable: bool information from the parent. If should split this up in a function separate from try_from to make this more explicit.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, you'll need to handle recursive types more generally anyway. Think list(list(int8)).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that would not work like this. I will fix that.

a = pyarrow.array([[], None, [1, 2], [4, 5, 6]], pyarrow.list_(pyarrow.int64()))
b = arrow_pyarrow_integration_testing.round_trip(a)

b.validate(full=True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be interesting to add del a above this, to make sure that b keeps the data alive.

Comment threadrust/arrow/src/ffi.rs
FFI_ArrowSchema {
format: CString::new(format).unwrap().into_raw(),
name: std::ptr::null_mut(),
// For child data a non null string is expected and is called item

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only for lists, though. For more general nested types you'll have to take the actual field name.

Comment threadrust/arrow/src/ffi.rs
Comment threadrust/arrow/src/ffi.rs
// at that point the child data is not yet known, but it is also not required to determine
// the buffer length of the list arrays.
"+l" => {
let nullable = schema.flags == 2;

@pitroupitrouApr 5, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be (schema.flags & 2) != 0. Also I'm surprised you're sprinkling magic numbers in the code instead of defining a constant.

Comment threadrust/arrow/src/ffi.rs
// Safety
// Should be set as this is expected from the C FFI definition
debug_assert!(!schema.name.is_null());
let name = unsafe { CString::from_raw(schema.name as *mut c_char) }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem right. The doc for from_raw says:

This should only ever be called with a pointer that was earlier obtained by calling CString::into_raw. Other usage (e.g., trying to take ownership of a string that was allocated by foreign code) is likely to lead to undefined behavior or allocator corruption.

But we are exactly in the case where schema.name can have been allocated by C++ or Python or anything else. It seems instead you should use CStr instead: "Representation of a borrowed C string".

Comment threadrust/arrow/src/ffi.rs
(DataType::Utf8, 2) | (DataType::Binary, 2) => size_of::<u8>() * 8,
(DataType::Utf8, _) | (DataType::Binary, _) => {
(DataType::Utf8, 1) | (DataType::Binary, 1) | (DataType::List(_), 1) => size_of::<i32>() * 8,
(DataType::Utf8, 2) | (DataType::Binary, 2) | (DataType::List(_), 2) => size_of::<u8>() * 8,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lists don't have a buffer number 2.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. I thought it was a validity bitmap and an offset buffer and that the child data was counted differently.

What should be the correct number?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validity bitmap is buffer 0 and the offsets are buffer 1. You are defining a buffer 2 (of u8) which doesn't exist.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validity is the buffer 0, the offsets the buffer 1. The List has no buffer number two. If someone requests buffer 2 from a List Array, we should error instead.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, Thanks!

Comment threadrust/arrow/src/ffi.rs
Sign up for freeto 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.

6 participants

@ritchie46@alamb@codecov-io@nevi-me@jorgecarleitao@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. … - #9778

Closed
ritchie46 wants to merge 12 commits into
apache:masterfrom
ritchie46:ffi_types
Closed

ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. …#9778
ritchie46 wants to merge 12 commits into
apache:masterfrom
ritchie46:ffi_types

Conversation

@ritchie46

Copy link
Copy Markdown
Contributor

This PR adds child data to Arrow's C FFI implementation and implements it for List and LargeList datatypes.

@ritchie46ritchie46 changed the title Add Child Data to Arrow's C FFI implementation. …ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. …Mar 23, 2021
@github-actions

Copy link
Copy Markdown

@apacheapache deleted a comment from github-actionsBotMar 24, 2021
@ritchie46
ritchie46force-pushed the ffi_types branch 2 times, most recently from 151be97 to b6fbf30CompareMarch 24, 2021 07:55
@alamb

Copy link
Copy Markdown
Contributor

I am not an expert in this level of code -- perhaps @jorgecarleitao has time to take a look at this PR?

@jorgecarleitaojorgecarleitao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this, @ritchie46 . it has the right ideas and looks great so far. 💯

I left some comments throughout the code.

My last general comment would be to add this type to the pyarrow-integration-tests crate, which contains real tests against pyarrow, which allow us to validate the behavior against the C++ implementation.

Comment threadrust/arrow/src/datatypes/field.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
@ritchie46

Copy link
Copy Markdown
ContributorAuthor

Thanks a lot for this, @ritchie46 . it has the right ideas and looks great so far. 100

I left some comments throughout the code.

My last general comment would be to add this type to the pyarrow-integration-tests crate, which contains real tests against pyarrow, which allow us to validate the behavior against the C++ implementation.

Cool! I think I've tackled all your comments @jorgecarleitao . I also added a test to the pyarrow-integration-tests-crate. That was a good one, because it did not succeed.

It turns out that we need to provide a name in the FFI_ArrowSchema when we provide child data. This was a null ptr, so I replaced this with an empty "" string, that seemed easiest to me. If you want to tackle that differently, I am open to suggestions.

@nevi-menevi-me left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments, I'm happy with the implementation though. Thanks @ritchie46 , and I apologise for taking long to review this.

Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow-pyarrow-integration-testing/tests/test_sql.py Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
@ritchie46

Copy link
Copy Markdown
ContributorAuthor

Some minor comments, I'm happy with the implementation though. Thanks @ritchie46 , and I apologise for taking long to review this.

No worries! 😄

@codecov-io

Copy link
Copy Markdown

Codecov Report

Merging #9778 (ba51b55) into master (29feea0) will decrease coverage by 0.16%.
The diff coverage is 77.71%.

Impacted file tree graph

@@ Coverage Diff @@## master #9778 +/- ##
==========================================
- Coverage 82.59% 82.43% -0.17% 
==========================================
Files 248 252 +4 Lines 58294 59024 +730 ==========================================
+ Hits 48149 48655 +506 - Misses 10145 10369 +224 
Impacted FilesCoverage Δ
rust/arrow-pyarrow-integration-testing/src/lib.rs0.00% <ø> (ø)
rust/arrow/src/compute/kernels/sort.rs94.37% <ø> (+0.80%)⬆️
rust/arrow/src/compute/kernels/substring.rs98.29% <ø> (ø)
rust/arrow/src/compute/kernels/take.rs96.06% <ø> (-0.01%)⬇️
rust/arrow/src/compute/kernels/window.rs100.00% <ø> (ø)
rust/arrow/src/compute/kernels/zip.rs82.14% <ø> (ø)
rust/arrow/src/compute/util.rs98.92% <ø> (ø)
rust/arrow/src/datatypes/field.rs55.47% <ø> (ø)
rust/arrow/src/ffi.rs80.23% <ø> (+4.00%)⬆️
rust/arrow/src/ipc/reader.rs84.36% <ø> (ø)
... and 90 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ee24399...ba51b55. Read the comment docs.

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@nevi-me there are some new clippy warnings due to new Rust version not related to this PR. Do they need to be fixed?

@nevi-me

Copy link
Copy Markdown
Contributor

@nevi-me there are some new clippy warnings due to new Rust version not related to this PR. Do they need to be fixed?

@ritchie46 I fixed them last night (depending on where in the world one is lol). CI's fine now. The integration failures are known issues at ARROW-12112

@nevi-menevi-me left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM from looking at what you're doing to construct the lists.

@jorgecarleitao can do the honours of the PR blessing approval :)

@nevi-me

Copy link
Copy Markdown
Contributor

It doesn't look like anything needs to be updated in docs/source/status.rst

Comment threadrust/arrow/src/ffi.rs Outdated
@alamb

alamb commented Apr 1, 2021

Copy link
Copy Markdown
Contributor

@ritchie46 / @jorgecarleitao / @nevi-me is this one ready to go? There is one smally clippy lint left which I can fixup but I didn't want to ram this PR through to keep the queue down if it wasn't actually ready

@jorgecarleitaojorgecarleitao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks a lot @ritchie46 for taking the extra mile to have the equality done right :) 💯

Also, thanks a lot @pitrou for your help here. 👍

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@alamb I fixed the clippy issue, so I think we're good to go. :)

@alamb

alamb commented Apr 2, 2021

Copy link
Copy Markdown
Contributor

Integration test failure seems due to https://github.com/apache/arrow/pull/9778/checks?check_run_id=2253660088 (not related to this PR):

-----------------------
/
/arrow/js /
npm WARN tar ENOSPC: no space left on device, write
npm WARN tar ENOSPC: no space left on device, write
npm ERR! cb() never called!

@alambalamb closed this in 2f3ed3aApr 2, 2021
@alamb

alamb commented Apr 2, 2021

Copy link
Copy Markdown
Contributor

Thanks again @ritchie46 👍

@ritchie46

ritchie46 commented Apr 4, 2021

Copy link
Copy Markdown
ContributorAuthor

Hmm.. This is sadly a bit too late. But the current implementation does an invalid read/write. I get a SIGILL if I run this test a 1000 times.

Current thread 0x00007f3bd56f4740 (most recent call first):
File "/home/ritchie46/code/arrow/rust/arrow-pyarrow-integration-testing/tests/test_sql.py", line 89 in test_list_array
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 633 in _callTestMethod
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 676 in run
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 736 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/unittest.py", line 207 in runtest
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 131 in pytest_runtest_call
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 207 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 234 in from_call
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 206 in call_runtest_hook
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 182 in call_and_report
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 96 in runtestprotocol
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 81 in pytest_runtest_protocol
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 270 in pytest_runtestloop
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 246 in _main
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 196 in wrap_session
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 239 in pytest_cmdline_main
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/config/__init__.py", line 91 in main
File "/opt/miniconda3/bin/pytest", line 8 in <module>
Illegal instruction (core dumped)

I hope I can find the location of this invalid read/write. If anybody has an idea of where did could occur that would be very much appreciated.

Some additional info/ thoughts:

  • I cannot reproduce this in the rust tests. Is there a way to interact with C++ arrow from rust? Using valgrind on python creates a lot of noise from the python VM.

  • the location of SIGILL, during the second traversal of create children (called by the children arrays), the Arc::clone of self.array leads to the SIGILL.

@ritchie46

ritchie46 commented Apr 5, 2021

Copy link
Copy Markdown
ContributorAuthor

If I prevent the drop in release array, this issue is resolved but we leak data.

TBH, I am stuck. @pitrou@jorgecarleitao have you got any idea how this can be resolved?

could this be related?

@jorgecarleitao

Copy link
Copy Markdown
Member

I am really sorry, this was sloppiness on my part: I should have checkout the code and go through it more carefully as FFI is always risky stuff. If you think it would take some pressure off, we can revert this PR until we find and fix this.

Regardless, could you run the memory-check to see if we find the problem in our internal roundtrips? Something like

cargo test --lib --feature memory-check -- --test-threads=1

on the rust crate. This counts every alloc/realloc/dealloc over all buffers over all tests and verifies that the sum is zero. The test-threads must be 1 so that tests run sequentially and the last test is the memory check.

I would try to run this before this PR's commit just to make sure that things work as expected since we do not run this as part of our CI. If it passes, then I would try again after this PR's commit.

@pitrou

Copy link
Copy Markdown
Member

@ritchie46 The guidelines for implementers of a release callback here. I would suggest following the example:
https://arrow.apache.org/docs/format/CDataInterface.html#release-callback-semantics-for-producers

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@ritchie46 The guidelines for implementers of a release callback here. I would suggest following the example:
https://arrow.apache.org/docs/format/CDataInterface.html#release-callback-semantics-for-producers

Yes, thank you. I will be going through that.

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

I am really sorry, this was sloppiness on my part: I should have checkout the code and go through it more carefully as FFI is always risky stuff. If you think it would take some pressure off, we can revert this PR until we find and fix this.

Yes, in that case we panic instead of UB, which cleary is better.

Regardless, could you run the memory-check to see if we find the problem in our internal roundtrips? Something like

Will do that.

Some extra info: I realize that we don't have any owned child data in private_data so, maybe the child_data is already dropped.

@pitroupitrou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this seems a bit delicate to get right, I think you should first add tests for roundtripping schemas:

  • roundtrip a schema as is, Rust->Python->Rust
  • roundtrip a schema as is, Python->Rust->Python
  • create a primitive type in Rust, return pa.list(primitive_type) from Python
  • create a list type in Rust, return list_type.value_type from Python

For each case, verify the expected result, also check for allocation/deallocation/leaks.

Once you got that right, you can tackle the array roundtrip issue.

.child_data()
.iter()
.map(|arr| {
let len = arr.len();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand: why isn't try_from called recursively?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because I otherwise cannot pass nullable: bool information from the parent. If should split this up in a function separate from try_from to make this more explicit.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, you'll need to handle recursive types more generally anyway. Think list(list(int8)).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that would not work like this. I will fix that.

a = pyarrow.array([[], None, [1, 2], [4, 5, 6]], pyarrow.list_(pyarrow.int64()))
b = arrow_pyarrow_integration_testing.round_trip(a)

b.validate(full=True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be interesting to add del a above this, to make sure that b keeps the data alive.

Comment threadrust/arrow/src/ffi.rs
FFI_ArrowSchema {
format: CString::new(format).unwrap().into_raw(),
name: std::ptr::null_mut(),
// For child data a non null string is expected and is called item

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only for lists, though. For more general nested types you'll have to take the actual field name.

Comment threadrust/arrow/src/ffi.rs
Comment threadrust/arrow/src/ffi.rs
// at that point the child data is not yet known, but it is also not required to determine
// the buffer length of the list arrays.
"+l" => {
let nullable = schema.flags == 2;

@pitroupitrouApr 5, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be (schema.flags & 2) != 0. Also I'm surprised you're sprinkling magic numbers in the code instead of defining a constant.

Comment threadrust/arrow/src/ffi.rs
// Safety
// Should be set as this is expected from the C FFI definition
debug_assert!(!schema.name.is_null());
let name = unsafe { CString::from_raw(schema.name as *mut c_char) }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem right. The doc for from_raw says:

This should only ever be called with a pointer that was earlier obtained by calling CString::into_raw. Other usage (e.g., trying to take ownership of a string that was allocated by foreign code) is likely to lead to undefined behavior or allocator corruption.

But we are exactly in the case where schema.name can have been allocated by C++ or Python or anything else. It seems instead you should use CStr instead: "Representation of a borrowed C string".

Comment threadrust/arrow/src/ffi.rs
(DataType::Utf8, 2) | (DataType::Binary, 2) => size_of::<u8>() * 8,
(DataType::Utf8, _) | (DataType::Binary, _) => {
(DataType::Utf8, 1) | (DataType::Binary, 1) | (DataType::List(_), 1) => size_of::<i32>() * 8,
(DataType::Utf8, 2) | (DataType::Binary, 2) | (DataType::List(_), 2) => size_of::<u8>() * 8,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lists don't have a buffer number 2.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. I thought it was a validity bitmap and an offset buffer and that the child data was counted differently.

What should be the correct number?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validity bitmap is buffer 0 and the offsets are buffer 1. You are defining a buffer 2 (of u8) which doesn't exist.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validity is the buffer 0, the offsets the buffer 1. The List has no buffer number two. If someone requests buffer 2 from a List Array, we should error instead.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, Thanks!

Comment threadrust/arrow/src/ffi.rs
Sign up for freeto 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.

6 participants

@ritchie46@alamb@codecov-io@nevi-me@jorgecarleitao@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. … - #9778

Closed
ritchie46 wants to merge 12 commits into
apache:masterfrom
ritchie46:ffi_types
Closed

ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. …#9778
ritchie46 wants to merge 12 commits into
apache:masterfrom
ritchie46:ffi_types

Conversation

@ritchie46

Copy link
Copy Markdown
Contributor

This PR adds child data to Arrow's C FFI implementation and implements it for List and LargeList datatypes.

@ritchie46ritchie46 changed the title Add Child Data to Arrow's C FFI implementation. …ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. …Mar 23, 2021
@github-actions

Copy link
Copy Markdown

@apacheapache deleted a comment from github-actionsBotMar 24, 2021
@ritchie46
ritchie46force-pushed the ffi_types branch 2 times, most recently from 151be97 to b6fbf30CompareMarch 24, 2021 07:55
@alamb

Copy link
Copy Markdown
Contributor

I am not an expert in this level of code -- perhaps @jorgecarleitao has time to take a look at this PR?

@jorgecarleitaojorgecarleitao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this, @ritchie46 . it has the right ideas and looks great so far. 💯

I left some comments throughout the code.

My last general comment would be to add this type to the pyarrow-integration-tests crate, which contains real tests against pyarrow, which allow us to validate the behavior against the C++ implementation.

Comment threadrust/arrow/src/datatypes/field.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
@ritchie46

Copy link
Copy Markdown
ContributorAuthor

Thanks a lot for this, @ritchie46 . it has the right ideas and looks great so far. 100

I left some comments throughout the code.

My last general comment would be to add this type to the pyarrow-integration-tests crate, which contains real tests against pyarrow, which allow us to validate the behavior against the C++ implementation.

Cool! I think I've tackled all your comments @jorgecarleitao . I also added a test to the pyarrow-integration-tests-crate. That was a good one, because it did not succeed.

It turns out that we need to provide a name in the FFI_ArrowSchema when we provide child data. This was a null ptr, so I replaced this with an empty "" string, that seemed easiest to me. If you want to tackle that differently, I am open to suggestions.

@nevi-menevi-me left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments, I'm happy with the implementation though. Thanks @ritchie46 , and I apologise for taking long to review this.

Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow-pyarrow-integration-testing/tests/test_sql.py Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
@ritchie46

Copy link
Copy Markdown
ContributorAuthor

Some minor comments, I'm happy with the implementation though. Thanks @ritchie46 , and I apologise for taking long to review this.

No worries! 😄

@codecov-io

Copy link
Copy Markdown

Codecov Report

Merging #9778 (ba51b55) into master (29feea0) will decrease coverage by 0.16%.
The diff coverage is 77.71%.

Impacted file tree graph

@@ Coverage Diff @@## master #9778 +/- ##
==========================================
- Coverage 82.59% 82.43% -0.17% 
==========================================
Files 248 252 +4 Lines 58294 59024 +730 ==========================================
+ Hits 48149 48655 +506 - Misses 10145 10369 +224 
Impacted FilesCoverage Δ
rust/arrow-pyarrow-integration-testing/src/lib.rs0.00% <ø> (ø)
rust/arrow/src/compute/kernels/sort.rs94.37% <ø> (+0.80%)⬆️
rust/arrow/src/compute/kernels/substring.rs98.29% <ø> (ø)
rust/arrow/src/compute/kernels/take.rs96.06% <ø> (-0.01%)⬇️
rust/arrow/src/compute/kernels/window.rs100.00% <ø> (ø)
rust/arrow/src/compute/kernels/zip.rs82.14% <ø> (ø)
rust/arrow/src/compute/util.rs98.92% <ø> (ø)
rust/arrow/src/datatypes/field.rs55.47% <ø> (ø)
rust/arrow/src/ffi.rs80.23% <ø> (+4.00%)⬆️
rust/arrow/src/ipc/reader.rs84.36% <ø> (ø)
... and 90 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ee24399...ba51b55. Read the comment docs.

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@nevi-me there are some new clippy warnings due to new Rust version not related to this PR. Do they need to be fixed?

@nevi-me

Copy link
Copy Markdown
Contributor

@nevi-me there are some new clippy warnings due to new Rust version not related to this PR. Do they need to be fixed?

@ritchie46 I fixed them last night (depending on where in the world one is lol). CI's fine now. The integration failures are known issues at ARROW-12112

@nevi-menevi-me left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM from looking at what you're doing to construct the lists.

@jorgecarleitao can do the honours of the PR blessing approval :)

@nevi-me

Copy link
Copy Markdown
Contributor

It doesn't look like anything needs to be updated in docs/source/status.rst

Comment threadrust/arrow/src/ffi.rs Outdated
@alamb

alamb commented Apr 1, 2021

Copy link
Copy Markdown
Contributor

@ritchie46 / @jorgecarleitao / @nevi-me is this one ready to go? There is one smally clippy lint left which I can fixup but I didn't want to ram this PR through to keep the queue down if it wasn't actually ready

@jorgecarleitaojorgecarleitao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks a lot @ritchie46 for taking the extra mile to have the equality done right :) 💯

Also, thanks a lot @pitrou for your help here. 👍

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@alamb I fixed the clippy issue, so I think we're good to go. :)

@alamb

alamb commented Apr 2, 2021

Copy link
Copy Markdown
Contributor

Integration test failure seems due to https://github.com/apache/arrow/pull/9778/checks?check_run_id=2253660088 (not related to this PR):

-----------------------
/
/arrow/js /
npm WARN tar ENOSPC: no space left on device, write
npm WARN tar ENOSPC: no space left on device, write
npm ERR! cb() never called!

@alambalamb closed this in 2f3ed3aApr 2, 2021
@alamb

alamb commented Apr 2, 2021

Copy link
Copy Markdown
Contributor

Thanks again @ritchie46 👍

@ritchie46

ritchie46 commented Apr 4, 2021

Copy link
Copy Markdown
ContributorAuthor

Hmm.. This is sadly a bit too late. But the current implementation does an invalid read/write. I get a SIGILL if I run this test a 1000 times.

Current thread 0x00007f3bd56f4740 (most recent call first):
File "/home/ritchie46/code/arrow/rust/arrow-pyarrow-integration-testing/tests/test_sql.py", line 89 in test_list_array
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 633 in _callTestMethod
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 676 in run
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 736 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/unittest.py", line 207 in runtest
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 131 in pytest_runtest_call
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 207 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 234 in from_call
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 206 in call_runtest_hook
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 182 in call_and_report
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 96 in runtestprotocol
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 81 in pytest_runtest_protocol
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 270 in pytest_runtestloop
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 246 in _main
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 196 in wrap_session
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 239 in pytest_cmdline_main
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/config/__init__.py", line 91 in main
File "/opt/miniconda3/bin/pytest", line 8 in <module>
Illegal instruction (core dumped)

I hope I can find the location of this invalid read/write. If anybody has an idea of where did could occur that would be very much appreciated.

Some additional info/ thoughts:

  • I cannot reproduce this in the rust tests. Is there a way to interact with C++ arrow from rust? Using valgrind on python creates a lot of noise from the python VM.

  • the location of SIGILL, during the second traversal of create children (called by the children arrays), the Arc::clone of self.array leads to the SIGILL.

@ritchie46

ritchie46 commented Apr 5, 2021

Copy link
Copy Markdown
ContributorAuthor

If I prevent the drop in release array, this issue is resolved but we leak data.

TBH, I am stuck. @pitrou@jorgecarleitao have you got any idea how this can be resolved?

could this be related?

@jorgecarleitao

Copy link
Copy Markdown
Member

I am really sorry, this was sloppiness on my part: I should have checkout the code and go through it more carefully as FFI is always risky stuff. If you think it would take some pressure off, we can revert this PR until we find and fix this.

Regardless, could you run the memory-check to see if we find the problem in our internal roundtrips? Something like

cargo test --lib --feature memory-check -- --test-threads=1

on the rust crate. This counts every alloc/realloc/dealloc over all buffers over all tests and verifies that the sum is zero. The test-threads must be 1 so that tests run sequentially and the last test is the memory check.

I would try to run this before this PR's commit just to make sure that things work as expected since we do not run this as part of our CI. If it passes, then I would try again after this PR's commit.

@pitrou

Copy link
Copy Markdown
Member

@ritchie46 The guidelines for implementers of a release callback here. I would suggest following the example:
https://arrow.apache.org/docs/format/CDataInterface.html#release-callback-semantics-for-producers

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@ritchie46 The guidelines for implementers of a release callback here. I would suggest following the example:
https://arrow.apache.org/docs/format/CDataInterface.html#release-callback-semantics-for-producers

Yes, thank you. I will be going through that.

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

I am really sorry, this was sloppiness on my part: I should have checkout the code and go through it more carefully as FFI is always risky stuff. If you think it would take some pressure off, we can revert this PR until we find and fix this.

Yes, in that case we panic instead of UB, which cleary is better.

Regardless, could you run the memory-check to see if we find the problem in our internal roundtrips? Something like

Will do that.

Some extra info: I realize that we don't have any owned child data in private_data so, maybe the child_data is already dropped.

@pitroupitrou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this seems a bit delicate to get right, I think you should first add tests for roundtripping schemas:

  • roundtrip a schema as is, Rust->Python->Rust
  • roundtrip a schema as is, Python->Rust->Python
  • create a primitive type in Rust, return pa.list(primitive_type) from Python
  • create a list type in Rust, return list_type.value_type from Python

For each case, verify the expected result, also check for allocation/deallocation/leaks.

Once you got that right, you can tackle the array roundtrip issue.

.child_data()
.iter()
.map(|arr| {
let len = arr.len();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand: why isn't try_from called recursively?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because I otherwise cannot pass nullable: bool information from the parent. If should split this up in a function separate from try_from to make this more explicit.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, you'll need to handle recursive types more generally anyway. Think list(list(int8)).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that would not work like this. I will fix that.

a = pyarrow.array([[], None, [1, 2], [4, 5, 6]], pyarrow.list_(pyarrow.int64()))
b = arrow_pyarrow_integration_testing.round_trip(a)

b.validate(full=True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be interesting to add del a above this, to make sure that b keeps the data alive.

Comment threadrust/arrow/src/ffi.rs
FFI_ArrowSchema {
format: CString::new(format).unwrap().into_raw(),
name: std::ptr::null_mut(),
// For child data a non null string is expected and is called item

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only for lists, though. For more general nested types you'll have to take the actual field name.

Comment threadrust/arrow/src/ffi.rs
Comment threadrust/arrow/src/ffi.rs
// at that point the child data is not yet known, but it is also not required to determine
// the buffer length of the list arrays.
"+l" => {
let nullable = schema.flags == 2;

@pitroupitrouApr 5, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be (schema.flags & 2) != 0. Also I'm surprised you're sprinkling magic numbers in the code instead of defining a constant.

Comment threadrust/arrow/src/ffi.rs
// Safety
// Should be set as this is expected from the C FFI definition
debug_assert!(!schema.name.is_null());
let name = unsafe { CString::from_raw(schema.name as *mut c_char) }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem right. The doc for from_raw says:

This should only ever be called with a pointer that was earlier obtained by calling CString::into_raw. Other usage (e.g., trying to take ownership of a string that was allocated by foreign code) is likely to lead to undefined behavior or allocator corruption.

But we are exactly in the case where schema.name can have been allocated by C++ or Python or anything else. It seems instead you should use CStr instead: "Representation of a borrowed C string".

Comment threadrust/arrow/src/ffi.rs
(DataType::Utf8, 2) | (DataType::Binary, 2) => size_of::<u8>() * 8,
(DataType::Utf8, _) | (DataType::Binary, _) => {
(DataType::Utf8, 1) | (DataType::Binary, 1) | (DataType::List(_), 1) => size_of::<i32>() * 8,
(DataType::Utf8, 2) | (DataType::Binary, 2) | (DataType::List(_), 2) => size_of::<u8>() * 8,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lists don't have a buffer number 2.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. I thought it was a validity bitmap and an offset buffer and that the child data was counted differently.

What should be the correct number?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validity bitmap is buffer 0 and the offsets are buffer 1. You are defining a buffer 2 (of u8) which doesn't exist.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validity is the buffer 0, the offsets the buffer 1. The List has no buffer number two. If someone requests buffer 2 from a List Array, we should error instead.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, Thanks!

Comment threadrust/arrow/src/ffi.rs
Sign up for freeto 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.

6 participants

@ritchie46@alamb@codecov-io@nevi-me@jorgecarleitao@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. … - #9778

Closed
ritchie46 wants to merge 12 commits into
apache:masterfrom
ritchie46:ffi_types
Closed

ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. …#9778
ritchie46 wants to merge 12 commits into
apache:masterfrom
ritchie46:ffi_types

Conversation

@ritchie46

Copy link
Copy Markdown
Contributor

This PR adds child data to Arrow's C FFI implementation and implements it for List and LargeList datatypes.

@ritchie46ritchie46 changed the title Add Child Data to Arrow's C FFI implementation. …ARROW-12052: [Rust] Add Child Data to Arrow's C FFI implementation. …Mar 23, 2021
@github-actions

Copy link
Copy Markdown

@apacheapache deleted a comment from github-actionsBotMar 24, 2021
@ritchie46
ritchie46force-pushed the ffi_types branch 2 times, most recently from 151be97 to b6fbf30CompareMarch 24, 2021 07:55
@alamb

Copy link
Copy Markdown
Contributor

I am not an expert in this level of code -- perhaps @jorgecarleitao has time to take a look at this PR?

@jorgecarleitaojorgecarleitao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this, @ritchie46 . it has the right ideas and looks great so far. 💯

I left some comments throughout the code.

My last general comment would be to add this type to the pyarrow-integration-tests crate, which contains real tests against pyarrow, which allow us to validate the behavior against the C++ implementation.

Comment threadrust/arrow/src/datatypes/field.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
@ritchie46

Copy link
Copy Markdown
ContributorAuthor

Thanks a lot for this, @ritchie46 . it has the right ideas and looks great so far. 100

I left some comments throughout the code.

My last general comment would be to add this type to the pyarrow-integration-tests crate, which contains real tests against pyarrow, which allow us to validate the behavior against the C++ implementation.

Cool! I think I've tackled all your comments @jorgecarleitao . I also added a test to the pyarrow-integration-tests-crate. That was a good one, because it did not succeed.

It turns out that we need to provide a name in the FFI_ArrowSchema when we provide child data. This was a null ptr, so I replaced this with an empty "" string, that seemed easiest to me. If you want to tackle that differently, I am open to suggestions.

@nevi-menevi-me left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments, I'm happy with the implementation though. Thanks @ritchie46 , and I apologise for taking long to review this.

Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow-pyarrow-integration-testing/tests/test_sql.py Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
Comment threadrust/arrow/src/ffi.rs Outdated
@ritchie46

Copy link
Copy Markdown
ContributorAuthor

Some minor comments, I'm happy with the implementation though. Thanks @ritchie46 , and I apologise for taking long to review this.

No worries! 😄

@codecov-io

Copy link
Copy Markdown

Codecov Report

Merging #9778 (ba51b55) into master (29feea0) will decrease coverage by 0.16%.
The diff coverage is 77.71%.

Impacted file tree graph

@@ Coverage Diff @@## master #9778 +/- ##
==========================================
- Coverage 82.59% 82.43% -0.17% 
==========================================
Files 248 252 +4 Lines 58294 59024 +730 ==========================================
+ Hits 48149 48655 +506 - Misses 10145 10369 +224 
Impacted FilesCoverage Δ
rust/arrow-pyarrow-integration-testing/src/lib.rs0.00% <ø> (ø)
rust/arrow/src/compute/kernels/sort.rs94.37% <ø> (+0.80%)⬆️
rust/arrow/src/compute/kernels/substring.rs98.29% <ø> (ø)
rust/arrow/src/compute/kernels/take.rs96.06% <ø> (-0.01%)⬇️
rust/arrow/src/compute/kernels/window.rs100.00% <ø> (ø)
rust/arrow/src/compute/kernels/zip.rs82.14% <ø> (ø)
rust/arrow/src/compute/util.rs98.92% <ø> (ø)
rust/arrow/src/datatypes/field.rs55.47% <ø> (ø)
rust/arrow/src/ffi.rs80.23% <ø> (+4.00%)⬆️
rust/arrow/src/ipc/reader.rs84.36% <ø> (ø)
... and 90 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ee24399...ba51b55. Read the comment docs.

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@nevi-me there are some new clippy warnings due to new Rust version not related to this PR. Do they need to be fixed?

@nevi-me

Copy link
Copy Markdown
Contributor

@nevi-me there are some new clippy warnings due to new Rust version not related to this PR. Do they need to be fixed?

@ritchie46 I fixed them last night (depending on where in the world one is lol). CI's fine now. The integration failures are known issues at ARROW-12112

@nevi-menevi-me left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM from looking at what you're doing to construct the lists.

@jorgecarleitao can do the honours of the PR blessing approval :)

@nevi-me

Copy link
Copy Markdown
Contributor

It doesn't look like anything needs to be updated in docs/source/status.rst

Comment threadrust/arrow/src/ffi.rs Outdated
@alamb

alamb commented Apr 1, 2021

Copy link
Copy Markdown
Contributor

@ritchie46 / @jorgecarleitao / @nevi-me is this one ready to go? There is one smally clippy lint left which I can fixup but I didn't want to ram this PR through to keep the queue down if it wasn't actually ready

@jorgecarleitaojorgecarleitao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks a lot @ritchie46 for taking the extra mile to have the equality done right :) 💯

Also, thanks a lot @pitrou for your help here. 👍

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@alamb I fixed the clippy issue, so I think we're good to go. :)

@alamb

alamb commented Apr 2, 2021

Copy link
Copy Markdown
Contributor

Integration test failure seems due to https://github.com/apache/arrow/pull/9778/checks?check_run_id=2253660088 (not related to this PR):

-----------------------
/
/arrow/js /
npm WARN tar ENOSPC: no space left on device, write
npm WARN tar ENOSPC: no space left on device, write
npm ERR! cb() never called!

@alambalamb closed this in 2f3ed3aApr 2, 2021
@alamb

alamb commented Apr 2, 2021

Copy link
Copy Markdown
Contributor

Thanks again @ritchie46 👍

@ritchie46

ritchie46 commented Apr 4, 2021

Copy link
Copy Markdown
ContributorAuthor

Hmm.. This is sadly a bit too late. But the current implementation does an invalid read/write. I get a SIGILL if I run this test a 1000 times.

Current thread 0x00007f3bd56f4740 (most recent call first):
File "/home/ritchie46/code/arrow/rust/arrow-pyarrow-integration-testing/tests/test_sql.py", line 89 in test_list_array
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 633 in _callTestMethod
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 676 in run
File "/opt/miniconda3/lib/python3.8/unittest/case.py", line 736 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/unittest.py", line 207 in runtest
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 131 in pytest_runtest_call
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 207 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 234 in from_call
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 206 in call_runtest_hook
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 182 in call_and_report
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 96 in runtestprotocol
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/runner.py", line 81 in pytest_runtest_protocol
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 270 in pytest_runtestloop
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 246 in _main
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 196 in wrap_session
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/main.py", line 239 in pytest_cmdline_main
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/callers.py", line 187 in _multicall
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 84 in <lambda>
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/opt/miniconda3/lib/python3.8/site-packages/pluggy/hooks.py", line 286 in __call__
File "/opt/miniconda3/lib/python3.8/site-packages/_pytest/config/__init__.py", line 91 in main
File "/opt/miniconda3/bin/pytest", line 8 in <module>
Illegal instruction (core dumped)

I hope I can find the location of this invalid read/write. If anybody has an idea of where did could occur that would be very much appreciated.

Some additional info/ thoughts:

  • I cannot reproduce this in the rust tests. Is there a way to interact with C++ arrow from rust? Using valgrind on python creates a lot of noise from the python VM.

  • the location of SIGILL, during the second traversal of create children (called by the children arrays), the Arc::clone of self.array leads to the SIGILL.

@ritchie46

ritchie46 commented Apr 5, 2021

Copy link
Copy Markdown
ContributorAuthor

If I prevent the drop in release array, this issue is resolved but we leak data.

TBH, I am stuck. @pitrou@jorgecarleitao have you got any idea how this can be resolved?

could this be related?

@jorgecarleitao

Copy link
Copy Markdown
Member

I am really sorry, this was sloppiness on my part: I should have checkout the code and go through it more carefully as FFI is always risky stuff. If you think it would take some pressure off, we can revert this PR until we find and fix this.

Regardless, could you run the memory-check to see if we find the problem in our internal roundtrips? Something like

cargo test --lib --feature memory-check -- --test-threads=1

on the rust crate. This counts every alloc/realloc/dealloc over all buffers over all tests and verifies that the sum is zero. The test-threads must be 1 so that tests run sequentially and the last test is the memory check.

I would try to run this before this PR's commit just to make sure that things work as expected since we do not run this as part of our CI. If it passes, then I would try again after this PR's commit.

@pitrou

Copy link
Copy Markdown
Member

@ritchie46 The guidelines for implementers of a release callback here. I would suggest following the example:
https://arrow.apache.org/docs/format/CDataInterface.html#release-callback-semantics-for-producers

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

@ritchie46 The guidelines for implementers of a release callback here. I would suggest following the example:
https://arrow.apache.org/docs/format/CDataInterface.html#release-callback-semantics-for-producers

Yes, thank you. I will be going through that.

@ritchie46

Copy link
Copy Markdown
ContributorAuthor

I am really sorry, this was sloppiness on my part: I should have checkout the code and go through it more carefully as FFI is always risky stuff. If you think it would take some pressure off, we can revert this PR until we find and fix this.

Yes, in that case we panic instead of UB, which cleary is better.

Regardless, could you run the memory-check to see if we find the problem in our internal roundtrips? Something like

Will do that.

Some extra info: I realize that we don't have any owned child data in private_data so, maybe the child_data is already dropped.

@pitroupitrou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this seems a bit delicate to get right, I think you should first add tests for roundtripping schemas:

  • roundtrip a schema as is, Rust->Python->Rust
  • roundtrip a schema as is, Python->Rust->Python
  • create a primitive type in Rust, return pa.list(primitive_type) from Python
  • create a list type in Rust, return list_type.value_type from Python

For each case, verify the expected result, also check for allocation/deallocation/leaks.

Once you got that right, you can tackle the array roundtrip issue.

.child_data()
.iter()
.map(|arr| {
let len = arr.len();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand: why isn't try_from called recursively?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because I otherwise cannot pass nullable: bool information from the parent. If should split this up in a function separate from try_from to make this more explicit.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, you'll need to handle recursive types more generally anyway. Think list(list(int8)).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that would not work like this. I will fix that.

a = pyarrow.array([[], None, [1, 2], [4, 5, 6]], pyarrow.list_(pyarrow.int64()))
b = arrow_pyarrow_integration_testing.round_trip(a)

b.validate(full=True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be interesting to add del a above this, to make sure that b keeps the data alive.

Comment threadrust/arrow/src/ffi.rs
FFI_ArrowSchema {
format: CString::new(format).unwrap().into_raw(),
name: std::ptr::null_mut(),
// For child data a non null string is expected and is called item

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only for lists, though. For more general nested types you'll have to take the actual field name.

Comment threadrust/arrow/src/ffi.rs
Comment threadrust/arrow/src/ffi.rs
// at that point the child data is not yet known, but it is also not required to determine
// the buffer length of the list arrays.
"+l" => {
let nullable = schema.flags == 2;

@pitroupitrouApr 5, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be (schema.flags & 2) != 0. Also I'm surprised you're sprinkling magic numbers in the code instead of defining a constant.

Comment threadrust/arrow/src/ffi.rs
// Safety
// Should be set as this is expected from the C FFI definition
debug_assert!(!schema.name.is_null());
let name = unsafe { CString::from_raw(schema.name as *mut c_char) }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem right. The doc for from_raw says:

This should only ever be called with a pointer that was earlier obtained by calling CString::into_raw. Other usage (e.g., trying to take ownership of a string that was allocated by foreign code) is likely to lead to undefined behavior or allocator corruption.

But we are exactly in the case where schema.name can have been allocated by C++ or Python or anything else. It seems instead you should use CStr instead: "Representation of a borrowed C string".

Comment threadrust/arrow/src/ffi.rs
(DataType::Utf8, 2) | (DataType::Binary, 2) => size_of::<u8>() * 8,
(DataType::Utf8, _) | (DataType::Binary, _) => {
(DataType::Utf8, 1) | (DataType::Binary, 1) | (DataType::List(_), 1) => size_of::<i32>() * 8,
(DataType::Utf8, 2) | (DataType::Binary, 2) | (DataType::List(_), 2) => size_of::<u8>() * 8,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lists don't have a buffer number 2.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. I thought it was a validity bitmap and an offset buffer and that the child data was counted differently.

What should be the correct number?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validity bitmap is buffer 0 and the offsets are buffer 1. You are defining a buffer 2 (of u8) which doesn't exist.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validity is the buffer 0, the offsets the buffer 1. The List has no buffer number two. If someone requests buffer 2 from a List Array, we should error instead.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, Thanks!

Comment threadrust/arrow/src/ffi.rs
Sign up for freeto 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.

6 participants

@ritchie46@alamb@codecov-io@nevi-me@jorgecarleitao@pitrou