🌐 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

Commit d230097

Browse files
committed
core: use consistent nl unmarshal, test atomic marshal round trips
Signed-off-by: Aaron Wilson <aawilson@nvidia.com>
1 parent fe855df commit d230097

3 files changed

Lines changed: 190 additions & 49 deletions

File tree

‎ais/ic.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ func (ic *ic) syncICBundle() error {
462462
if !smap.IsIC(ic.p.si) {
463463
return nil
464464
}
465-
if err := jsoniter.Unmarshal(bundle.OwnershipTbl, &ic.p.notifs); err != nil {
465+
if err := cos.JSON.Unmarshal(bundle.OwnershipTbl, &ic.p.notifs); err != nil {
466466
return fmt.Errorf(cmn.FmtErrUnmarshal, ic.p, "ownership table", cos.BHead(bundle.OwnershipTbl), err)
467467
}
468468
return nil

‎ais/prxnotif_internal_test.go‎

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net/http"
1010
"net/http/httptest"
1111
"runtime"
12+
"strconv"
1213
"sync"
1314
"testing"
1415
"time"
@@ -23,6 +24,7 @@ import (
2324
"github.com/NVIDIA/aistore/nl"
2425
"github.com/NVIDIA/aistore/xact"
2526

27+
jsoniter "github.com/json-iterator/go"
2628
. "github.com/onsi/ginkgo/v2"
2729
. "github.com/onsi/gomega"
2830
)
@@ -554,6 +556,123 @@ var _ = Describe("Notifications xaction test", func() {
554556
})
555557
})
556558

559+
func newTestNotifs() *notifs {
560+
return &notifs{
561+
nls: newListeners(),
562+
fin: newListeners(),
563+
}
564+
}
565+
566+
func ownershipTblWithEndTime(t *testing.T, want int64) (*notifs, string) {
567+
t.Helper()
568+
xid := cos.GenUUID()
569+
smap := &meta.Smap{}
570+
targets := meta.NodeMap{"t1": &meta.Snode{DaeID: "t1"}}
571+
sampleNL := xact.NewXactNL(xid, apc.ActECEncode, smap, targets)
572+
sampleNL.EndTimeX.Store(want)
573+
src := newTestNotifs()
574+
if err := src.add(sampleNL); err != nil {
575+
t.Fatalf("add listener: %v", err)
576+
}
577+
return src, xid
578+
}
579+
580+
// Pull path (syncICBundle): ownership table bytes unmarshaled directly into notifs.
581+
func TestOwnershipTblPullJSON(t *testing.T) {
582+
cos.InitShortID(0)
583+
584+
const want = int64(1700000000000000000)
585+
src, xid := ownershipTblWithEndTime(t, want)
586+
data := cos.MustMarshal(src)
587+
wireEndTime := `"EndTimeX":{"v":` + strconv.FormatInt(want, 10) + `}`
588+
if !bytes.Contains(data, []byte(wireEndTime)) {
589+
t.Fatalf("marshaled ownership table missing %s: %s", wireEndTime, data)
590+
}
591+
592+
t.Run("roundTrip", func(t *testing.T) {
593+
dst := newTestNotifs()
594+
if err := cos.JSON.Unmarshal(data, dst); err != nil {
595+
t.Fatalf("unmarshal ownership table: %v", err)
596+
}
597+
got := dst.entry(xid)
598+
if got == nil {
599+
t.Fatal("listener missing after unmarshal")
600+
}
601+
if got.EndTime() != want {
602+
t.Fatalf("EndTimeX round-trip: want %d, got %d", want, got.EndTime())
603+
}
604+
})
605+
// Previous versions did not define marshaling for int64 atomics
606+
// With no exported fields, EndTimeX always marshaled as {}
607+
t.Run("legacyEmptyEndTimeX", func(t *testing.T) {
608+
legacy := bytes.Replace(data, []byte(wireEndTime), []byte(`"EndTimeX":{}`), 1)
609+
dst := newTestNotifs()
610+
if err := cos.JSON.Unmarshal(legacy, dst); err != nil {
611+
t.Fatalf("unmarshal legacy ownership table: %v", err)
612+
}
613+
got := dst.entry(xid)
614+
if got == nil {
615+
t.Fatal("listener missing after legacy unmarshal")
616+
}
617+
if got.EndTime() != 0 {
618+
t.Fatalf("legacy EndTimeX: want 0, got %d", got.EndTime())
619+
}
620+
})
621+
622+
t.Run("bareNumberEndTimeX", func(t *testing.T) {
623+
const bare = int64(12345)
624+
patched := bytes.Replace(data, []byte(wireEndTime), []byte(`"EndTimeX":12345`), 1)
625+
dst := newTestNotifs()
626+
if err := cos.JSON.Unmarshal(patched, dst); err != nil {
627+
t.Fatalf("unmarshal bare-number ownership table: %v", err)
628+
}
629+
got := dst.entry(xid)
630+
if got == nil {
631+
t.Fatal("listener missing after bare-number unmarshal")
632+
}
633+
if got.EndTime() != bare {
634+
t.Fatalf("bare-number EndTimeX: want %d, got %d", bare, got.EndTime())
635+
}
636+
})
637+
}
638+
639+
// Push path (sendOwnershipTbl -> handlePost ActMergeOwnershipTbl): notifs embedded in
640+
// actMsgExt.Value, wire round-trip via jsoniter, then MorphMarshal into notifs.
641+
func TestOwnershipTblActMergeOwnershipTbl(t *testing.T) {
642+
cos.InitShortID(0)
643+
644+
const want = int64(1700000000000000000)
645+
src, xid := ownershipTblWithEndTime(t, want)
646+
647+
sent := &actMsgExt{
648+
ActMsg: apc.ActMsg{
649+
Action: apc.ActMergeOwnershipTbl,
650+
Value: src,
651+
},
652+
}
653+
wire := cos.MustMarshal(sent)
654+
655+
decoded := &actMsgExt{}
656+
if err := jsoniter.Unmarshal(wire, decoded); err != nil {
657+
t.Fatalf("unmarshal actMsgExt (ReadJSON path): %v", err)
658+
}
659+
if decoded.Action != apc.ActMergeOwnershipTbl {
660+
t.Fatalf("action: want %q, got %q", apc.ActMergeOwnershipTbl, decoded.Action)
661+
}
662+
663+
dst := newTestNotifs()
664+
if err := cos.MorphMarshal(decoded.Value, dst); err != nil {
665+
t.Fatalf("MorphMarshal ownership table: %v", err)
666+
}
667+
got := dst.entry(xid)
668+
if got == nil {
669+
t.Fatal("listener missing after MorphMarshal")
670+
}
671+
if got.EndTime() != want {
672+
t.Fatalf("EndTimeX push path: want %d, got %d", want, got.EndTime())
673+
}
674+
}
675+
557676
//
558677
// micro-benches ------- TODO: isolate from TestMain() and side effects - see ais/tgtobj_internal_test.go :TODO
559678
//

