Skip to content
Open
Show file tree
Hide file tree
Changes from all 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