Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/gregjones/httpcache v0.0.0-20171119193500-2bcd89a1743f
github.com/launchdarkly/ccache v1.1.0
github.com/launchdarkly/eventsource v1.10.0
github.com/launchdarkly/go-jsonstream/v3 v3.1.1
github.com/launchdarkly/go-jsonstream/v3 v3.1.2-0.20260729181618-28aa1e35cda3
github.com/launchdarkly/go-ntlm-proxy-auth v1.0.3
github.com/launchdarkly/go-sdk-common/v3 v3.5.0
github.com/launchdarkly/go-sdk-events/v3 v3.6.2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ github.com/launchdarkly/ccache v1.1.0 h1:voD1M+ZJXR3MREOKtBwgTF9hYHl1jg+vFKS/+VA
github.com/launchdarkly/ccache v1.1.0/go.mod h1:TlxzrlnzvYeXiLHmesMuvoZetu4Z97cV1SsdqqBJi1Q=
github.com/launchdarkly/eventsource v1.10.0 h1:H9Tp6AfGu/G2qzBJC26iperrvwhzdbiA/gx7qE2nDFI=
github.com/launchdarkly/eventsource v1.10.0/go.mod h1:J3oa50bPvJesZqNAJtb5btSIo5N6roDWhiAS3IpsKck=
github.com/launchdarkly/go-jsonstream/v3 v3.1.1 h1:ugupp2eNtwVbr69KCdeUrm1vUf1/3ju4Wdliaob95uY=
github.com/launchdarkly/go-jsonstream/v3 v3.1.1/go.mod h1:ZBjhKq8mhArCtqotGRGnteY6eXpNm1GaOdUSZHh+ZjM=
github.com/launchdarkly/go-jsonstream/v3 v3.1.2-0.20260729181618-28aa1e35cda3 h1:wT4WhK2sqM4j0nJwa+eO7g+XCblsT5myDhiadFaXeA8=
github.com/launchdarkly/go-jsonstream/v3 v3.1.2-0.20260729181618-28aa1e35cda3/go.mod h1:ZBjhKq8mhArCtqotGRGnteY6eXpNm1GaOdUSZHh+ZjM=
github.com/launchdarkly/go-ntlm-proxy-auth v1.0.3 h1:i3V0N+R0Fd2nXfGEVKCBIZ8kyttZ+SRKvBG8cdcphO4=
github.com/launchdarkly/go-ntlm-proxy-auth v1.0.3/go.mod h1:kU5uMfNSTpYE6fIzmAXjFxUdmnaDPUEQ5zKm3RVKUsY=
github.com/launchdarkly/go-ntlmssp v1.0.3 h1:rFxOnnEJ2DzJ+NU0plhXqnldJUwn3wWJFTWKCmaiQdE=
Expand Down
51 changes: 51 additions & 0 deletions internal/datasourcev2/event_parsing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package datasourcev2

import (
"encoding/json"

"github.com/launchdarkly/go-server-sdk/v7/subsystems"
"github.com/launchdarkly/go-server-sdk/v7/subsystems/ldstoretypes"
)

// This file contains the parts of FDv2 protocol event decoding that are shared by both build
// variants. The event decoders themselves are split by build tag:
//
// - event_parsing_default.go: a single-pass jsonstream decoder using jreader.RawValue/Offset for
// zero-copy raw capture. Each event, and in particular each put-object item's JSON, is scanned
// once. Its scalar decoders are written to match encoding/json's acceptance rules (see below).
// - event_parsing_easyjson.go: a reflection-based decode (encoding/json) for the
// launchdarkly_easyjson build, so that no new code depends on the easyjson token reader, which
// is planned for removal from go-jsonstream.
//
// The two variants accept and reject the same payloads. The easyjson build decodes each scalar
// event with encoding/json into the subsystems types; the default build's jsonstream decoders are
// written to mirror that behavior exactly -- including rejecting non-integer version/target values
// on the plain-int types and matching Selector's float64-based (truncating) version handling. Both
// produce a parsedPutObject carrying the item's raw bytes and, for recognized kinds, its parsed
// form, so the ChangeSet's collections are assembled without a later re-parse.

const errNoKnownPollingEvents = "didn't receive any known protocol events in polling payload"

// parsedPutObject is the result of decoding a put-object event's data.
type parsedPutObject struct {
kind subsystems.ObjectKind
key string
version int
// object is the item's raw JSON. In the default build it references the input buffer passed
// to parsePutObjectEventData and is valid only as long as that buffer is.
object json.RawMessage
// item is the parsed representation of object, set only when hasItem is true. hasItem is
// false when the kind is unrecognized, in which case the raw bytes are still retained for
// forwards compatibility.
item ldstoretypes.ItemDescriptor
hasItem bool
}

// addTo adds the put to a change-set builder, carrying the parsed item along when there is one.
func (p parsedPutObject) addTo(builder *subsystems.ChangeSetBuilder) {
if p.hasItem {
builder.AddParsedPut(p.kind, p.key, p.version, p.object, p.item)
} else {
builder.AddPut(p.kind, p.key, p.version, p.object)
}
}
43 changes: 43 additions & 0 deletions internal/datasourcev2/event_parsing_benchmark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package datasourcev2

import (
"context"
"testing"
)

// These benchmarks guard the single-pass polling-payload parser against regressions, and keep the
// previous reflection-based decode path measurable for comparison. The dominant cost of client
// initialization over FDv2 is parsing the initial polling payload, so this path is
// performance-sensitive: every SDK start pays it once per payload.

func BenchmarkParsePollingPayload(b *testing.B) {
body := makePollingBody(b, 2000)
b.SetBytes(int64(len(body)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
changeSet, err := parsePollingPayload(context.Background(), body)
if err != nil {
b.Fatal(err)
}
if _, err := changeSet.Collections(); err != nil {
b.Fatal(err)
}
}
}

func BenchmarkParsePollingPayloadReflectionReference(b *testing.B) {
body := makePollingBody(b, 2000)
b.SetBytes(int64(len(body)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
changeSet, err := parsePollingPayloadReflectionReference(body)
if err != nil {
b.Fatal(err)
}
if _, err := changeSet.Collections(); err != nil {
b.Fatal(err)
}
}
}
Loading
Loading