github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/pkg/pipeline/pipeline_test.go (about) 1 // Copyright 2020 PingCAP, Inc. 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 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package pipeline 15 16 import ( 17 stdCtx "context" 18 "fmt" 19 "testing" 20 "time" 21 22 "github.com/pingcap/check" 23 "github.com/pingcap/errors" 24 "github.com/pingcap/log" 25 "github.com/pingcap/ticdc/cdc/model" 26 "github.com/pingcap/ticdc/pkg/context" 27 cerror "github.com/pingcap/ticdc/pkg/errors" 28 "github.com/pingcap/ticdc/pkg/util/testleak" 29 "go.uber.org/zap" 30 ) 31 32 func TestSuite(t *testing.T) { check.TestingT(t) } 33 34 type pipelineSuite struct{} 35 36 var _ = check.Suite(&pipelineSuite{}) 37 38 type echoNode struct { 39 } 40 41 func (e echoNode) Init(ctx NodeContext) error { 42 ctx.SendToNextNode(PolymorphicEventMessage(&model.PolymorphicEvent{ 43 Row: &model.RowChangedEvent{ 44 Table: &model.TableName{ 45 Schema: "init function is called in echo node", 46 }, 47 }, 48 })) 49 return nil 50 } 51 52 func (e echoNode) Receive(ctx NodeContext) error { 53 msg := ctx.Message() 54 log.Info("Receive message in echo node", zap.Any("msg", msg)) 55 ctx.SendToNextNode(msg) 56 ctx.SendToNextNode(PolymorphicEventMessage(&model.PolymorphicEvent{ 57 Row: &model.RowChangedEvent{ 58 Table: &model.TableName{ 59 Schema: "ECHO: " + msg.PolymorphicEvent.Row.Table.Schema, 60 Table: "ECHO: " + msg.PolymorphicEvent.Row.Table.Table, 61 }, 62 }, 63 })) 64 return nil 65 } 66 67 func (e echoNode) Destroy(ctx NodeContext) error { 68 ctx.SendToNextNode(PolymorphicEventMessage(&model.PolymorphicEvent{ 69 Row: &model.RowChangedEvent{ 70 Table: &model.TableName{ 71 Schema: "destory function is called in echo node", 72 }, 73 }, 74 })) 75 return nil 76 } 77 78 type checkNode struct { 79 c *check.C 80 expected []Message 81 index int 82 } 83 84 func (n *checkNode) Init(ctx NodeContext) error { 85 // do nothing 86 return nil 87 } 88 89 func (n *checkNode) Receive(ctx NodeContext) error { 90 msg := ctx.Message() 91 92 log.Info("Receive message in check node", zap.Any("msg", msg)) 93 n.c.Assert(msg, check.DeepEquals, n.expected[n.index], check.Commentf("index: %d", n.index)) 94 n.index++ 95 return nil 96 } 97 98 func (n *checkNode) Destroy(ctx NodeContext) error { 99 n.c.Assert(n.index, check.Equals, len(n.expected)) 100 return nil 101 } 102 103 func (s *pipelineSuite) TestPipelineUsage(c *check.C) { 104 defer testleak.AfterTest(c)() 105 ctx := context.NewContext(stdCtx.Background(), &context.GlobalVars{}) 106 ctx, cancel := context.WithCancel(ctx) 107 ctx = context.WithErrorHandler(ctx, func(err error) error { 108 c.Fatal(err) 109 return err 110 }) 111 runnersSize, outputChannelSize := 2, 64 112 p := NewPipeline(ctx, -1, runnersSize, outputChannelSize) 113 p.AppendNode(ctx, "echo node", echoNode{}) 114 p.AppendNode(ctx, "check node", &checkNode{ 115 c: c, 116 expected: []Message{ 117 PolymorphicEventMessage(&model.PolymorphicEvent{ 118 Row: &model.RowChangedEvent{ 119 Table: &model.TableName{ 120 Schema: "init function is called in echo node", 121 }, 122 }, 123 }), 124 PolymorphicEventMessage(&model.PolymorphicEvent{ 125 Row: &model.RowChangedEvent{ 126 Table: &model.TableName{ 127 Schema: "I am built by test function", 128 Table: "AA1", 129 }, 130 }, 131 }), 132 PolymorphicEventMessage(&model.PolymorphicEvent{ 133 Row: &model.RowChangedEvent{ 134 Table: &model.TableName{ 135 Schema: "ECHO: I am built by test function", 136 Table: "ECHO: AA1", 137 }, 138 }, 139 }), 140 PolymorphicEventMessage(&model.PolymorphicEvent{ 141 Row: &model.RowChangedEvent{ 142 Table: &model.TableName{ 143 Schema: "I am built by test function", 144 Table: "BB2", 145 }, 146 }, 147 }), 148 PolymorphicEventMessage(&model.PolymorphicEvent{ 149 Row: &model.RowChangedEvent{ 150 Table: &model.TableName{ 151 Schema: "ECHO: I am built by test function", 152 Table: "ECHO: BB2", 153 }, 154 }, 155 }), 156 PolymorphicEventMessage(&model.PolymorphicEvent{ 157 Row: &model.RowChangedEvent{ 158 Table: &model.TableName{ 159 Schema: "destory function is called in echo node", 160 }, 161 }, 162 }), 163 }, 164 }) 165 166 err := p.SendToFirstNode(PolymorphicEventMessage(&model.PolymorphicEvent{ 167 Row: &model.RowChangedEvent{ 168 Table: &model.TableName{ 169 Schema: "I am built by test function", 170 Table: "AA1", 171 }, 172 }, 173 })) 174 c.Assert(err, check.IsNil) 175 err = p.SendToFirstNode(PolymorphicEventMessage(&model.PolymorphicEvent{ 176 Row: &model.RowChangedEvent{ 177 Table: &model.TableName{ 178 Schema: "I am built by test function", 179 Table: "BB2", 180 }, 181 }, 182 })) 183 c.Assert(err, check.IsNil) 184 cancel() 185 p.Wait() 186 } 187 188 type errorNode struct { 189 c *check.C 190 index int 191 } 192 193 func (n *errorNode) Init(ctx NodeContext) error { 194 // do nothing 195 return nil 196 } 197 198 func (n *errorNode) Receive(ctx NodeContext) error { 199 msg := ctx.Message() 200 log.Info("Receive message in error node", zap.Any("msg", msg)) 201 n.index++ 202 if n.index >= 3 { 203 return errors.Errorf("error node throw an error, index: %d", n.index) 204 } 205 ctx.SendToNextNode(msg) 206 return nil 207 } 208 209 func (n *errorNode) Destroy(ctx NodeContext) error { 210 n.c.Assert(n.index, check.Equals, 3) 211 return nil 212 } 213 214 func (s *pipelineSuite) TestPipelineError(c *check.C) { 215 defer testleak.AfterTest(c)() 216 ctx := context.NewContext(stdCtx.Background(), &context.GlobalVars{}) 217 ctx, cancel := context.WithCancel(ctx) 218 defer cancel() 219 ctx = context.WithErrorHandler(ctx, func(err error) error { 220 c.Assert(err.Error(), check.Equals, "error node throw an error, index: 3") 221 return nil 222 }) 223 runnersSize, outputChannelSize := 3, 64 224 p := NewPipeline(ctx, -1, runnersSize, outputChannelSize) 225 p.AppendNode(ctx, "echo node", echoNode{}) 226 p.AppendNode(ctx, "error node", &errorNode{c: c}) 227 p.AppendNode(ctx, "check node", &checkNode{ 228 c: c, 229 expected: []Message{ 230 PolymorphicEventMessage(&model.PolymorphicEvent{ 231 Row: &model.RowChangedEvent{ 232 Table: &model.TableName{ 233 Schema: "init function is called in echo node", 234 }, 235 }, 236 }), 237 PolymorphicEventMessage(&model.PolymorphicEvent{ 238 Row: &model.RowChangedEvent{ 239 Table: &model.TableName{ 240 Schema: "I am built by test function", 241 Table: "CC1", 242 }, 243 }, 244 }), 245 }, 246 }) 247 248 err := p.SendToFirstNode(PolymorphicEventMessage(&model.PolymorphicEvent{ 249 Row: &model.RowChangedEvent{ 250 Table: &model.TableName{ 251 Schema: "I am built by test function", 252 Table: "CC1", 253 }, 254 }, 255 })) 256 c.Assert(err, check.IsNil) 257 // this line may be return an error because the pipeline maybe closed before this line was executed 258 //nolint:errcheck 259 p.SendToFirstNode(PolymorphicEventMessage(&model.PolymorphicEvent{ 260 Row: &model.RowChangedEvent{ 261 Table: &model.TableName{ 262 Schema: "I am built by test function", 263 Table: "DD2", 264 }, 265 }, 266 })) 267 p.Wait() 268 } 269 270 type throwNode struct { 271 c *check.C 272 index int 273 } 274 275 func (n *throwNode) Init(ctx NodeContext) error { 276 // do nothing 277 return nil 278 } 279 280 func (n *throwNode) Receive(ctx NodeContext) error { 281 msg := ctx.Message() 282 log.Info("Receive message in error node", zap.Any("msg", msg)) 283 n.index++ 284 if n.index >= 3 { 285 ctx.Throw(errors.Errorf("error node throw an error, index: %d", n.index)) 286 return nil 287 } 288 ctx.SendToNextNode(msg) 289 return nil 290 } 291 292 func (n *throwNode) Destroy(ctx NodeContext) error { 293 n.c.Assert(map[int]bool{4: true, 6: true}, check.HasKey, n.index) 294 return nil 295 } 296 297 func (s *pipelineSuite) TestPipelineThrow(c *check.C) { 298 defer testleak.AfterTest(c)() 299 ctx := context.NewContext(stdCtx.Background(), &context.GlobalVars{}) 300 ctx, cancel := context.WithCancel(ctx) 301 defer cancel() 302 var errs []error 303 ctx = context.WithErrorHandler(ctx, func(err error) error { 304 errs = append(errs, err) 305 return nil 306 }) 307 runnersSize, outputChannelSize := 2, 64 308 p := NewPipeline(ctx, -1, runnersSize, outputChannelSize) 309 p.AppendNode(ctx, "echo node", echoNode{}) 310 p.AppendNode(ctx, "error node", &throwNode{c: c}) 311 err := p.SendToFirstNode(PolymorphicEventMessage(&model.PolymorphicEvent{ 312 Row: &model.RowChangedEvent{ 313 Table: &model.TableName{ 314 Schema: "I am built by test function", 315 Table: "CC1", 316 }, 317 }, 318 })) 319 c.Assert(err, check.IsNil) 320 // whether err is nil is not determined 321 // If add some delay here, such as sleep 50ms, there will be more probability 322 // that the second message is not sent. 323 // time.Sleep(time.Millisecond * 50) 324 err = p.SendToFirstNode(PolymorphicEventMessage(&model.PolymorphicEvent{ 325 Row: &model.RowChangedEvent{ 326 Table: &model.TableName{ 327 Schema: "I am built by test function", 328 Table: "DD2", 329 }, 330 }, 331 })) 332 if err != nil { 333 // pipeline closed before the second message was sent 334 c.Assert(cerror.ErrSendToClosedPipeline.Equal(err), check.IsTrue) 335 p.Wait() 336 c.Assert(len(errs), check.Equals, 2) 337 c.Assert(errs[0].Error(), check.Equals, "error node throw an error, index: 3") 338 c.Assert(errs[1].Error(), check.Equals, "error node throw an error, index: 4") 339 } else { 340 // the second message was sent before pipeline closed 341 p.Wait() 342 c.Assert(len(errs), check.Equals, 4) 343 c.Assert(errs[0].Error(), check.Equals, "error node throw an error, index: 3") 344 c.Assert(errs[1].Error(), check.Equals, "error node throw an error, index: 4") 345 c.Assert(errs[2].Error(), check.Equals, "error node throw an error, index: 5") 346 c.Assert(errs[3].Error(), check.Equals, "error node throw an error, index: 6") 347 } 348 } 349 350 func (s *pipelineSuite) TestPipelineAppendNode(c *check.C) { 351 defer testleak.AfterTest(c)() 352 ctx := context.NewContext(stdCtx.Background(), &context.GlobalVars{}) 353 ctx, cancel := context.WithCancel(ctx) 354 ctx = context.WithErrorHandler(ctx, func(err error) error { 355 c.Fatal(err) 356 return err 357 }) 358 runnersSize, outputChannelSize := 2, 64 359 p := NewPipeline(ctx, -1, runnersSize, outputChannelSize) 360 err := p.SendToFirstNode(PolymorphicEventMessage(&model.PolymorphicEvent{ 361 Row: &model.RowChangedEvent{ 362 Table: &model.TableName{ 363 Schema: "I am built by test function", 364 Table: "CC1", 365 }, 366 }, 367 })) 368 c.Assert(err, check.IsNil) 369 err = p.SendToFirstNode(PolymorphicEventMessage(&model.PolymorphicEvent{ 370 Row: &model.RowChangedEvent{ 371 Table: &model.TableName{ 372 Schema: "I am built by test function", 373 Table: "DD2", 374 }, 375 }, 376 })) 377 c.Assert(err, check.IsNil) 378 p.AppendNode(ctx, "echo node", echoNode{}) 379 // wait the echo node sent all messages to next node 380 time.Sleep(1 * time.Second) 381 382 p.AppendNode(ctx, "check node", &checkNode{ 383 c: c, 384 expected: []Message{ 385 PolymorphicEventMessage(&model.PolymorphicEvent{ 386 Row: &model.RowChangedEvent{ 387 Table: &model.TableName{ 388 Schema: "init function is called in echo node", 389 }, 390 }, 391 }), 392 PolymorphicEventMessage(&model.PolymorphicEvent{ 393 Row: &model.RowChangedEvent{ 394 Table: &model.TableName{ 395 Schema: "I am built by test function", 396 Table: "CC1", 397 }, 398 }, 399 }), 400 PolymorphicEventMessage(&model.PolymorphicEvent{ 401 Row: &model.RowChangedEvent{ 402 Table: &model.TableName{ 403 Schema: "ECHO: I am built by test function", 404 Table: "ECHO: CC1", 405 }, 406 }, 407 }), 408 PolymorphicEventMessage(&model.PolymorphicEvent{ 409 Row: &model.RowChangedEvent{ 410 Table: &model.TableName{ 411 Schema: "I am built by test function", 412 Table: "DD2", 413 }, 414 }, 415 }), 416 PolymorphicEventMessage(&model.PolymorphicEvent{ 417 Row: &model.RowChangedEvent{ 418 Table: &model.TableName{ 419 Schema: "ECHO: I am built by test function", 420 Table: "ECHO: DD2", 421 }, 422 }, 423 }), 424 PolymorphicEventMessage(&model.PolymorphicEvent{ 425 Row: &model.RowChangedEvent{ 426 Table: &model.TableName{ 427 Schema: "destory function is called in echo node", 428 }, 429 }, 430 }), 431 }, 432 }) 433 434 cancel() 435 p.Wait() 436 } 437 438 type panicNode struct { 439 } 440 441 func (e panicNode) Init(ctx NodeContext) error { 442 panic("panic in panicNode") 443 } 444 445 func (e panicNode) Receive(ctx NodeContext) error { 446 return nil 447 } 448 449 func (e panicNode) Destroy(ctx NodeContext) error { 450 return nil 451 } 452 453 func (s *pipelineSuite) TestPipelinePanic(c *check.C) { 454 defer testleak.AfterTest(c)() 455 // why skip this test? 456 // this test is panic expected, but the panic is not happened at the main goroutine. 457 // so we can't recover the panic through the defer code block at the main goroutine. 458 // the c.ExpectFailure() is not warking cause the same reason. 459 c.Skip("this test should be panic") 460 defer func() { 461 panicInfo := recover().(string) 462 c.Assert(panicInfo, check.Equals, "panic in panicNode") 463 }() 464 ctx := context.NewContext(stdCtx.Background(), &context.GlobalVars{}) 465 ctx, cancel := context.WithCancel(ctx) 466 defer cancel() 467 ctx = context.WithErrorHandler(ctx, func(err error) error { 468 c.Fatal(err) 469 return err 470 }) 471 ctx = context.WithErrorHandler(ctx, func(err error) error { 472 return nil 473 }) 474 runnersSize, outputChannelSize := 1, 64 475 p := NewPipeline(ctx, -1, runnersSize, outputChannelSize) 476 p.AppendNode(ctx, "panic", panicNode{}) 477 p.Wait() 478 } 479 480 type forward struct { 481 ch chan Message 482 } 483 484 func (n *forward) Init(ctx NodeContext) error { 485 return nil 486 } 487 488 func (n *forward) Receive(ctx NodeContext) error { 489 m := ctx.Message() 490 if n.ch != nil { 491 n.ch <- m 492 } else { 493 ctx.SendToNextNode(m) 494 } 495 return nil 496 } 497 498 func (n *forward) Destroy(ctx NodeContext) error { 499 return nil 500 } 501 502 // Run the benchmark 503 // go test -benchmem -run='^$' -bench '^(BenchmarkPipeline)$' github.com/pingcap/ticdc/pkg/pipeline 504 func BenchmarkPipeline(b *testing.B) { 505 ctx := context.NewContext(stdCtx.Background(), &context.GlobalVars{}) 506 runnersSize, outputChannelSize := 2, 64 507 508 b.Run("BenchmarkPipeline", func(b *testing.B) { 509 for i := 1; i <= 8; i++ { 510 ctx, cancel := context.WithCancel(ctx) 511 ctx = context.WithErrorHandler(ctx, func(err error) error { 512 b.Fatal(err) 513 return err 514 }) 515 516 ch := make(chan Message) 517 p := NewPipeline(ctx, -1, runnersSize, outputChannelSize) 518 for j := 0; j < i; j++ { 519 if (j + 1) == i { 520 // The last node 521 p.AppendNode(ctx, "forward node", &forward{ch: ch}) 522 } else { 523 p.AppendNode(ctx, "forward node", &forward{}) 524 } 525 } 526 527 b.ResetTimer() 528 b.Run(fmt.Sprintf("%d node(s)", i), func(b *testing.B) { 529 for i := 0; i < b.N; i++ { 530 err := p.SendToFirstNode(BarrierMessage(1)) 531 if err != nil { 532 b.Fatal(err) 533 } 534 <-ch 535 } 536 }) 537 b.StopTimer() 538 cancel() 539 p.Wait() 540 } 541 }) 542 }