diff options
| author | sangtran <tranquangsang12.7@gmail.com> | 2026-08-31 23:51:37 +0700 |
|---|---|---|
| committer | sangtran <tranquangsang12.7@gmail.com> | 2026-08-31 23:51:37 +0700 |
| commit | 6631d8b00e8c5bafea15ea24415c5b55439193ee (patch) | |
| tree | a320af141ba14bb02c669dbccbf00bcc850c6dbb | |
| parent | 96c07bab1086510a55a004e9a1b90c30bc723a5c (diff) | |
| signature | Verified sangtran <tranquangsang12.7@gmail.com> Key: 8DD79823D235E1BB ultimate trust | |
| download | flux-6631d8b00e8c5bafea15ea24415c5b55439193ee.tar.gz flux-6631d8b00e8c5bafea15ea24415c5b55439193ee.zip | |
Verified bug: fix memory_state clear and guard window state empty with implement main.go for test
| -rw-r--r-- | core/operator/window.go | 4 | ||||
| -rw-r--r-- | main.go | 55 | ||||
| -rw-r--r-- | state/memory_backend.go | 21 | ||||
| -rw-r--r-- | state/memory_state.go | 18 |
4 files changed, 65 insertions, 33 deletions
diff --git a/core/operator/window.go b/core/operator/window.go index 610f6e3..3f75d60 100644 --- a/core/operator/window.go +++ b/core/operator/window.go | |||
| @@ -79,6 +79,10 @@ func (o *WindowOperator[IN, OUT]) Process(ctx context.Context, record core.Strea | |||
| 79 | return err | 79 | return err |
| 80 | } | 80 | } |
| 81 | 81 | ||
| 82 | if len(currentWindowState) == 0 { | ||
| 83 | continue | ||
| 84 | } | ||
| 85 | |||
| 82 | // NOTE: this is workaround create inline window, refactor later | 86 | // NOTE: this is workaround create inline window, refactor later |
| 83 | 87 | ||
| 84 | if err := o.userfunc.Apply(ctx, timer.Key, timer.Namespace.(core.Window), currentWindowState, out); err != nil { | 88 | if err := o.userfunc.Apply(ctx, timer.Key, timer.Namespace.(core.Window), currentWindowState, out); err != nil { |
| @@ -19,24 +19,24 @@ type Transaction struct { | |||
| 19 | Timestamp int64 | 19 | Timestamp int64 |
| 20 | } | 20 | } |
| 21 | 21 | ||
| 22 | type TransactionSummary[IN Transaction, OUT Transaction] struct { | 22 | type TransactionSummary[IN Transaction, OUT string] struct { |
| 23 | totalTransaction int | 23 | totalTransaction int |
| 24 | totalAmount int | ||
| 25 | average int | 24 | average int |
| 26 | } | 25 | } |
| 27 | 26 | ||
| 28 | func (t *TransactionSummary[IN, OUT]) Apply(ctx context.Context, key string, window core.Window, in []Transaction, out transport.Emitter[Transaction]) error { | 27 | func (t *TransactionSummary[IN, OUT]) Apply(ctx context.Context, key string, window core.Window, in []Transaction, out operator.Emitter[string]) error { |
| 28 | totalAmount := 0 | ||
| 29 | for _, v := range in { | 29 | for _, v := range in { |
| 30 | t.totalAmount += v.Amount | 30 | totalAmount += v.Amount |
| 31 | out.Emit(ctx, core.StreamRecord[Transaction]{ | ||
| 32 | Value: v, | ||
| 33 | Type: core.RecordData, | ||
| 34 | Timestamp: v.Timestamp, | ||
| 35 | }) | ||
| 36 | } | 31 | } |
| 37 | 32 | ||
| 38 | return nil | 33 | msg := fmt.Sprintf("Khách: %s | Số đơn: %d | Tổng tiền: %d$", key, len(in), totalAmount) |
| 39 | } | 34 | |
| 35 | return out.Emit(ctx, core.StreamRecord[string]{ | ||
| 36 | Value: msg, | ||
| 37 | Type: core.RecordData, | ||
| 38 | Timestamp: window.MaxTimestamp(), | ||
| 39 | })} | ||
| 40 | 40 | ||
| 41 | func main() { | 41 | func main() { |
| 42 | // init | 42 | // init |
| @@ -55,25 +55,45 @@ func main() { | |||
| 55 | { | 55 | { |
| 56 | User: "Sang", | 56 | User: "Sang", |
| 57 | Amount: 4, | 57 | Amount: 4, |
| 58 | Timestamp: 1200, | 58 | Timestamp: 1500, |
| 59 | }, | ||
| 60 | { | ||
| 61 | User: "Sang", | ||
| 62 | Amount: 10, | ||
| 63 | Timestamp: 2000, | ||
| 64 | }, | ||
| 65 | { | ||
| 66 | User: "Sang", | ||
| 67 | Amount: 12, | ||
| 68 | Timestamp: 2500, | ||
| 59 | }, | 69 | }, |
| 60 | { | 70 | { |
| 61 | User: "Sang", | 71 | User: "Sang", |
| 62 | Amount: 10, | 72 | Amount: 10, |
| 63 | Timestamp: 1600, | 73 | Timestamp: 3000, |
| 64 | }, | 74 | }, |
| 65 | } | 75 | } |
| 66 | 76 | ||
| 67 | ch1 := make(chan core.StreamRecord[Transaction], len(txList)) | 77 | ch1 := make(chan core.StreamRecord[Transaction], len(txList)) |
| 68 | ch2 := make(chan core.StreamRecord[Transaction], len(txList)) | 78 | ch2 := make(chan core.StreamRecord[Transaction], len(txList)) |
| 79 | ch3 := make(chan core.StreamRecord[Transaction], len(txList)) | ||
| 80 | ch4 := make(chan core.StreamRecord[string], len(txList)) | ||
| 69 | 81 | ||
| 70 | wo := operator.NewWatermarkGeneratorOperator[Transaction](0, func(in Transaction) int64 { | 82 | wo := operator.NewWatermarkGeneratorOperator[Transaction](0, func(in Transaction) int64 { |
| 71 | return in.Timestamp | 83 | return in.Timestamp |
| 72 | }) | 84 | }) |
| 73 | 85 | ||
| 86 | ko := operator.NewKeyByOperator[Transaction](func(in Transaction) string { | ||
| 87 | return in.User | ||
| 88 | }) | ||
| 89 | |||
| 90 | wop := operator.NewWindowOperator[Transaction, string](core.NewTumblingWindows[Transaction](1000), &TransactionSummary[Transaction, string]{}) | ||
| 91 | |||
| 74 | var wg sync.WaitGroup | 92 | var wg sync.WaitGroup |
| 75 | 93 | ||
| 76 | runtime.RunTask[Transaction, Transaction](ctx, transport.NewLocalReceiveChannel(ch1), transport.NewLocalEmitChannel(ch2), wo) | 94 | runtime.RunTask(ctx, transport.NewLocalReceiveChannel(ch1), transport.NewLocalEmitChannel(ch2), wo) |
| 95 | runtime.RunTask(ctx, transport.NewLocalReceiveChannel(ch2), transport.NewLocalEmitChannel(ch3), ko) | ||
| 96 | runtime.RunTask(ctx, transport.NewLocalReceiveChannel(ch3), transport.NewLocalEmitChannel(ch4), wop) | ||
| 77 | 97 | ||
| 78 | wg.Go(func() { | 98 | wg.Go(func() { |
| 79 | for i, tx := range txList { | 99 | for i, tx := range txList { |
| @@ -84,13 +104,12 @@ func main() { | |||
| 84 | }) | 104 | }) |
| 85 | 105 | ||
| 86 | wg.Go(func() { | 106 | wg.Go(func() { |
| 87 | for v := range ch2 { | 107 | for v := range ch4 { |
| 88 | if v.Type == core.RecordData { | 108 | if v.Type == core.RecordData { |
| 89 | fmt.Println("data: ", v.Value) | 109 | fmt.Println("Total Amount after: ", v.Value) |
| 90 | } else { | ||
| 91 | fmt.Println("watermark: ", v.Timestamp) | ||
| 92 | } | 110 | } |
| 93 | } | 111 | } |
| 94 | }) | 112 | }) |
| 113 | |||
| 95 | wg.Wait() | 114 | wg.Wait() |
| 96 | } | 115 | } |
diff --git a/state/memory_backend.go b/state/memory_backend.go index 6594657..f4043a1 100644 --- a/state/memory_backend.go +++ b/state/memory_backend.go | |||
| @@ -2,6 +2,7 @@ package state | |||
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "context" | 4 | "context" |
| 5 | |||
| 5 | "flux/core" | 6 | "flux/core" |
| 6 | ) | 7 | ) |
| 7 | 8 | ||
| @@ -11,9 +12,9 @@ import ( | |||
| 11 | // This should be optimize with ZERO GC Scanning, Memory Alignment | 12 | // This should be optimize with ZERO GC Scanning, Memory Alignment |
| 12 | 13 | ||
| 13 | type MemoryStateBackend struct { | 14 | type MemoryStateBackend struct { |
| 14 | maxParallelism int | 15 | maxParallelism int |
| 15 | currentKey string | 16 | currentKey string |
| 16 | currentGroupKey int | 17 | currentGroupKey int |
| 17 | currentNamespace any | 18 | currentNamespace any |
| 18 | // 4D structure StateName -> KeyGroup -> Key -> Namespace -> Value | 19 | // 4D structure StateName -> KeyGroup -> Key -> Namespace -> Value |
| 19 | states map[string]map[int]map[string]map[any]any | 20 | states map[string]map[int]map[string]map[any]any |
| @@ -36,18 +37,18 @@ func (ms *MemoryStateBackend) SetCurrentKey(key string) error { | |||
| 36 | return nil | 37 | return nil |
| 37 | } | 38 | } |
| 38 | 39 | ||
| 39 | func (ms *MemoryStateBackend) GetValueState(ctx context.Context, key string) (core.ValueState[any], error) { | 40 | func (ms *MemoryStateBackend) GetValueState(ctx context.Context, statename string) (core.ValueState[any], error) { |
| 40 | // Assign if state not existed | 41 | // Assign if state not existed |
| 41 | 42 | ||
| 42 | if _, ok := ms.states[key]; !ok { | 43 | if _, ok := ms.states[statename]; !ok { |
| 43 | ms.states[key] = make(map[int]map[string]map[any]any) | 44 | // create state name map if not existed |
| 45 | ms.states[statename] = make(map[int]map[string]map[any]any) | ||
| 44 | } | 46 | } |
| 45 | 47 | ||
| 46 | return &memoryStateValue{backend: ms, stateName: key}, nil | 48 | return &memoryStateValue{backend: ms, stateName: statename}, nil |
| 47 | } | 49 | } |
| 48 | 50 | ||
| 49 | func (ms *MemoryStateBackend) GetCurrentNamespace() any{ | 51 | func (ms *MemoryStateBackend) GetCurrentNamespace() any { |
| 50 | |||
| 51 | return ms.currentNamespace | 52 | return ms.currentNamespace |
| 52 | } | 53 | } |
| 53 | 54 | ||
diff --git a/state/memory_state.go b/state/memory_state.go index 95c7318..97ecad1 100644 --- a/state/memory_state.go +++ b/state/memory_state.go | |||
| @@ -1,7 +1,7 @@ | |||
| 1 | package state | 1 | package state |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "context" | 4 | "context" |
| 5 | ) | 5 | ) |
| 6 | 6 | ||
| 7 | type memoryStateValue struct { | 7 | type memoryStateValue struct { |
| @@ -13,7 +13,7 @@ func (m *memoryStateValue) Value(ctx context.Context) (any, error) { | |||
| 13 | kg := m.backend.currentGroupKey | 13 | kg := m.backend.currentGroupKey |
| 14 | key := m.backend.currentKey | 14 | key := m.backend.currentKey |
| 15 | ns := m.backend.currentNamespace | 15 | ns := m.backend.currentNamespace |
| 16 | if kgMap, exist := m.backend.states[m.stateName][kg][key]; exist { | 16 | if kgMap, ok := m.backend.states[m.stateName][kg][key]; ok { |
| 17 | if v, hasKey := kgMap[ns]; hasKey { | 17 | if v, hasKey := kgMap[ns]; hasKey { |
| 18 | return v, nil | 18 | return v, nil |
| 19 | } | 19 | } |
| @@ -26,10 +26,15 @@ func (m *memoryStateValue) Update(ctx context.Context, newVal any) (any, error) | |||
| 26 | kg := m.backend.currentGroupKey | 26 | kg := m.backend.currentGroupKey |
| 27 | key := m.backend.currentKey | 27 | key := m.backend.currentKey |
| 28 | ns := m.backend.currentNamespace | 28 | ns := m.backend.currentNamespace |
| 29 | if _, exists := m.backend.states[m.stateName][kg][key]; !exists { | 29 | |
| 30 | if _, ok := m.backend.states[m.stateName][kg]; !ok { | ||
| 30 | m.backend.states[m.stateName][kg] = make(map[string]map[any]any) | 31 | m.backend.states[m.stateName][kg] = make(map[string]map[any]any) |
| 31 | } | 32 | } |
| 32 | 33 | ||
| 34 | if _, ok := m.backend.states[m.stateName][kg][key]; !ok { | ||
| 35 | m.backend.states[m.stateName][kg][key] = make(map[any]any) | ||
| 36 | } | ||
| 37 | |||
| 33 | m.backend.states[m.stateName][kg][key][ns] = newVal | 38 | m.backend.states[m.stateName][kg][key][ns] = newVal |
| 34 | return newVal, nil | 39 | return newVal, nil |
| 35 | } | 40 | } |
| @@ -37,8 +42,11 @@ func (m *memoryStateValue) Update(ctx context.Context, newVal any) (any, error) | |||
| 37 | func (m *memoryStateValue) Clear(ctx context.Context) error { | 42 | func (m *memoryStateValue) Clear(ctx context.Context) error { |
| 38 | kg := m.backend.currentGroupKey | 43 | kg := m.backend.currentGroupKey |
| 39 | key := m.backend.currentKey | 44 | key := m.backend.currentKey |
| 40 | if kgMap, exists := m.backend.states[m.stateName][kg]; exists { | 45 | ns := m.backend.currentNamespace |
| 41 | delete(kgMap, key) | 46 | if kgMap, ok := m.backend.states[m.stateName][kg]; ok { |
| 47 | if nsMap, ok := kgMap[key]; ok { | ||
| 48 | delete(nsMap, ns) | ||
| 49 | } | ||
| 42 | } | 50 | } |
| 43 | return nil | 51 | return nil |
| 44 | } | 52 | } |
