Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reimpl Drop for pgx::datum::Array<'a, T> #706

Conversation

workingjubilee
Copy link
Member

@workingjubilee workingjubilee commented Sep 23, 2022

While discussing the 0.5.0 release with @Hoverbear, the current situation regarding leaking memory stuck out as a sore spot. We discussed ways to make the Drop impl sound again, and I found this implementation after a bit of thought. It should actually be correct this time.

This effectively reverses #633 but is more correct than the prior state.

While discussing the 0.5.0 release with @Hoverbear,
the current situation regarding leaking memory stuck out as a sore spot.
We discussed ways to make the `Drop` impl sound again,
and I found this implementation after a bit of thought.
It should actually be correct this time.
Copy link
Contributor

@Hoverbear Hoverbear left a comment

Choose a reason for hiding this comment

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

I kinda wish I understood this better but it does read logically to me and not appear to have obvious issues!

Copy link
Member Author

@workingjubilee workingjubilee left a comment

Choose a reason for hiding this comment

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

Ah, so, the issue is that when pg_sys::deconstruct_array is called, Postgres builds an alternate "expanded" representation. This was selected by PGX because it is more... "user-friendly". However, in doing so, it allocates: once for the [Datum]s, once for the [bool]s.

Comment on lines 89 to 95
// Now we check for detoasting clones
let raw = raw.take().map(|r| r.into_ptr());
let ptr = ptr.take();
match (ptr, raw) {
// SAFETY: if pgx detoasted a clone of this varlena, pfree the clone
(Some(p), Some(r)) if r.cast() != p => unsafe { pg_sys::pfree(r.as_ptr().cast()) },
_ => (),
Copy link
Member Author

Choose a reason for hiding this comment

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

Postgres also may allocate when you call pg_detoast_datum: the canonical way to check if it did is checking pointer equivalence. 🫠 So this .take()s both the varlena pointer from earlier and the RawArray from earlier, and checks that they are both Some, mapping RawArray to *mut ArrayType, casting from there to *mut pg_sys::varlena to compare, then casting again to *mut c_void to free it.

Comment on lines 198 to 209
// Assert correctness of our nullness checks and cleanup.
// SAFETY: Should be correctly constructed for slice validity.
let pallocd_null_slice = unsafe { slice::from_raw_parts(nulls, nelems) };
for i in 0..nelems {
assert_eq!(null_slice.get(i).unwrap(), pallocd_null_slice[i]);
}

// Throw away the slice we made.
mem::drop(pallocd_null_slice);
// SAFETY: We made it, we can break it. Or Postgres can, at least.
unsafe { pg_sys::pfree(nulls.cast()) };

Copy link
Member Author

Choose a reason for hiding this comment

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

This code continues to call deconstruct_array to maintain compatibility for current schemes for now, but it immediately frees the pallocated slice of bool as unnecessary after checking that our NullKind-wrapped BitSlice "understands" the null mapping in the same way.

Comment on lines 76 to 87
if let Array {
ptr,
raw,
datum_palloc: Some(data),
elem_slice,
..
} = self
{
// It's just a slice, dropping it doesn't "do" anything, but out of an abundance of caution:
mem::drop(elem_slice);
// No conflict possible with ditching this.
unsafe { pg_sys::pfree(data.as_ptr().cast()) };
Copy link
Member Author

Choose a reason for hiding this comment

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

While dropping the Array, check if the datum_palloc field is Some, and then if so, drop the elem_slice (it's a no-op, but it means there's no other pointers) and pfree the pointer to the allocation of Datums.

@Hoverbear
Copy link
Contributor

Understand better now, thanks, :) I didn't get much chance to dig into arrays yet.

@workingjubilee workingjubilee merged commit 0738115 into pgcentralfoundation:develop Sep 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants