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

Improve memory safety of cgo interop code. #366

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

teo-tsirpanis
Copy link
Member

SC-58351
SC-62492

  • We use runtime.KeepAlive to keep the finalizers of TileDB objects from being called during each cgo call.
    • The keepalives are put right after the call, ordered by the object's appearence in the C function's parameters.
    • If a C API returns pointers to C-owned memory, these are documented, and the keepalives are put right after the last use of that pointer.
    • If a variable does not need a keepalive when using it or passing it to C, that fact is documented.
  • We use the runtime.Pinner type to pin memory held long-term by C, in a well-defined way.
    • Unpinning happens in the Free() function, after the C handle is freed.
    • This makes us (to the best of my knowledge) no longer reliant on a non-moving garbage collector. I'm not removing the dependency to https://go4.org/unsafe/assume-no-moving-gc right now out of abundance of caution.
  • Updates CI to build tests with the GOEXPERIMENT=cgocheck2 option.

Unpinning must be done after the native handle is freed.
Also do not unpin the buffers in `Query.Finalize`.
}
runtime.KeepAlive(sa)
// The start and end pointers are being kept alive by passing them to cgo calls.
Copy link
Member Author

Choose a reason for hiding this comment

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

From documentation:

Go pointers passed as function arguments to C functions have the memory they point to implicitly pinned for the duration of the call.

}
cpy := C.GoBytes(cbuffer, C.int(csize))
Copy link
Member Author

Choose a reason for hiding this comment

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

We can't know whether the memory is owned by Go or C, so we always make a copy. This API is not recommended either way.

Comment on lines -306 to -308
q.bufferMutex.Lock()
defer q.bufferMutex.Unlock()
q.buffers = nil
Copy link
Member Author

Choose a reason for hiding this comment

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

We should not be unpinning the buffers in Finalize.

@@ -118,6 +118,7 @@ func WithStartTime(start time.Time) ArrayOpenOption {
func WithEndTimestamp(endTimestamp uint64) ArrayOpenOption {
return func(tdbArray *Array) error {
ret := C.tiledb_array_set_open_timestamp_end(tdbArray.context.tiledbContext, tdbArray.tiledbArray, C.uint64_t(endTimestamp))
runtime.KeepAlive(tdbArray)
Copy link
Member Author

@teo-tsirpanis teo-tsirpanis Feb 14, 2025

Choose a reason for hiding this comment

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

When in the future we support Go 1.24's AddCleanup, we would have to explicitly keep the contexts alive, because the finalizers do not run in order.

Let's do it now, before marking the PR as ready for review.

Copy link
Member Author

Choose a reason for hiding this comment

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

On second thought, I have misread documentation. runtime.KeepAlive does apply recursively (obviously), and as long as an object is kept alive, its finalizers will not run. This is what we want, and the order finalizers execute does not matter here.

@@ -533,6 +555,8 @@ func DeserializeFragmentInfo(fragmentInfo FragmentInfo, buffer *Buffer, arrayURI
defer C.free(unsafe.Pointer(cArrayURI))

ret := C.tiledb_deserialize_fragment_info(fragmentInfo.context.tiledbContext, buffer.tiledbBuffer, C.tiledb_serialization_type_t(serializationType), cArrayURI, cClientSide, fragmentInfo.tiledbFragmentInfo)
runtime.KeepAlive(fragmentInfo)
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 FragmentInfo (and the one in line 581) is passed by value, so this call does not do anything. We might want to change it now or in the future to take a pointer, don't know if the break is tolerable.

@teo-tsirpanis teo-tsirpanis marked this pull request as ready for review February 18, 2025 01:31
@teo-tsirpanis teo-tsirpanis requested a review from a team as a code owner February 18, 2025 01:31
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.

1 participant