-
Notifications
You must be signed in to change notification settings - Fork 651
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
Make tests work on 32-bit #486
Conversation
Motivation: Make tests compile on Raspberry Pi. Modifications: Change tests so that they compile on 32-bit machines, like Raspi. Replaced 0xDEADBEEF which doesn't fit into an Int32, w/ 0x1337BEEF. Also use TimeAmount.Value in one test. Result: Tests will compile on Raspi ...
Motivation: The testAllocationOfReallyBigByteBuffer fails because ByteBuffer has some UInt32 dependencies (which is >Int on 32-bit) Modifications: Fallback to Int32 in some places, reduce really big buffer size on 32-bit. Result: The test survives a few Swift Int asserts, but on Raspi it still eventually fails in mempcy. Don't know why.
Motivation: Make tests work on 32-bit. Modifications: Reduced the `writevLimitBytes` from Int32.max to Int32.max / 4. Int32 produces too many edge conditions on 32-bit. This is not optimal, but should be fine in practice. Result: More ChannelTests run.
Motivation: Make tests work on 32-bit. Modifications: Reduced `maxNIOFrameSize` from Int32.max to Int32.max / 4 on `arm` platforms. Int32 produces too many edge conditions on 32-bit. This is not optimal, but should be fine in practice. Result: WebSocket tests run.
Motivation: If the tests don't run, nothing else will. Modifications: - make the testAllocationOfReallyBigByteBuffer always fail on Raspi, produces a low-level fault which needs to be investigated eventually: Thread 1 "swift-nioPackag" received signal SIGSEGV, Segmentation fault. __memcpy_neon () at ../sysdeps/arm/armv7/multiarch/memcpy_impl.S:362 362 ../sysdeps/arm/armv7/multiarch/memcpy_impl.S: No such file or directory. - Reduce "lotsOfData" tests from Int32.max to Int32.max/8 bytes. Full Int32.max hits too many edge conditions in NIO. Result: Test suite runs through w/o hard crashes on Raspi 32-bit / Swift 4.1. Test Suite 'All tests' failed at 2018-06-12 12:55:45.411 Executed 711 tests, with 2 failures (0 unexpected) in 608.633 (608.633) seconds real 15m19.413s user 35m29.010s sys 1m15.440s
Motivation: Make it compile on 64-bit Intel again. Modifications: Just some type casting. Result: Builds on 64-bit again.
Can one of the admins verify this patch? |
1 similar comment
Can one of the admins verify this patch? |
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.
Thanks @helje5! I've left a few small notes inline but this mostly looks great.
let max = UInt32(Int32.max) | ||
#else | ||
let max = UInt32.max | ||
#endif |
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.
This change violates the doc comment up above, so we should probably change this.
Incidentally, I assume the reason for this change is that malloc
accepts Int
on 32-bit platforms, so returning UInt32.max is not a super great plan, so it may be worth changing the doc comment to say something like:
"Returns the next power of two unless that would overflow, in which case UInt32.max (on 64-bit systems) or Int32.max (on 32-bit systems) is returned. The returned value is always safe to be cast to Int
and passed to malloc
on all platforms."
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.
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.
This was probably to support the sysMalloc(Int)
issue. Looks like you changed that upstream to sysMalloc(size_t)
, so it may not be necessary anymore.
// Note(hh): This is not a _proper_ fix, but necessary because | ||
// other places extend on that. Should be fine in | ||
// practice on 32-bit platforms. | ||
return Int(Int32.max / 4) |
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.
Hrm, I don't love this. AFAICT this change works around the fact that we have a test that validates what happens if we attempt to write more than writevLimitBytes
, which does some unguarded addition on this value.
For the test in ChannelTests.swift
, it may be best to change it on 32-bit systems to create the buffer at writevLimitBytes
and adjust the expected return values. That's the largest possible allocated buffer on 32-bit NIO anyway.
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.
No, there are more NIO issues wrt that. I.e. ByteBuffer uses UInt32 and nextPoW2 to round up the size, which subsequently fails when casting backt to malloc, which wants an Int.
It simply is no good to live on that edge on 32-bit platforms. IMO it is not worthwhile to adjust all of NIO just for the 32-bit edge case. And you don't do similar edge-checks for 64-bit either (but stick to Int32.max ;-) )
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.
ByteBuffer uses UInt32 and nextPoW2 to round up the size, which subsequently fails when casting backt to malloc
Doesn't this patch change that?
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.
I tracked down this specific thing, yes (but stuff kept failing). What I was trying to say is that it probably makes no sense to support that max-addressable-memory edge-case in NIO (generally). If you really want to, I suggest starting out by writing tests that check against Int64.max on 64-bit too. It is not worth it, IMO.
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.
@helje5 I don't understand why Int32.max
doesn't work here. It fits into an Int
on both 32 and 64 bit platforms.
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.
// Note(hh): This is not a _proper_ fix, but necessary because
// other places extend on that. Should be fine in
// practice on 32-bit platforms.
Well, it is in the comment above it (and in my reply above yours). It is not a proper fix, but it is a lot of work to track down all edge cases (for little gain).
P.S.: Maybe a lot of that went away when you changed sysMalloc(Int) to sysMalloc(size_t).
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.
@helje5 could we re-check this?
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.
@helje5 if you could just re-check that this is still the case. And then just remove the two note(hh)
comments with a good explanation why we need (if we need) special casing.
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.
if that's done we can merge I think.
// Note(hh): This is not a _proper_ fix, but necessary because | ||
// other places extend on that. Should be fine in | ||
// practice on 32-bit platforms. | ||
private let maxNIOFrameSize = Int(Int32.max / 4) |
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.
Any reason not to just set this to Int(Int32.max )
? So far as I know we don't ever do math on this, so setting it to Int32.max
gets the behaviour as close as reasonably possible to the 64-bit behaviour.
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.
Same as above. Probably breaks when it travels to the ByteBuffer, which will use UInt32 and then fail.
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.
we should fully understand this. I think Int32.max
should just work
Tests/NIOTests/ByteBufferTest.swift
Outdated
@@ -1073,22 +1073,37 @@ class ByteBufferTest: XCTestCase { | |||
} | |||
|
|||
func testAllocationOfReallyBigByteBuffer() throws { | |||
#if !arch(arm) // !32-bit, Raspi/AppleWatch/etc |
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.
Can we invert this conditional? It'd be nice to see the intended effect of the other branch closer to the top of the test.
Tests/NIOTests/ByteBufferTest.swift
Outdated
@@ -1073,22 +1073,37 @@ class ByteBufferTest: XCTestCase { | |||
} | |||
|
|||
func testAllocationOfReallyBigByteBuffer() throws { | |||
#if !arch(arm) // !32-bit, Raspi/AppleWatch/etc |
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.
I assume this conditional is intended to be temporary? If it's not, can we remove the tests for arch(arm)
elsewhere in this test function, as they're unreachable.
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.
Yes. This fails in a specific ARM memcpy, I hope that we get a fix for that eventually. I raised this in the ARM-community slack. The checks within make the test working until that memcpy error. (maybe that memcpy has additional size or alignment restrictions, no idea yet).
@@ -169,9 +169,15 @@ public class ChannelTests: XCTestCase { | |||
for _ in 0..<bufferSize { | |||
buffer.write(staticString: "a") | |||
} | |||
|
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.
Mind removing this whitespace line?
XCTAssertEqual(0xdeadbeef, Int(bitPattern: iovecs.last!.iov_base)) | ||
XCTAssertEqual(0xdeadbeef, iovecs.last!.iov_len) | ||
XCTAssertEqual(0x1337beef, Int(bitPattern: iovecs.last!.iov_base)) | ||
XCTAssertEqual(0x1337beef, iovecs.last!.iov_len) |
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.
Out of interest why does the old value prevent compilation?
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.
Int(0xdeadbeef) (which the compiler generates for literals) overflows Int32
(which then asserts). The other option which I think I actually used in the original patch is 0xdeadbeef as UInt
or sth like that. I don't mind either.
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.
Might be better to use that other version, just for consistency with ourselves (we use deadbeef
as our marker value elsewhere in the code)
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.
I'd propose 0xdedbeef
or 0xdadbeef
or smth but only if we can't use 0xdeadbeef
(which fits fine in a UInt32
)
Just noticed that this PR is appropriately numbered: #486 |
OK, I incorporated the easy changes requested, but I don't have the time to harden NIO for the max-mem edge case. So your choice whether you want no tests or all tests with no memory-edge-case like on 64-bit. Or maybe someone else steps in. |
Do not merge yet, I may have to revert another deadbeef to 1337beef, tests coredump again. |
Was crashing the tests. Back to 0x1337beef which works everywhere.
OK, done. Runs through again. |
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.
Mind changing the remaining instances of 1337beef to deadbeef as UInt
? There are a few more. 😄
@Lukasa I originally changed all, but some didn't work. See the commit log. E.g.:
(some API is expecting Int, not UInt on 32-bit) |
Ok, I think I'm happy. @weissi want to review? |
Sources/NIO/ByteBuffer-core.swift
Outdated
@@ -332,7 +332,7 @@ public struct ByteBuffer { | |||
self._slice = _ByteBufferSlice(_slice.lowerBound ..< self._storage.capacity) | |||
} | |||
} | |||
assert(self._slice.lowerBound + index + capacity <= self._slice.upperBound) | |||
assert(self._slice.lowerBound + index + capacity <= self._slice.upperBound) // TODO(hh): fails on 32-bit |
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.
Remove the TODO ?
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.
@helje5 why does this fail on 32-bit ? If that fails, the code above doesn't work
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.
Probably was a temporary note for myself and I fixed it on the calling side, need to check.
Sources/NIO/ByteBuffer-core.swift
Outdated
@@ -231,7 +231,7 @@ public struct ByteBuffer { | |||
} | |||
|
|||
private static func allocateAndPrepareRawMemory(bytes: Capacity, allocator: Allocator) -> UnsafeMutableRawPointer { | |||
let bytes = Int(bytes) | |||
let bytes = Int(bytes) /* Note(hh): this can fail on 32-bit (Int32 vs UInt32 Capacity)) */ |
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.
remove the Note ?
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.
I'm also not sure why this is an Int
. malloc
takes size_t
which is a UInt
. Maybe our allocator interface needs to change? Hope it's not public
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.
ping @helje5
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.
Just checked: size_t
is unsigned int
on the 32-bit RasPi Ubuntu (I kinda assumed that it might be signed int
because quite often you can't actually alloc more than 2GB on 32-bit platforms).
It probably did fail when one of the max-out tests passed in 0xDEADBEEF, which will fail the Int(bytes)
conversion.
Why you are using Int
I don't know, I didn't introduce this. But your code says:
let sysMalloc: @convention(c) (Int) -> UnsafeMutableRawPointer? = malloc
I don't know, maybe you should just use size_t
?
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.
Moved this one to: #499.
|
||
XCTAssertEqual(AllocationExpectationState.reallocDone, testAllocationOfReallyBigByteBuffer_state) | ||
XCTAssertEqual(buf.capacity, Int(UInt32.max)) | ||
#if arch(arm) // 32-bit, Raspi/AppleWatch/etc |
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.
instead of indenting everything, can we just do
#if arch(arm)
// this test fails on 32 bit platforms
return
#endif
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.
I don't care if you prefer that, but visually an indent makes sense, IMO. Sometimes people do a 4-indent for regular Swift code and indent the #
stuff by 2, which I also kinda like.
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.
Oh, you are saying I should return early, not remove the actual indent. But this will produce a Swift compiler warning wrt unreachable code.
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.
ahh, ok, then leave this as is.
Tests/NIOTests/ChannelTests.swift
Outdated
#if arch(arm) // 32-bit, Raspi/AppleWatch/etc | ||
let lotsOfData = Int(Int32.max / 8) | ||
#else | ||
let lotsOfData = Int(INT32_MAX) // TBD(hh): should that be Int32.max? |
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.
please use Int32.max
|
||
#if arch(arm) // 32-bit, Raspi/AppleWatch/etc | ||
let lotsOfData = Int(Int32.max / 8) |
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.
why do we use less data here? Is that documented to be the maximum?
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.
Why do you use Int32.max
instead of Int64.max
on 64-bit? Same reason ;-) (allocating 4GB on 64-bit is usually OK, but a Raspi has only 512MB or maybe 1GB of RAM - and no swap by default either. so lets not overdo this test).
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.
but we're only allocating 2MB here, right? We'll write the same buffer over and over again, this should work. The sizes were chosen so it will exhaust both the number of bytes limit (IIRC 2GB) as well as the number of chunks (1024) you're allowed to give to writev
. If we think this isn't worthwhile, then we can lower the number for all platforms. CC @Lukasa
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.
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.
Oh, I see. You just repeatedly write the 2MB buffer. Maybe the overflow happens because of <=
? I guess I need to check this one on a Raspi again.
(I mean, internally it still has to spool up Int32.max of buffers, which won't work out well, right?)
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.
it'd be awesome if you could validate why it fails. There must be something else wrong.
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.
Well, see my () comment:
internally it still has to spool up Int32.max of buffers, which won't work out
Motivation: Get the PR merged. Modifications: - drop two `// Note(hh)` comments - use Int32.max Result: Beauty. Pure beauty.
Not sure why |
I think this should be good now? I don't know how/why GitHub is confused. |
public func nextPowerOf2ClampedToMax() -> UInt32 { | ||
guard self > 0 else { | ||
return 1 | ||
} | ||
|
||
var n = self | ||
|
||
#if arch(arm) // 32-bit, Raspi/AppleWatch/etc | ||
let max = UInt32(Int32.max) |
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.
this is maybe nit-picking but we'd like it to be Int.max
(yes, same as Int32.max
on 32-bit platforms) because really we'd be okay with it being larger but Swift uses the Int
type so maybe it's clearer to users? I'm okay with leaving that too though. @Lukasa
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.
Fine with changing this to Int.max
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.
I'm not entirely sure about this. You want UInt32(Int.max)
? That would fail on 64-bit platforms? This really only is for the UInt32(Int32.max)
case, and it makes sense to be explicit here, no?
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.
yes UInt32(Int.max)
would be good. I want it to fail if it's compiled on 64-bits. This thing should only be there for 32-bit platforms
Sources/NIO/ByteBuffer-int.swift
Outdated
@@ -105,21 +105,27 @@ extension FixedWidthInteger { | |||
} | |||
|
|||
extension UInt32 { | |||
/// Returns the next power of two unless that would overflow in which case UInt32.max is returned. | |||
/// Returns the next power of two unless that would overflow, in which case UInt32.max (on 64-bit systems) or Int32.max (on 32-bit systems) is returned. The returned value is always safe to be cast to Int and passed to malloc on all platforms. |
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.
mind breaking that line in two so it's doesn't go over 120 characters ideally?
n -= 1 | ||
n |= n >> 1 | ||
n |= n >> 2 | ||
n |= n >> 4 | ||
n |= n >> 8 | ||
n |= n >> 16 | ||
if n != .max { | ||
if n != max { |
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.
do we actually have a test that on 32-bit systems tests that this returns the right value?
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.
There is no test for this on 64-bit either. /shrug
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.
that's bad, we should definitely have one
... as requested by @weissi. 78 or _maybe_ 80 would be right though. Hope this compiles nevertheless.
@helje5 let's get this merged. If you address the remaining comments then we can get this through really quickly |
@swift-nio-bot test this please |
@weissi I'm a little busy with other stuff. What exactly is still missing? |
@swift-nio-bot test this please |
* 32-bit fix for tests: Replace 0xDEADBEEF w/ 0x1337BEEF Motivation: Make tests compile on Raspberry Pi. Modifications: - Change tests so that they compile on 32-bit machines, like Raspi. Replaced 0xDEADBEEF which doesn't fit into an Int32, w/ 0x1337BEEF. - make ByteBuffer work better on 32-bit - fix other wrong assertions and limits that are too big on 32bit Also use TimeAmount.Value in one test. Result: Tests will compile on Raspi ... Motivation: Explain here the context, and why you're making that change. What is the problem you're trying to solve. Modifications: Describe the modifications you've done. Result: After your change, what will change.
Ported test code to 32-bit Linux (Raspi)
Motivation:
Nothing will work without tests!
Modifications:
NIO has edge conditions wrt Int32.max. Mostly workaround this by using lower values, or adjusting types.
Also replaced some 0xdeadbeef w/ 0x1337beef in the tests to make stuff compile.
Result:
The test suite runs through w/o hard crashes. One test doesn't run at all (testAllocationOfReallyBigByteBuffer), another one fails regularly.
But at least the suite runs through now!