-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Fix String::Buffer
and IO::Memory
capacity to grow beyond 1GB
#13989
Fix String::Buffer
and IO::Memory
capacity to grow beyond 1GB
#13989
Conversation
e9cdf27
to
38ccd21
Compare
38ccd21
to
7f2e9cb
Compare
7f2e9cb
to
d227e8b
Compare
22fe7a9
to
039c9cc
Compare
I have no idea what's going on in wasm32 CI (https://github.com/crystal-lang/crystal/actions/runs/6895836942/job/18764260201?pr=13989). The error seems definitely triggered by the changes in this PR (more precisely d227e8b). But I don't see any direct connection to the failing code, which is in some time function in the spec framework (which runs after the example is completed).
|
1c97581
to
abc9a02
Compare
abc9a02
to
5ce06b0
Compare
This needs further investigation into the wasm runtime. |
About the WASM32 issue: Strings are always terminated with a null byte ( |
Yeah, that's correct. This extra capacity for the final null byte should be taken care of in all capactity calculations, though. |
The initial buffer allocation accounts for HEADER_SIZE + 1 but the reallocations don't, though you likely account for it in the bytesize when you check for IO::EOFError. BTW: a String bytesize can be exactly Int32::MAX + 1 for the null byte: slice = Slice(UInt8).new(Int32::MAX)
slice[-1] = 97
str = String.new(slice.to_unsafe, Int32::MAX)
p str[-1] # => 'a' (97) I tried to make String::Builder always reallocate to bytesize + header size + 1, but I get crashes. |
I reproduce the wasm32 error on my local with your branch. I wrote a patch that always allocates bytesize + header size + 1 and the error is gone. That really sounds like a buffer overflow. I'm trying to apply it on your branch. So far I'm having spec errors because I can allocate up to Int32::MAX bytesize (yes, it's accepted in |
The spec passes until we try to build the actual string object, which doesn't allocate anything, but merely writes a few things on the buffer (the trailing null byte and the string header). The spec still succeeds, but calling EDIT: I tried wasmtime v15.0.1, just in case, but I get the same result. |
@ysbaddaden Do you think this we can merge this? |
Yes, the problem isn't your code change, but allocating that much memory in wasm seems to trigger an issue with the wasm libs. The only detail related to the patch is that we can allocate one more byte (see above comment) but what's 1 byte compared to 1GB 😂 |
# range (1 << 30) < new_bytesize < Int32::MAX | ||
return Int32::MAX if new_bytesize > 1 << 30 | ||
|
||
Math.pw2ceil(new_bytesize) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated, but I wonder if we shouldn't change the formula like we did for array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And the converse is also true; that we should cap the max of array too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe time for a Growable
module...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=> #14173
The byte buffer for
IO::Memory
andString::Buffer
grows exponentially by the next power of two bigger then the currently requested size. So for example, if space is requested for 78 bytes, it allocates for a capacity of 128 bytes (1 << 7
).The maximum size of a buffer is
Int32::MAX
, which is just one below a power of tw ((1 << 31) - 1
). So if space is requested for more than1 << 30
bytes, the next power of two would be1 << 31
which is out of range and raisesArithmeticError
.This means, the sizes between
(1 << 30) + 1
and(2 << 31) - 1
would all be valid but cannot be reached by the dynamically growing buffer. If the actually required bytesize does not exeed(2 << 31) - 1
, it should be possible to allocate that much.This patch clamps the maximum cacpacity to
Int32::MAX
. That enables sizes in that range and there will only be an error if that is definitely not enough space.Resolves #13987