‎cmn/atomic/a_test.go‎

Lines changed: 70 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,67 +7,89 @@ package atomic_test
77
import (
88
"encoding/json"
99
"strconv"
10-
stdatomic "sync/atomic"
1110
"testing"
11+
"time"
1212

1313
"github.com/NVIDIA/aistore/cmn/atomic"
14+
"github.com/NVIDIA/aistore/cmn/cos"
15+
16+
jsoniter "github.com/json-iterator/go"
1417
)
1518

19+
// Each atomic type with a custom MarshalJSON/UnmarshalJSON must round-trip
20+
// through cos.MustMarshal (cos.JSON, frozen) into any of the three decoders:
21+
// - default jsoniter: jsoniter.ConfigDefault
22+
// - project frozen config cos.JSON
23+
// - stdlib encoding/json.
24+
var unmarshalers = []struct {
25+
name string
26+
fn func([]byte, any) error
27+
}{
28+
{"jsoniter", jsoniter.Unmarshal},
29+
{"cos.JSON", cos.JSON.Unmarshal},
30+
{"stdlib", json.Unmarshal},
31+
}
32+
1633
func TestInt64JSONRoundTrip(t *testing.T) {
1734
for _, want := range []int64{0, 1, -1, 1 << 30, -1 << 30, 1700000000000000000} {
18-
in := atomic.NewInt64(want)
19-
data, err := json.Marshal(in)
20-
if err != nil {
21-
t.Fatalf("marshal %d: %v", want, err)
22-
}
23-
if string(data) != `{"v":`+strconv.FormatInt(want, 10)+`}` {
24-
t.Fatalf("unexpected wire format for %d: %s", want, data)
25-
}
26-
var got atomic.Int64
27-
if err := json.Unmarshal(data, &got); err != nil {
28-
t.Fatalf("unmarshal %s: %v", data, err)
35+
data := cos.MustMarshal(atomic.NewInt64(want))
36+
if s, exp := string(data), `{"v":`+strconv.FormatInt(want, 10)+`}`; s != exp {
37+
t.Fatalf("wire format for %d: got %s, want %s", want, s, exp)
2938
}
30-
if got.Load() != want {
31-
t.Fatalf("round-trip mismatch: want %d, got %d (json=%s)", want, got.Load(), data)
39+
for _, u := range unmarshalers {
40+
t.Run(strconv.FormatInt(want, 10)+"/"+u.name, func(t *testing.T) {
41+
var got atomic.Int64
42+
if err := u.fn(data, &got); err != nil {
43+
t.Fatalf("unmarshal %s: %v", data, err)
44+
}
45+
if got.Load() != want {
46+
t.Fatalf("round-trip mismatch: want %d, got %d", want, got.Load())
47+
}
48+
})
3249
}
3350
}
3451
}
3552

36-
// Cross-version IC sync compat: new code must accept legacy `{}` (from a
37-
// pre-MarshalJSON peer), and an older default-struct decoder must accept the
38-
// new `{"v":N}` without error - both yielding zero, matching current behavior.
39-
func TestInt64JSONMixedVersionCompat(t *testing.T) {
40-
type newShape struct {
41-
EndTimeX atomic.Int64 `json:"EndTimeX"`
42-
}
43-
type oldShape struct {
44-
EndTimeX stdatomic.Int64 `json:"EndTimeX"`
45-
}
46-
47-
// old -> new: legacy `{}` decodes to zero
48-
var n newShape
49-
if err := json.Unmarshal([]byte(`{"EndTimeX":{}}`), &n); err != nil {
50-
t.Fatalf("new failed to decode legacy {}: %v", err)
51-
}
52-
if n.EndTimeX.Load() != 0 {
53-
t.Fatalf("expected 0 from legacy {}, got %d", n.EndTimeX.Load())
54-
}
55-
56-
// backward-compat: `{"v":N}` parsed to 0 by default struct decoder (unknown key ignored)
57-
var o oldShape
58-
if err := json.Unmarshal([]byte(`{"EndTimeX":{"v":1700000000000000000}}`), &o); err != nil {
59-
t.Fatalf("old failed to decode new format: %v", err)
60-
}
61-
if o.EndTimeX.Load() != 0 {
62-
t.Fatalf("old decoder should yield zero from unknown nested object, got %d", o.EndTimeX.Load())
53+
func TestBoolJSONRoundTrip(t *testing.T) {
54+
for _, want := range []bool{true, false} {
55+
data := cos.MustMarshal(atomic.NewBool(want))
56+
if s, exp := string(data), strconv.FormatBool(want); s != exp {
57+
t.Fatalf("wire format for %v: got %s, want %s", want, s, exp)
58+
}
59+
for _, u := range unmarshalers {
60+
t.Run(strconv.FormatBool(want)+"/"+u.name, func(t *testing.T) {
61+
var got atomic.Bool
62+
if err := u.fn(data, &got); err != nil {
63+
t.Fatalf("unmarshal %s: %v", data, err)
64+
}
65+
if got.Load() != want {
66+
t.Fatalf("round-trip mismatch: want %v, got %v", want, got.Load())
67+
}
68+
})
69+
}
6370
}
71+
}
6472

65-
// forward-compat: new also accepts a bare number
66-
var n2 newShape
67-
if err := json.Unmarshal([]byte(`{"EndTimeX":12345}`), &n2); err != nil {
68-
t.Fatalf("new failed to decode bare number: %v", err)
69-
}
70-
if n2.EndTimeX.Load() != 12345 {
71-
t.Fatalf("expected 12345, got %d", n2.EndTimeX.Load())
73+
func TestTimeJSONRoundTrip(t *testing.T) {
74+
for _, want := range []time.Time{
75+
time.Unix(0, 0),
76+
time.Unix(0, 1),
77+
time.Unix(0, 1700000000000000000),
78+
} {
79+
data := cos.MustMarshal(atomic.NewTime(want))
80+
if s, exp := string(data), strconv.FormatInt(want.UnixNano(), 10); s != exp {
81+
t.Fatalf("wire format for %v: got %s, want %s", want, s, exp)
82+
}
83+
for _, u := range unmarshalers {
84+
t.Run(strconv.FormatInt(want.UnixNano(), 10)+"/"+u.name, func(t *testing.T) {
85+
var got atomic.Time
86+
if err := u.fn(data, &got); err != nil {
87+
t.Fatalf("unmarshal %s: %v", data, err)
88+
}
89+
if got.Load().UnixNano() != want.UnixNano() {
90+
t.Fatalf("round-trip mismatch: want %v, got %v", want.UnixNano(), got.Load().UnixNano())
91+
}
92+
})
93+
}
7294
}
7395
}

0 commit comments

Comments
 (0)