-
Notifications
You must be signed in to change notification settings - Fork 452
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
Allow messages to be (de/)serialized from Unsafe[Mutable]RawBufferPointer #883
Conversation
…nter. This change allows users who already have a buffer allocated to serialize a message straight into that buffer, avoiding an additional copy via Data.
/// This should be used with `serializeBytes(into:partial:)` so that enough space may | ||
/// be allocated for the buffer so that encoding can proceed without bounds checks | ||
/// or reallocation. | ||
public func serializedBinarySize() throws -> Int { | ||
// Note: since this api is internal, it doesn't currently worry about |
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 wasn't sure how to handle this comment: it seems like we shouldn't need to check for required fields as that's left to the serialize methods?
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 just promote it to a public comment.
Quick comment:
We'd sorta been avoiding this because it lets folks fall into the trap of calling it and then fetching the |
Ah, okay, that makes sense. I suppose another option would be keeping it internal and changing |
@@ -31,26 +31,24 @@ extension Message { | |||
if !partial && !isInitialized { | |||
throw BinaryEncodingError.missingRequiredFields | |||
} |
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 should get removed, otherwise the validation is done twice because of the chaining of calls.
/// - Returns: The number of bytes written into the buffer. | ||
/// - Throws: `BinaryEncodingError` if encoding fails. | ||
@discardableResult | ||
public func serializeBinary(into buffer: UnsafeMutableRawBufferPointer, partial: Bool = false) throws -> Int { |
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 you wrap this like the others are, we mostly try to keep to 80c
extensions: extensions) | ||
try decoder.decodeFullMessage(message: &self) | ||
} | ||
try self.merge(body: body, extensions: extensions, options: options) | ||
} | ||
} | ||
if !partial && !isInitialized { |
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.
Aren't we checking twice here also? I guess here you need it just in an else
clause for when data was empty.
/// `BinaryDecodingError.missingRequiredFields`. | ||
/// - options: The BinaryDecodingOptions to use. | ||
/// - Throws: `BinaryDecodingError` if decoding fails. | ||
public init( |
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 group the two init
methods together?
/// `BinaryEncodingError.missingRequiredFields`. | ||
/// - Returns: The number of bytes written into the buffer. | ||
/// - Throws: `BinaryEncodingError` if encoding fails. | ||
@discardableResult |
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.
Seems like we shouldn't add this annotation as callers should always use this value, no?
@@ -42,6 +42,22 @@ extension PBTestHelpers where MessageTestType: SwiftProtobuf.Message & Equatable | |||
} catch { | |||
XCTFail("Failed to decode protobuf: \(string(from: encoded))", file: file, line: line) | |||
} | |||
|
|||
let expectedSize = try configured.serializedBinarySize() |
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 add tests that use serializeBinary(into:)
with a few more cases:
- a buffer a little too short and a message with fields set to ensure that a proper error is thrown (looks like
BinaryEncodingError
doesn't have something for "too short", so the current code might not support this case (just assert), so it might take a lot of plumbing to get this right. - a zero length buffer and a message with no fields set (since the binary size will be zero) and ensure it passes
- a zero length buffer and a message with fields set to ensure that does error.
- a buffer larger than needed and a message with fields set to ensure it works and reports the correct size.
- a buffer larger than needed and a message with no fields set to ensure it works and reports zero.
That was about the only thing I could think of, but I wasn't sure how well that would work for consumers. Would that work for grpc/swiftnio based things? |
I'm not sure that works, actually. MutableRawBufferPointers are vended only as closure arguments in Swift; they're never returned as results. I think you'd have to do something like this:
That is, the provider is given the size of the message. It then calls another provided function with the requested buffer pointer. |
Rather than providing an API that works with unsafe pointers, we should consider using the new data protocols in Foundation that come as part of 5.1. My comment in the related issue:
|
You're totally right, thanks. It feels a little clunky but I don't think that's necessarily a bad thing considering it's "unsafe". |
Are you opposed to having an unsafe pointer API entirely? Using the new protocols with NIOs |
Closing this in favour of using |
This change allows users who already have a buffer allocated to
serialize a message straight into that buffer, avoiding an additional
copy via Data.
The motivation behind this is that in Swift gRPC the currency for
storing bytes is NIOs
ByteBuffer
, and we'd like to avoid havingan additional copy from going via
Data
. As such this changeserializedDataSize
public and renames itserializedBinarySize
serializeBinary(into:partial:)
method, andinit
toMessage
to read from anUnsafeRawBufferPointer
.A couple of errors in the documentation were also fixed.
I also saw issue #816 after implementing this so more than happy to
hear what everyone has to think about this!