-
Notifications
You must be signed in to change notification settings - Fork 0
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
Include response chunking info in nonce #3
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package ads_test | ||
|
||
import ( | ||
"log" | ||
|
||
"github.com/linkedin/diderot/ads" | ||
) | ||
|
||
func ExampleParseRemainingChunksFromNonce() { | ||
// Acquire a delta ADS client | ||
var client ads.DeltaClient | ||
|
||
var responses []*ads.DeltaDiscoveryResponse | ||
for { | ||
res, err := client.Recv() | ||
if err != nil { | ||
log.Panicf("Error receiving delta response: %v", err) | ||
} | ||
responses = append(responses, res) | ||
|
||
if ads.ParseRemainingChunksFromNonce(res.Nonce) == 0 { | ||
break | ||
} | ||
} | ||
|
||
log.Printf("All responses received: %+v", responses) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -92,7 +92,7 @@ type deltaSender struct { | |
queuedUpdates []queuedResourceUpdate | ||
// The minimum size an encoded chunk will serialize to, in bytes. Used to check whether a given | ||
// update can _ever_ be sent, and as the initial size of a chunk. Note that this value only depends | ||
// on utils.NonceLength and the length of typeURL. | ||
// on utils.MaxNonceLength and the length of typeURL. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there seems to be no MaxNonceLength? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops |
||
minChunkSize int | ||
} | ||
|
||
|
@@ -174,6 +174,11 @@ func (ds *deltaSender) chunk(resourceUpdates map[string]entry) (chunks []*ads.De | |
"typeURL", ds.typeURL, | ||
"updates", len(ds.queuedUpdates), | ||
) | ||
for i, c := range chunks { | ||
c.Nonce = utils.NewNonce(len(chunks) - i - 1) | ||
} | ||
} else { | ||
chunks[0].Nonce = utils.NewNonce(0) | ||
} | ||
|
||
return chunks | ||
|
@@ -182,7 +187,6 @@ func (ds *deltaSender) chunk(resourceUpdates map[string]entry) (chunks []*ads.De | |
func (ds *deltaSender) newChunk() *ads.DeltaDiscoveryResponse { | ||
return &ads.DeltaDiscoveryResponse{ | ||
TypeUrl: ds.typeURL, | ||
Nonce: utils.NewNonce(), | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,8 +2,9 @@ package utils | |
|
||
import ( | ||
"encoding/base64" | ||
"encoding/binary" | ||
"encoding/hex" | ||
"slices" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
|
@@ -12,24 +13,33 @@ import ( | |
"google.golang.org/protobuf/proto" | ||
) | ||
|
||
// NonceLength is the length of the string returned by NewNonce. NewNonce encodes the current UNIX | ||
// time in nanos in hex encoding, so the nonce will be 16 characters if the current UNIX nano time is | ||
// greater than 2^60-1. This is because it takes 16 hex characters to encode 64 bits, but only 15 to | ||
// encode 60 bits (the output of strconv.FormatInt is not padded by 0s). 2^60-1 nanos from epoch time | ||
// (January 1st 1970) is 2006-07-14 23:58:24.606, which as of this writing is over 17 years ago. This | ||
// is why it's guaranteed that NonceLength will be 16 characters (before that date, encoding the | ||
// nanos only required 15 characters). For the curious, the UNIX nano timestamp will overflow int64 | ||
// some time in 2262, making this constant valid for the next few centuries. | ||
const NonceLength = 16 | ||
|
||
// NewNonce creates a new unique nonce based on the current UNIX time in nanos. It always returns a | ||
// string of length NonceLength. | ||
func NewNonce() string { | ||
// The second parameter to FormatInt is the base, e.g. 2 will return binary, 8 will return octal | ||
// encoding, etc. 16 means FormatInt returns the integer in hex encoding, e.g. 30 => "1e" or | ||
// 1704239351400 => "18ccc94c668". | ||
const hexBase = 16 | ||
return strconv.FormatInt(time.Now().UnixNano(), hexBase) | ||
const ( | ||
// NonceLength is the length of the string returned by NewNonce. NewNonce encodes the current UNIX | ||
// time in nanos and the remaining chunks, encoded as 64-bit and 32-bit integers respectively, then | ||
// hex encoded. This means a nonce will always be 8 + 4 bytes, multiplied by 2 by the hex encoding. | ||
NonceLength = (8 + 4) * 2 | ||
) | ||
|
||
// NewNonce creates a new unique nonce based on the current UNIX time in nanos, always returning a | ||
// string of [NonceLength]. | ||
func NewNonce(remainingChunks int) string { | ||
Comment on lines
+23
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a smart yet big change to the nonce. To make sure it's backward compatible, I checked Rest.li xds client is not using the nonce in any way currently. May want to let Xin and Yan know to make sure Envoy is not using it too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No one is, it's not supposed to be used for anything other than ACKs/NACKs, it's not meant to be interpreted more than that. Envoy doesn't use it at all for sure. |
||
return newNonce(time.Now(), remainingChunks) | ||
} | ||
|
||
func newNonce(now time.Time, remainingChunks int) string { | ||
// preallocating these buffers with constants (instead of doing `out = make([]byte, len(buf) * 2)`) | ||
// means the compiler will allocate them on the stack, instead of heap. This significantly reduces | ||
// the amount of garbage created by this function, as the only heap allocation will be the final | ||
// string(out), rather than all of these buffers. | ||
buf := make([]byte, NonceLength/2) | ||
out := make([]byte, NonceLength) | ||
|
||
binary.BigEndian.PutUint64(buf[:8], uint64(now.UnixNano())) | ||
binary.BigEndian.PutUint32(buf[8:], uint32(remainingChunks)) | ||
|
||
hex.Encode(out, buf) | ||
|
||
return string(out) | ||
} | ||
|
||
func GetTypeURL[T proto.Message]() string { | ||
|
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.
Should this return
-1
instead of0
or some other special value if the nonce didn't match the expected format? It could also returnremainingChunks, err
if that would be more idiomaticOtherwise, someone using
ParseRemainingChunksFromNonce
would be unable to distinguish "this is the last chunk in the response" from "the nonce didn't match the format so we don't know how many chunks remain in the response", 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.
That's kind of the point actually. By default, the behavior in xDS is to not wait for chunks, since that notion doesn't really exist. Ultimately, if the nonce doesn't have the right format, then the only valid behavior is not to wait, that's why it just returns 0. There's literally nothing a caller of this function could do.
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.
Zoab brought up a good point offline which is that returning an error at least signals to callers that the nonce wasn't in the right format, and even though they can't do anything about it, they can still log a warning.