Skip to content

Commit 88fd5da

Browse files
authored
fix(topo): detach subtopo from closed rule (#3010)
Signed-off-by: Jiyong Huang <[email protected]>
1 parent 0237f0f commit 88fd5da

File tree

6 files changed

+92
-15
lines changed

6 files changed

+92
-15
lines changed

internal/topo/node/contract.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323

2424
type Emitter interface {
2525
AddOutput(chan<- interface{}, string) error
26+
RemoveOutput(string) error
2627
}
2728

2829
type Collector interface {
@@ -83,7 +84,7 @@ type MergeableTopo interface {
8384
// SubMetrics return the metrics of the sub nodes
8485
SubMetrics() ([]string, []any)
8586
// Close notifies subtopo to deref
86-
Close(ctx api.StreamContext, ruleId string)
87+
Close(ctx api.StreamContext, ruleId string, runId int)
8788
}
8889

8990
type SchemaNode interface {

internal/topo/node/node.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package node
1717
import (
1818
"errors"
1919
"fmt"
20+
"strings"
2021
"sync"
2122

2223
"github.com/lf-edge/ekuiper/contract/v2/api"
@@ -56,13 +57,28 @@ func newDefaultNode(name string, options *def.RuleOption) *defaultNode {
5657
}
5758
}
5859

59-
func (o *defaultNode) AddOutput(output chan<- interface{}, name string) error {
60+
func (o *defaultNode) AddOutput(output chan<- any, name string) error {
6061
o.outputMu.Lock()
6162
defer o.outputMu.Unlock()
6263
o.outputs[name] = output
6364
return nil
6465
}
6566

67+
func (o *defaultNode) RemoveOutput(name string) error {
68+
o.outputMu.Lock()
69+
defer o.outputMu.Unlock()
70+
namePre := name + "_"
71+
for n := range o.outputs {
72+
if strings.HasPrefix(n, namePre) {
73+
delete(o.outputs, n)
74+
if o.ctx != nil {
75+
o.ctx.GetLogger().Infof("Remove output %s from %s", n, o.name)
76+
}
77+
}
78+
}
79+
return nil
80+
}
81+
6682
func (o *defaultNode) GetName() string {
6783
return o.name
6884
}
@@ -84,11 +100,11 @@ func (o *defaultNode) RemoveMetrics(ruleId string) {
84100
}
85101
}
86102

87-
func (o *defaultNode) Broadcast(val interface{}) {
103+
func (o *defaultNode) Broadcast(val any) {
88104
o.BroadcastCustomized(val, o.doBroadcast)
89105
}
90106

91-
func (o *defaultNode) BroadcastCustomized(val interface{}, broadcastFunc func(val any)) {
107+
func (o *defaultNode) BroadcastCustomized(val any, broadcastFunc func(val any)) {
92108
if _, ok := val.(error); ok && !o.sendError {
93109
return
94110
}
@@ -104,7 +120,7 @@ func (o *defaultNode) BroadcastCustomized(val interface{}, broadcastFunc func(va
104120
return
105121
}
106122

107-
func (o *defaultNode) doBroadcast(val interface{}) {
123+
func (o *defaultNode) doBroadcast(val any) {
108124
o.outputMu.RLock()
109125
defer o.outputMu.RUnlock()
110126
l := len(o.outputs)
@@ -151,7 +167,7 @@ func newDefaultSinkNode(name string, options *def.RuleOption) *defaultSinkNode {
151167
}
152168
}
153169

154-
func (o *defaultSinkNode) GetInput() (chan<- interface{}, string) {
170+
func (o *defaultSinkNode) GetInput() (chan<- any, string) {
155171
return o.input, o.name
156172
}
157173

@@ -236,7 +252,7 @@ func (o *defaultSinkNode) handleEof(ctx api.StreamContext, d xsql.EOFTuple) {
236252
}
237253
}
238254

239-
func SourcePing(sourceType string, config map[string]interface{}) error {
255+
func SourcePing(sourceType string, config map[string]any) error {
240256
source, err := io.Source(sourceType)
241257
if err != nil {
242258
return err

internal/topo/node/node_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2024 EMQ Technologies Co., Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package node
16+
17+
import (
18+
"testing"
19+
20+
"github.com/stretchr/testify/assert"
21+
22+
"github.com/lf-edge/ekuiper/v2/internal/pkg/def"
23+
)
24+
25+
func TestOutputs(t *testing.T) {
26+
n := newDefaultNode("test", &def.RuleOption{})
27+
err := n.AddOutput(make(chan<- any), "rule.1_test")
28+
assert.NoError(t, err)
29+
err = n.AddOutput(make(chan<- any), "rule.2_test")
30+
assert.NoError(t, err)
31+
err = n.RemoveOutput("rule.1")
32+
assert.NoError(t, err)
33+
err = n.RemoveOutput("rule.4")
34+
assert.NoError(t, err)
35+
assert.Equal(t, 1, len(n.outputs))
36+
}

internal/topo/subtopo.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ func (s *SrcSubTopo) AddOutput(output chan<- interface{}, name string) error {
6565
return s.tail.AddOutput(output, name)
6666
}
6767

68+
func (s *SrcSubTopo) RemoveOutput(name string) error {
69+
return s.tail.RemoveOutput(name)
70+
}
71+
6872
func (s *SrcSubTopo) Open(ctx api.StreamContext, parentErrCh chan<- error) {
6973
// Update the ref count
7074
if _, loaded := s.refRules.LoadOrStore(ctx.GetRuleId(), parentErrCh); !loaded {
@@ -171,7 +175,7 @@ func (s *SrcSubTopo) StoreSchema(ruleID, dataSource string, schema map[string]*a
171175
}
172176
}
173177

174-
func (s *SrcSubTopo) Close(ctx api.StreamContext, ruleId string) {
178+
func (s *SrcSubTopo) Close(ctx api.StreamContext, ruleId string, runId int) {
175179
if _, ok := s.refRules.LoadAndDelete(ruleId); ok {
176180
s.refCount.Add(-1)
177181
if s.refCount.Load() == 0 {
@@ -186,6 +190,7 @@ func (s *SrcSubTopo) Close(ctx api.StreamContext, ruleId string) {
186190
}
187191
}
188192
}
193+
_ = s.RemoveOutput(fmt.Sprintf("%s.%d", ruleId, runId))
189194
}
190195

191196
// RemoveMetrics is called when the rule is deleted

internal/topo/subtopo_test.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,10 @@ func TestSubtopoLC(t *testing.T) {
108108
assert.Equal(t, []checkpoint.StreamTask{srcNode}, sources)
109109
assert.Equal(t, []checkpoint.NonSourceTask{opNode}, ops)
110110
// Stop
111-
subTopo.Close(ctx1, "rule1")
111+
subTopo.Close(ctx1, "rule1", 1)
112112
assert.Equal(t, int32(1), subTopo.refCount.Load())
113113
assert.Equal(t, 1, mlen(&subTopoPool))
114-
subTopo2.Close(ctx2, "rule2")
114+
subTopo2.Close(ctx2, "rule2", 2)
115115
assert.Equal(t, int32(0), subTopo.refCount.Load())
116116
assert.Equal(t, 0, mlen(&subTopoPool))
117117
assert.Equal(t, 2, len(subTopo.schemaReg))
@@ -138,7 +138,7 @@ func TestSubtopoRunError(t *testing.T) {
138138
subTopo.Open(ctx1, make(chan error))
139139
assert.Equal(t, int32(1), subTopo.refCount.Load())
140140
assert.Equal(t, true, subTopo.opened.Load())
141-
subTopo.Close(ctx1, "rule1")
141+
subTopo.Close(ctx1, "rule1", 1)
142142
assert.Equal(t, int32(0), subTopo.refCount.Load())
143143
assert.Equal(t, 0, mlen(&subTopoPool))
144144
time.Sleep(10 * time.Millisecond)
@@ -156,14 +156,14 @@ func TestSubtopoRunError(t *testing.T) {
156156
select {
157157
case err := <-errCh1:
158158
assert.Equal(t, assert.AnError, err)
159-
subTopo.Close(ctx1, "rule1")
159+
subTopo.Close(ctx1, "rule1", 1)
160160
case <-time.After(1 * time.Second):
161161
assert.Fail(t, "Should receive error")
162162
}
163163
select {
164164
case err := <-errCh2:
165165
assert.Equal(t, assert.AnError, err)
166-
subTopo2.Close(ctx2, "rule2")
166+
subTopo2.Close(ctx2, "rule2", 2)
167167
case <-time.After(1 * time.Second):
168168
assert.Fail(t, "Should receive error")
169169
}
@@ -239,6 +239,11 @@ func (m *mockSrc) AddOutput(c chan<- interface{}, s string) error {
239239
return nil
240240
}
241241

242+
func (m *mockSrc) RemoveOutput(s string) error {
243+
m.outputs = m.outputs[1:]
244+
return nil
245+
}
246+
242247
func (m *mockSrc) Open(ctx api.StreamContext, errCh chan<- error) {
243248
if m.runCount%3 != 0 {
244249
fmt.Printf("sent error for %d \n", m.runCount)
@@ -273,6 +278,13 @@ type mockOp struct {
273278
schemaCount int
274279
}
275280

281+
func (m *mockOp) RemoveOutput(s string) error {
282+
if len(m.outputs) > 0 {
283+
m.outputs = m.outputs[1:]
284+
}
285+
return nil
286+
}
287+
276288
func (m *mockOp) AddOutput(c chan<- interface{}, s string) error {
277289
m.outputs = append(m.outputs, c)
278290
return nil

internal/topo/topo.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ import (
3939
"github.com/lf-edge/ekuiper/v2/pkg/timex"
4040
)
4141

42+
var uid atomic.Uint32
43+
44+
// Topo is the runtime DAG for a rule
45+
// It only run once. If the rule restarts, another topo is created.
4246
type Topo struct {
4347
streams []string
4448
sources []node.DataSourceNode
@@ -48,6 +52,7 @@ type Topo struct {
4852
drain chan error
4953
ops []node.OperatorNode
5054
name string
55+
runId int
5156
options *def.RuleOption
5257
store api.Store
5358
coordinator *checkpoint.Coordinator
@@ -59,8 +64,10 @@ type Topo struct {
5964
}
6065

6166
func NewWithNameAndOptions(name string, options *def.RuleOption) (*Topo, error) {
67+
id := uid.Add(1)
6268
tp := &Topo{
6369
name: name,
70+
runId: int(id),
6471
options: options,
6572
topo: &def.PrintableTopo{
6673
Sources: make([]string, 0),
@@ -115,7 +122,7 @@ func (s *Topo) Cancel() {
115122
s.coordinator = nil
116123
for _, src := range s.sources {
117124
if rt, ok := src.(node.MergeableTopo); ok {
118-
rt.Close(s.ctx, s.name)
125+
rt.Close(s.ctx, s.name, s.runId)
119126
}
120127
}
121128
}
@@ -158,7 +165,7 @@ func (s *Topo) AddOperator(inputs []node.Emitter, operator node.OperatorNode) *T
158165
ch, opName := operator.GetInput()
159166
for _, input := range inputs {
160167
// add rule id to make operator name unique
161-
_ = input.AddOutput(ch, fmt.Sprintf("%s_%s", s.name, opName))
168+
_ = input.AddOutput(ch, fmt.Sprintf("%s.%d_%s", s.name, s.runId, opName))
162169
operator.AddInputCount()
163170
switch rt := input.(type) {
164171
case node.MergeableTopo:

0 commit comments

Comments
 (0)