fix: align typed column buffers to T in decode paths
cast_slice::<u8, T> panics with TargetAlignmentGreaterAndInputNotAligned on KDB IPC payloads where a variable-length column leaves a numeric column at a misaligned wire offset. The sync decode path's alignment fallback used Bytes::copy_from_slice (Vec<u8> layout, align=1), which only happens to work because most allocators over-align byte blocks -- not guaranteed by Rust's allocator API. The async pipelined path went through read_bytes(len * size) directly, with no alignment branch at all, and panicked in arrow projection's as_*_slice on Windows release builds under AsyncPool.query. Both paths now back typed columns with Vec<T> (Layout::array::<T> guarantees align_of::<T>()), exposed as bytes::Bytes via a new AlignedTBuf<T> AsRef<[u8]> owner passed to Bytes::from_owner. Sync fallback uses the same wrapper. Pipelined typed reads route through a new read_typed_bytes::<T> helper that swaps in for every typed Primitive arm in decode_vector_async. Regression test in pipelined::tests constructs a table with an odd- length symbol column followed by Long, exercising the previously panicking path.
This commit is contained in:
parent
53ac90fe84
commit
f24af467ec
2 changed files with 114 additions and 18 deletions
|
|
@ -70,6 +70,16 @@ impl Default for DecodeOptions {
|
|||
}
|
||||
}
|
||||
|
||||
/// Owns a `Vec<T>` and exposes it as `&[u8]` so `bytes::Bytes::from_owner`
|
||||
/// can keep a T-aligned allocation alive while presenting a byte view.
|
||||
struct AlignedTBuf<T: bytemuck::Pod>(Vec<T>);
|
||||
|
||||
impl<T: bytemuck::Pod> AsRef<[u8]> for AlignedTBuf<T> {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
bytemuck::cast_slice(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
struct BodyReader {
|
||||
bytes: bytes::Bytes,
|
||||
offset: usize,
|
||||
|
|
@ -128,8 +138,13 @@ impl BodyReader {
|
|||
/// Returns a `Bytes` wrapper of `count * size_of::<T>()` bytes, aligned for `T`.
|
||||
///
|
||||
/// If the current offset is already aligned for `T`, this is zero-copy
|
||||
/// (a `Bytes::slice`). Otherwise it copies into a new aligned allocation.
|
||||
fn read_bytes_aligned<T: bytemuck::Pod>(&mut self, count: usize) -> CoreResult<bytes::Bytes> {
|
||||
/// (a `Bytes::slice`). Otherwise it copies into a `Vec<T>`-backed allocation,
|
||||
/// guaranteeing T-alignment regardless of the global allocator's behavior
|
||||
/// for `Vec<u8>` (whose layout only requires align=1).
|
||||
fn read_bytes_aligned<T>(&mut self, count: usize) -> CoreResult<bytes::Bytes>
|
||||
where
|
||||
T: bytemuck::Pod + Send + Sync + 'static,
|
||||
{
|
||||
let byte_len = count
|
||||
.checked_mul(std::mem::size_of::<T>())
|
||||
.ok_or(CoreError::LengthOverflow(count))?;
|
||||
|
|
@ -143,11 +158,12 @@ impl BodyReader {
|
|||
let ptr = self.bytes[self.offset..].as_ptr();
|
||||
let align = std::mem::align_of::<T>();
|
||||
let result = if (ptr as usize) % align == 0 {
|
||||
// Already aligned — zero-copy slice.
|
||||
self.bytes.slice(self.offset..end)
|
||||
} else {
|
||||
// Misaligned — must copy into an aligned allocation.
|
||||
bytes::Bytes::copy_from_slice(&self.bytes[self.offset..end])
|
||||
let mut aligned = vec![T::zeroed(); count];
|
||||
let dst: &mut [u8] = bytemuck::cast_slice_mut(&mut aligned);
|
||||
dst.copy_from_slice(&self.bytes[self.offset..end]);
|
||||
bytes::Bytes::from_owner(AlignedTBuf(aligned))
|
||||
};
|
||||
self.offset = end;
|
||||
Ok(result)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue