Inflate large loose blobs - #4443
Conversation
138384b to
42d149f
Compare
|
lolol the build machines OOM trying to run this test as we inflate a very large blob into memory. 🙄 I'll add a streaming reader test, just to make sure that we can read 5GB of blobs into |
pks-t
left a comment
There was a problem hiding this comment.
I just had a cursory read right now, in particular I only skimmed the migration to git_zstream. I'll have another look as soon as you've changed the tests.
|
|
||
| for (i = 0; i < ARRAY_SIZE(git_objects_table); i++) | ||
| if (git_object_typeisloose(i) && | ||
| if (*git_objects_table[i].str && |
There was a problem hiding this comment.
Ah, yeah, that line confused me a lot in the last commit. So probably this line change should be moved to the previous commit?
There was a problem hiding this comment.
Yes, agreed. I think I fixup'd the wrong commit here.
| obj_hdr *out, size_t *out_len, unsigned char *data, size_t data_len) | ||
| obj_hdr *out, size_t *out_len, unsigned char *_data, size_t data_len) | ||
| { | ||
| const char *data = (char *)_data; |
There was a problem hiding this comment.
Looks like this chunk belongs into "odb: support large loose objects"? You should also make the data parameter const.
| goto on_error; | ||
|
|
||
| if ((uint64_t)size > SIZE_MAX) { | ||
| giterr_set(GITERR_OBJECT, "object is larger than available memory"); |
There was a problem hiding this comment.
This is technically not correct, as this is not about available memory (which we don't know about right now). Rather, this should be somethink like "object size exceeds address space"?
There was a problem hiding this comment.
Well, I think that's not technically correct either. size_t doesn't reflect the address space, it reflects the size of the largest single object size. But I think that this is practically correct for the set of computers that are actually in use, and that this is a much more actionable error message.
There was a problem hiding this comment.
Yeah, guess one should be a bit more precise when nagging about technical incorrectness :P
| GIT_INLINE(int) git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) | ||
| GIT_INLINE(int) git_hash_update(git_hash_ctx *ctx, const void *_data, size_t len) | ||
| { | ||
| const unsigned char *data = _data; |
There was a problem hiding this comment.
By the way, why did you add all those casts to the functions? Just curious
There was a problem hiding this comment.
xcode was whinging about a signed/unsigned mismatch on CC_SHA1_Update so I decided to cast them to their proper types as defined by the implementations.
42d149f to
79faa70
Compare
Oh, I remember way back when I wrote this comment, full of naivete. Yes, I would simply add a streaming reader test... if we had a streaming reader. Despite having a streaming reader API in the ODB layer, it turns out that we don't actually have any backends that implement it. So I've now changed the streaming reader API a bit, to provide the type and length of the object when initializing the stream. This would be a breaking API change - except, of course, that nobody is actually using the streaming reader API since none of the backends support it. So I feel pretty good breaking this API that nobody could be using. Then I added streaming reader support to the loose object ODB backend. I cleaned up a bit while I was in there to add some more tests for things like I didn't really expect this PR to become so large. Alas. |
8efb32c to
de3ae26
Compare
Actually, I'm going to limit this PR to the large loose blob handling. I will follow up with a PR that includes the refactoring to include |
zlib will return `Z_BUF_ERROR` whenever there is more input to inflate or deflate than there is output to store the result. This is normal for us as we iterate through the input, particularly with very large input buffers.
Introduce `git_prefixncmp` that will search up to the first `n` characters of a string to see if it is prefixed by another string. This is useful for examining if a non-null terminated character array is prefixed by a particular substring. Consolidate the various implementations of `git__prefixcmp` around a single core implementation and add some test cases to validate its behavior.
Introduce a test for very large objects in the ODB. Write a large object (5 GB) and ensure that the write succeeds and provides us the expected object ID. Introduce a test that writes that file and ensures that we can subsequently read it.
Introduce an internal API to get the object type based on a length-specified (not null terminated) string representation. This can be used to compare the (space terminated) object type name in a loose object. Reimplement `git_object_string2type` based on this API.
zlib will only inflate/deflate an `int`s worth of data at a time. We need to loop through large files in order to ensure that we inflate the entire file, not just an `int`s worth of data. Thankfully, we already have this loop in our `git_zstream` layer. Handle large objects using the `git_zstream`.
Instead of paging to zlib in INT_MAX sized chunks, we can give it as many as UINT_MAX bytes at a time. zlib doesn't care how big a buffer we give it, this simply results in fewer calls into zlib.
Check the size of objects being read from the loose odb backend and reject those that would not fit in memory with an error message that reflects the actual problem, instead of error'ing later with an unintuitive error message regarding truncation or invalid hashes.
Teach the win32 hash mechanisms to support large files. The hash primitives take at most `ULONG_MAX` bytes at a time. Loop, giving the hash function the maximum supported number of bytes, until we have hashed the entire file.
Teach the CommonCrypto hash mechanisms to support large files. The hash primitives take a `CC_LONG` (aka `uint32_t`) at a time. So loop to give the hash function at most an unsigned 32 bit's worth of bytes until we have hashed the entire file.
Writing very large files may be slow, particularly on inefficient filesystems and when running instrumented code to detect invalid memory accesses (eg within valgrind or similar tools). Introduce `GITTEST_SLOW` so that tests that are slow can be skipped by the CI system.
de3ae26 to
456e521
Compare
|
The new tests were timing out the build during the valgrind run. So I ve added still another environment variable so that we can skip them on our CI runs. Another option is to just stop running the tests marked "invasive" during CI entirely. But I like running as many tests as is feasible during our CI run, even at the expense of bloating our guard environment variables. I thought about spinning up a background thread to dump something to the console during the tests, but, you know, ugh. I'm not sure which is worse: tests failing because they took more than 10 minutes to run, or tests that just keep running. |
|
I'd like to unblock users who are facing this. I'm going to merge this unless there are complaints. |
|
Going down the rabbit hole once again |
As reported at libgit2/libgit2sharp#1515, we are unable to inflate large loose blobs (that are larger than 4GB). (In that issue, we create a large loose blob but fail to read it immediately after that.) The culprit, of course, is 32 bitness: in a number of places we either improperly truncate 64 bit values via casting, or fail to read/write in a loop for APIs that take blocks of data, attempting to give them the entire blob at a time.
Fix this by:
git_zstreamAPI instead of using zlib directly. Thegit_zstreamAPI simplifies the common inflate or deflate experience and helps eliminate common problems like neglecting to loop to process the entire file.Also add a test for reading/writing loose objects, guarded by an environment variable to avoid running by default.