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

Stop using null terminators on JSON (de)serialization. #5353

Merged
merged 10 commits into from
Nov 26, 2024

Conversation

teo-tsirpanis
Copy link
Member

SC-58051

This PR updates the JSON deserialization code to explicitly pass the message's length to capnproto, instead of appending a null terminator and relying on it to get the length. Correspondingly, it removes the null terminator in serialized JSON messages, which is technically a behavior breaking change but the null terminator gets already removed by both the Go API and the REST server (see story), and the serialization APIs are documented as being for internal use.


TYPE: NO_HISTORY

Comment on lines +708 to +712
// Trim null terminator if exists.
if (!buffer.empty() && buffer.back() == '\0') {
buffer = buffer.first(buffer.size() - 1);
}

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 this might not be needed, considering that the null terminator gets trimmed on the server side.

::capnp::MallocMessageBuilder message_builder;
auto builder =
message_builder.initRoot<capnp::LoadArraySchemaResponse>();
json.decode(kj::StringPtr(data.data()), builder);
Copy link
Member Author

Choose a reason for hiding this comment

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

You can validate that I did not miss a case by seeing there are zero occurences of kj::StringPtr in the codebase.

@teo-tsirpanis
Copy link
Member Author

CI is green, apart from an unrelated failure that is expected to be fixed in #5355. This is ready for review.

@teo-tsirpanis teo-tsirpanis marked this pull request as ready for review October 22, 2024 00:43
We were missing a header, and we can pass a `kj::ArrayPtr` which does not require being null-terminated.
@ypatia
Copy link
Member

ypatia commented Oct 22, 2024

Question before I review this PR , what is the motivation for this change? Do you expect some performance improvement or some other sort of benefit from this refactoring?

@teo-tsirpanis
Copy link
Member Author

@ypatia the motivation is to unify the diverging logic that depends on the format being capnp or JSON and have code always just send the whole buffer to the client. This will both simplify the code and enable some (likely significant) optimizations outlined in SC-58009.

@ypatia
Copy link
Member

ypatia commented Oct 24, 2024

As discussed yesterday in planning, it'd be good to test this a bit more thoroughly just in case we are missing something that could be potentially breaking. I don't have context on why this null terminator needed to be added in the past, so I don't feel comfortable removing it just like this. Let's at least run REST-CI with JSON as default mode for this PR, @shaunrd0 said he can help with that if needed. Then we could also consider some testing using the JS client that is using JSON as serialization type, @SarantopoulosKon could help with instructions if we decide to go that way.

@teo-tsirpanis
Copy link
Member Author

teo-tsirpanis commented Oct 24, 2024

Some REST-CI tests failed with Error: Cannot serialize query; json format not supported. and then it segfaulted. Don't know yet whether it's a real problem or caused by query JSON serialization not being supported. Will try to reproduce locally.

@Shelnutt2
Copy link
Member

As discussed yesterday in planning, it'd be good to test this a bit more thoroughly just in case we are missing something that could be potentially breaking. I don't have context on why this null terminator needed to be added in the past, so I don't feel comfortable removing it just like this. Let's at least run REST-CI with JSON as default mode for this PR, @shaunrd0 said he can help with that if needed. Then we could also consider some testing using the JS client that is using JSON as serialization type, @SarantopoulosKon could help with instructions if we decide to go that way.

I believe this might indeed cause issues, as in some places it was assumed there would be a null terminator. i.e https://github.com/TileDB-Inc/TileDB-Go/blob/9ec3a9462135bf6005dcc06a1f44a6dc24afecba/buffer.go#L107

@teo-tsirpanis
Copy link
Member Author

The attached snippet does not assume the existence of a null terminator. If there isn't one, TrimSuffix will not modify the slice.

@teo-tsirpanis
Copy link
Member Author

I found the cause of the segfault:

rc = tiledb_query_submit(ctx_, query);
CHECK(rc == TILEDB_OK);

std::vector<uint64_t> fragment_timestamps;
rc = tiledb_vfs_ls(
ctx_,
vfs_,
get_commit_dir(array_path).c_str(),
&get_fragment_timestamps,
&fragment_timestamps);
CHECK(rc == TILEDB_OK);

rc = tiledb_array_set_open_timestamp_end(ctx_, array, fragment_timestamps[0]);

The array writes failed because serializing queries in JSON is not supported but we continued execution because of the CHECK, which means that when we ls the fragments directory we get an empty vector, which means that fragment_timestamps[0] is an out-of-bounds access. I replaced all CHECKs on query_submit in this test with REQUIREs, and that test failed gracefully on my machine. Doing this for all tests will not provide meaningful results for acceptance-testing this PR, considering how prevalent queries are in REST-CI.

@SarantopoulosKon let's sync tomorrow on how to run the Cloud-JS tests with this PR.

@teo-tsirpanis
Copy link
Member Author

I added a REST CI test that creates an array using JSON serialization. Let me know if this would be sufficient for validating this PR.

Copy link
Member

@ypatia ypatia left a comment

Choose a reason for hiding this comment

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

Thanks for this change and for you effort to test it. If all goes well, it will enable an important optimization server side that we believe it can bring both memory and latency improvements.

@teo-tsirpanis
Copy link
Member Author

CI is green, merging…

@teo-tsirpanis teo-tsirpanis merged commit 414a261 into dev Nov 26, 2024
63 checks passed
@teo-tsirpanis teo-tsirpanis deleted the teo/json-null-term branch November 26, 2024 11:45
ihnorton pushed a commit to ihnorton/TileDB that referenced this pull request Dec 4, 2024
[SC-58051](https://app.shortcut.com/tiledb-inc/story/58051/stop-using-null-terminators-on-json-de-serialization)

This PR updates the JSON deserialization code to explicitly pass the
message's length to capnproto, instead of appending a null terminator
and relying on it to get the length. Correspondingly, it removes the
null terminator in serialized JSON messages, which is technically a
behavior breaking change but the null terminator gets already removed by
both [the Go
API](https://github.com/TileDB-Inc/TileDB-Go/blob/9ec3a9462135bf6005dcc06a1f44a6dc24afecba/buffer.go#L105-L107)
and the REST server (see story), and the serialization APIs are
documented as being for internal use.

---
TYPE: NO_HISTORY
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.

3 participants