Repository navigation
[MINOR] Reject invalid CUDA streams in buffer factories - #1263
Conversation
| // Deallocate on the HostBuffer's current stream, which `set_stream()` may | ||
| // have changed since construction. | ||
| [shared_db_ = std::move(shared_db)](cuda::stream_ref s) mutable { | ||
| shared_db_->set_stream(s); |
There was a problem hiding this comment.
Since the from_rmm_device_buffer deallocator now frees memory on the HostBuffer's current stream, HostBuffer::set_stream(cuda::invalid_stream) leads to freeing on an invalid stream inside the noexcept destructor. I think HostBuffer::set_stream should get the same std::invalid_argument.
| EXPECT_NO_THROW(test_buffer(std::move(buffer), source_data)); | ||
| } | ||
|
|
||
| TEST(HostBuffer, invalid_stream_throws) { |
There was a problem hiding this comment.
Could you add an invalid-stream EXPECT_THROW for HostBuffer::from_rmm_device_buffer and HostBuffer::from_external? It would also help to test the new from_rmm_device_buffer behavior, build a pinned rmm::device_buffer on stream A, call from_rmm_device_buffer(..., B), check the contents are read correctly on B, and destroy it after set_stream(C). Right now nothing covers the new join or the free on the current stream.
There was a problem hiding this comment.
Thanks for the checks. Two things from the earlier comment are still open:
- an
EXPECT_THROWforHostBuffer::from_external(span, invalid, ...) - a test of the new ordering and freeing. For example: fill a pinned
rmm::device_bufferon stream A, callfrom_rmm_device_buffer(..., B), check the contents on B, thenset_stream(C)and destroy it. If this is out of scope for this PR, that's fine, but please say so in the PR description, because it currently says each entry point is tested.
There was a problem hiding this comment.
I think 1 is already there. I will add a test to 2. That is a good point. It was a bug uncovered during this invalid stream changes
pentschev
left a comment
There was a problem hiding this comment.
Some more test asks but otherwise LGTM. I'm preemptively approving.
| auto br = | ||
| rapidsmpf::BufferResource::create(rmm::mr::get_current_device_resource_ref()); |
There was a problem hiding this comment.
br here is created without PinnedPoolProperties, so br->pinned_mr() throws std::invalid_argument while the argument is being built. EXPECT_THROW then passes without ever calling from_rmm_device_buffer. Could you create a separate resource with pinned memory turned on, e.g. BufferResource::create(rmm::mr::get_current_device_resource_ref(), rapidsmpf::PinnedPoolProperties{})? Then allocate the rmm::device_buffer outside the EXPECT_THROW, so only the from_rmm_device_buffer call is inside it.
There was a problem hiding this comment.
Yes, thanks. This was a good catch. this check was masked by the invalid arg exception from pinned mr, rather than the invalid stream
| EXPECT_NO_THROW(test_buffer(std::move(buffer), source_data)); | ||
| } | ||
|
|
||
| TEST(HostBuffer, invalid_stream_throws) { |
There was a problem hiding this comment.
Thanks for the checks. Two things from the earlier comment are still open:
- an
EXPECT_THROWforHostBuffer::from_external(span, invalid, ...) - a test of the new ordering and freeing. For example: fill a pinned
rmm::device_bufferon stream A, callfrom_rmm_device_buffer(..., B), check the contents on B, thenset_stream(C)and destroy it. If this is out of scope for this PR, that's fine, but please say so in the PR description, because it currently says each entry point is tested.
|
/merge |
Passing
cuda::invalid_streamto a buffer factory used to succeed and only fail later, ascudaErrorInvalidResourceHandlefrom a CUDA call far from the cause. The publicHostBuffer::from_rmm_device_bufferalso skipped the stream ordering thatBufferResource::movedoes. The buffer factories andBuffer::rebind_streamnow throwstd::invalid_argumenton an invalid stream.from_rmm_device_buffernow orders its stream after the device buffer's pending work. Follow-up to #1261.BufferResource::make_buffer,BufferResource::move, theHostBufferconstructors (used byfrom_uint8_vectorandfrom_owned_vector),HostBuffer::from_rmm_device_bufferandBuffer::rebind_streamthrowstd::invalid_argumentforcuda::invalid_stream.HostBuffer::from_rmm_device_buffermakesstreamwait on the device buffer's stream, and frees the memory on theHostBuffer's current stream. Before, memory was freed on the original stream, which could be stale afterrebind_stream.BufferResource::moveandfrom_rmm_device_buffernow just checkscapacity() > 0. An empty buffer owns no memory, so there's nothing to join. This includes default-constructed buffers, which have no stream.