github.com/qiuhoude/go-web@v0.0.0-20220223060959-ab545e78f20d/prepare/23_proto_actor/cluster-broadcast/shared/protos_protoactor.go (about) 1 package shared 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "math" 8 "time" 9 10 "github.com/AsynkronIT/protoactor-go/actor" 11 "github.com/AsynkronIT/protoactor-go/cluster" 12 "github.com/AsynkronIT/protoactor-go/remote" 13 "github.com/gogo/protobuf/proto" 14 ) 15 16 var _ = proto.Marshal 17 var _ = fmt.Errorf 18 var _ = math.Inf 19 20 var rootContext = actor.EmptyRootContext 21 22 var xCalculatorFactory func() Calculator 23 24 // CalculatorFactory produces a Calculator 25 func CalculatorFactory(factory func() Calculator) { 26 xCalculatorFactory = factory 27 } 28 29 // GetCalculatorGrain instantiates a new CalculatorGrain with given ID 30 func GetCalculatorGrain(id string) *CalculatorGrain { 31 return &CalculatorGrain{ID: id} 32 } 33 34 // Calculator interfaces the services available to the Calculator 35 type Calculator interface { 36 Init(id string) 37 Terminate() 38 39 Add(*NumberRequest, cluster.GrainContext) (*CountResponse, error) 40 41 Subtract(*NumberRequest, cluster.GrainContext) (*CountResponse, error) 42 43 GetCurrent(*Noop, cluster.GrainContext) (*CountResponse, error) 44 } 45 46 // CalculatorGrain holds the base data for the CalculatorGrain 47 type CalculatorGrain struct { 48 ID string 49 } 50 51 // Add requests the execution on to the cluster using default options 52 func (g *CalculatorGrain) Add(r *NumberRequest) (*CountResponse, error) { 53 return g.AddWithOpts(r, cluster.DefaultGrainCallOptions()) 54 } 55 56 // AddWithOpts requests the execution on to the cluster 57 func (g *CalculatorGrain) AddWithOpts(r *NumberRequest, opts *cluster.GrainCallOptions) (*CountResponse, error) { 58 fun := func() (*CountResponse, error) { 59 pid, statusCode := cluster.Get(g.ID, "Calculator") 60 if statusCode != remote.ResponseStatusCodeOK { 61 return nil, fmt.Errorf("get PID failed with StatusCode: %v", statusCode) 62 } 63 bytes, err := proto.Marshal(r) 64 if err != nil { 65 return nil, err 66 } 67 request := &cluster.GrainRequest{MethodIndex: 0, MessageData: bytes} 68 response, err := rootContext.RequestFuture(pid, request, opts.Timeout).Result() 69 if err != nil { 70 return nil, err 71 } 72 switch msg := response.(type) { 73 case *cluster.GrainResponse: 74 result := &CountResponse{} 75 err = proto.Unmarshal(msg.MessageData, result) 76 if err != nil { 77 return nil, err 78 } 79 return result, nil 80 case *cluster.GrainErrorResponse: 81 return nil, errors.New(msg.Err) 82 default: 83 return nil, errors.New("unknown response") 84 } 85 } 86 87 var res *CountResponse 88 var err error 89 for i := 0; i < opts.RetryCount; i++ { 90 res, err = fun() 91 if err == nil || err.Error() != "future: timeout" { 92 return res, err 93 } else if opts.RetryAction != nil { 94 opts.RetryAction(i) 95 } 96 } 97 return nil, err 98 } 99 100 // AddChan allows to use a channel to execute the method using default options 101 func (g *CalculatorGrain) AddChan(r *NumberRequest) (<-chan *CountResponse, <-chan error) { 102 return g.AddChanWithOpts(r, cluster.DefaultGrainCallOptions()) 103 } 104 105 // AddChanWithOpts allows to use a channel to execute the method 106 func (g *CalculatorGrain) AddChanWithOpts(r *NumberRequest, opts *cluster.GrainCallOptions) (<-chan *CountResponse, <-chan error) { 107 c := make(chan *CountResponse) 108 e := make(chan error) 109 go func() { 110 res, err := g.AddWithOpts(r, opts) 111 if err != nil { 112 e <- err 113 } else { 114 c <- res 115 } 116 close(c) 117 close(e) 118 }() 119 return c, e 120 } 121 122 // Subtract requests the execution on to the cluster using default options 123 func (g *CalculatorGrain) Subtract(r *NumberRequest) (*CountResponse, error) { 124 return g.SubtractWithOpts(r, cluster.DefaultGrainCallOptions()) 125 } 126 127 // SubtractWithOpts requests the execution on to the cluster 128 func (g *CalculatorGrain) SubtractWithOpts(r *NumberRequest, opts *cluster.GrainCallOptions) (*CountResponse, error) { 129 fun := func() (*CountResponse, error) { 130 pid, statusCode := cluster.Get(g.ID, "Calculator") 131 if statusCode != remote.ResponseStatusCodeOK { 132 return nil, fmt.Errorf("get PID failed with StatusCode: %v", statusCode) 133 } 134 bytes, err := proto.Marshal(r) 135 if err != nil { 136 return nil, err 137 } 138 request := &cluster.GrainRequest{MethodIndex: 1, MessageData: bytes} 139 response, err := rootContext.RequestFuture(pid, request, opts.Timeout).Result() 140 if err != nil { 141 return nil, err 142 } 143 switch msg := response.(type) { 144 case *cluster.GrainResponse: 145 result := &CountResponse{} 146 err = proto.Unmarshal(msg.MessageData, result) 147 if err != nil { 148 return nil, err 149 } 150 return result, nil 151 case *cluster.GrainErrorResponse: 152 return nil, errors.New(msg.Err) 153 default: 154 return nil, errors.New("unknown response") 155 } 156 } 157 158 var res *CountResponse 159 var err error 160 for i := 0; i < opts.RetryCount; i++ { 161 res, err = fun() 162 if err == nil || err.Error() != "future: timeout" { 163 return res, err 164 } else if opts.RetryAction != nil { 165 opts.RetryAction(i) 166 } 167 } 168 return nil, err 169 } 170 171 // SubtractChan allows to use a channel to execute the method using default options 172 func (g *CalculatorGrain) SubtractChan(r *NumberRequest) (<-chan *CountResponse, <-chan error) { 173 return g.SubtractChanWithOpts(r, cluster.DefaultGrainCallOptions()) 174 } 175 176 // SubtractChanWithOpts allows to use a channel to execute the method 177 func (g *CalculatorGrain) SubtractChanWithOpts(r *NumberRequest, opts *cluster.GrainCallOptions) (<-chan *CountResponse, <-chan error) { 178 c := make(chan *CountResponse) 179 e := make(chan error) 180 go func() { 181 res, err := g.SubtractWithOpts(r, opts) 182 if err != nil { 183 e <- err 184 } else { 185 c <- res 186 } 187 close(c) 188 close(e) 189 }() 190 return c, e 191 } 192 193 // GetCurrent requests the execution on to the cluster using default options 194 func (g *CalculatorGrain) GetCurrent(r *Noop) (*CountResponse, error) { 195 return g.GetCurrentWithOpts(r, cluster.DefaultGrainCallOptions()) 196 } 197 198 // GetCurrentWithOpts requests the execution on to the cluster 199 func (g *CalculatorGrain) GetCurrentWithOpts(r *Noop, opts *cluster.GrainCallOptions) (*CountResponse, error) { 200 fun := func() (*CountResponse, error) { 201 pid, statusCode := cluster.Get(g.ID, "Calculator") 202 if statusCode != remote.ResponseStatusCodeOK { 203 return nil, fmt.Errorf("get PID failed with StatusCode: %v", statusCode) 204 } 205 bytes, err := proto.Marshal(r) 206 if err != nil { 207 return nil, err 208 } 209 request := &cluster.GrainRequest{MethodIndex: 2, MessageData: bytes} 210 response, err := rootContext.RequestFuture(pid, request, opts.Timeout).Result() 211 if err != nil { 212 return nil, err 213 } 214 switch msg := response.(type) { 215 case *cluster.GrainResponse: 216 result := &CountResponse{} 217 err = proto.Unmarshal(msg.MessageData, result) 218 if err != nil { 219 return nil, err 220 } 221 return result, nil 222 case *cluster.GrainErrorResponse: 223 return nil, errors.New(msg.Err) 224 default: 225 return nil, errors.New("unknown response") 226 } 227 } 228 229 var res *CountResponse 230 var err error 231 for i := 0; i < opts.RetryCount; i++ { 232 res, err = fun() 233 if err == nil || err.Error() != "future: timeout" { 234 return res, err 235 } else if opts.RetryAction != nil { 236 opts.RetryAction(i) 237 } 238 } 239 return nil, err 240 } 241 242 // GetCurrentChan allows to use a channel to execute the method using default options 243 func (g *CalculatorGrain) GetCurrentChan(r *Noop) (<-chan *CountResponse, <-chan error) { 244 return g.GetCurrentChanWithOpts(r, cluster.DefaultGrainCallOptions()) 245 } 246 247 // GetCurrentChanWithOpts allows to use a channel to execute the method 248 func (g *CalculatorGrain) GetCurrentChanWithOpts(r *Noop, opts *cluster.GrainCallOptions) (<-chan *CountResponse, <-chan error) { 249 c := make(chan *CountResponse) 250 e := make(chan error) 251 go func() { 252 res, err := g.GetCurrentWithOpts(r, opts) 253 if err != nil { 254 e <- err 255 } else { 256 c <- res 257 } 258 close(c) 259 close(e) 260 }() 261 return c, e 262 } 263 264 // CalculatorActor represents the actor structure 265 type CalculatorActor struct { 266 inner Calculator 267 Timeout *time.Duration 268 } 269 270 // Receive ensures the lifecycle of the actor for the received message 271 func (a *CalculatorActor) Receive(ctx actor.Context) { 272 switch msg := ctx.Message().(type) { 273 case *actor.Started: 274 a.inner = xCalculatorFactory() 275 id := ctx.Self().Id 276 a.inner.Init(id[7:]) // skip "remote$" 277 if a.Timeout != nil { 278 ctx.SetReceiveTimeout(*a.Timeout) 279 } 280 case *actor.ReceiveTimeout: 281 a.inner.Terminate() 282 rootContext.PoisonFuture(ctx.Self()).Wait() 283 284 case actor.AutoReceiveMessage: // pass 285 case actor.SystemMessage: // pass 286 287 case *cluster.GrainRequest: 288 switch msg.MethodIndex { 289 290 case 0: 291 req := &NumberRequest{} 292 err := proto.Unmarshal(msg.MessageData, req) 293 if err != nil { 294 log.Fatalf("[GRAIN] proto.Unmarshal failed %v", err) 295 } 296 r0, err := a.inner.Add(req, ctx) 297 if err == nil { 298 bytes, errMarshal := proto.Marshal(r0) 299 if errMarshal != nil { 300 log.Fatalf("[GRAIN] proto.Marshal failed %v", errMarshal) 301 } 302 resp := &cluster.GrainResponse{MessageData: bytes} 303 ctx.Respond(resp) 304 } else { 305 resp := &cluster.GrainErrorResponse{Err: err.Error()} 306 ctx.Respond(resp) 307 } 308 309 case 1: 310 req := &NumberRequest{} 311 err := proto.Unmarshal(msg.MessageData, req) 312 if err != nil { 313 log.Fatalf("[GRAIN] proto.Unmarshal failed %v", err) 314 } 315 r0, err := a.inner.Subtract(req, ctx) 316 if err == nil { 317 bytes, errMarshal := proto.Marshal(r0) 318 if errMarshal != nil { 319 log.Fatalf("[GRAIN] proto.Marshal failed %v", errMarshal) 320 } 321 resp := &cluster.GrainResponse{MessageData: bytes} 322 ctx.Respond(resp) 323 } else { 324 resp := &cluster.GrainErrorResponse{Err: err.Error()} 325 ctx.Respond(resp) 326 } 327 328 case 2: 329 req := &Noop{} 330 err := proto.Unmarshal(msg.MessageData, req) 331 if err != nil { 332 log.Fatalf("[GRAIN] proto.Unmarshal failed %v", err) 333 } 334 r0, err := a.inner.GetCurrent(req, ctx) 335 if err == nil { 336 bytes, errMarshal := proto.Marshal(r0) 337 if errMarshal != nil { 338 log.Fatalf("[GRAIN] proto.Marshal failed %v", errMarshal) 339 } 340 resp := &cluster.GrainResponse{MessageData: bytes} 341 ctx.Respond(resp) 342 } else { 343 resp := &cluster.GrainErrorResponse{Err: err.Error()} 344 ctx.Respond(resp) 345 } 346 347 } 348 default: 349 log.Printf("Unknown message %v", msg) 350 } 351 } 352 353 var xTrackerFactory func() Tracker 354 355 // TrackerFactory produces a Tracker 356 func TrackerFactory(factory func() Tracker) { 357 xTrackerFactory = factory 358 } 359 360 // GetTrackerGrain instantiates a new TrackerGrain with given ID 361 func GetTrackerGrain(id string) *TrackerGrain { 362 return &TrackerGrain{ID: id} 363 } 364 365 // Tracker interfaces the services available to the Tracker 366 type Tracker interface { 367 Init(id string) 368 Terminate() 369 370 RegisterGrain(*RegisterMessage, cluster.GrainContext) (*Noop, error) 371 372 DeregisterGrain(*RegisterMessage, cluster.GrainContext) (*Noop, error) 373 374 BroadcastGetCounts(*Noop, cluster.GrainContext) (*TotalsResponse, error) 375 } 376 377 // TrackerGrain holds the base data for the TrackerGrain 378 type TrackerGrain struct { 379 ID string 380 } 381 382 // RegisterGrain requests the execution on to the cluster using default options 383 func (g *TrackerGrain) RegisterGrain(r *RegisterMessage) (*Noop, error) { 384 return g.RegisterGrainWithOpts(r, cluster.DefaultGrainCallOptions()) 385 } 386 387 // RegisterGrainWithOpts requests the execution on to the cluster 388 func (g *TrackerGrain) RegisterGrainWithOpts(r *RegisterMessage, opts *cluster.GrainCallOptions) (*Noop, error) { 389 fun := func() (*Noop, error) { 390 pid, statusCode := cluster.Get(g.ID, "Tracker") 391 if statusCode != remote.ResponseStatusCodeOK { 392 return nil, fmt.Errorf("get PID failed with StatusCode: %v", statusCode) 393 } 394 bytes, err := proto.Marshal(r) 395 if err != nil { 396 return nil, err 397 } 398 request := &cluster.GrainRequest{MethodIndex: 0, MessageData: bytes} 399 response, err := rootContext.RequestFuture(pid, request, opts.Timeout).Result() 400 if err != nil { 401 return nil, err 402 } 403 switch msg := response.(type) { 404 case *cluster.GrainResponse: 405 result := &Noop{} 406 err = proto.Unmarshal(msg.MessageData, result) 407 if err != nil { 408 return nil, err 409 } 410 return result, nil 411 case *cluster.GrainErrorResponse: 412 return nil, errors.New(msg.Err) 413 default: 414 return nil, errors.New("unknown response") 415 } 416 } 417 418 var res *Noop 419 var err error 420 for i := 0; i < opts.RetryCount; i++ { 421 res, err = fun() 422 if err == nil || err.Error() != "future: timeout" { 423 return res, err 424 } else if opts.RetryAction != nil { 425 opts.RetryAction(i) 426 } 427 } 428 return nil, err 429 } 430 431 // RegisterGrainChan allows to use a channel to execute the method using default options 432 func (g *TrackerGrain) RegisterGrainChan(r *RegisterMessage) (<-chan *Noop, <-chan error) { 433 return g.RegisterGrainChanWithOpts(r, cluster.DefaultGrainCallOptions()) 434 } 435 436 // RegisterGrainChanWithOpts allows to use a channel to execute the method 437 func (g *TrackerGrain) RegisterGrainChanWithOpts(r *RegisterMessage, opts *cluster.GrainCallOptions) (<-chan *Noop, <-chan error) { 438 c := make(chan *Noop) 439 e := make(chan error) 440 go func() { 441 res, err := g.RegisterGrainWithOpts(r, opts) 442 if err != nil { 443 e <- err 444 } else { 445 c <- res 446 } 447 close(c) 448 close(e) 449 }() 450 return c, e 451 } 452 453 // DeregisterGrain requests the execution on to the cluster using default options 454 func (g *TrackerGrain) DeregisterGrain(r *RegisterMessage) (*Noop, error) { 455 return g.DeregisterGrainWithOpts(r, cluster.DefaultGrainCallOptions()) 456 } 457 458 // DeregisterGrainWithOpts requests the execution on to the cluster 459 func (g *TrackerGrain) DeregisterGrainWithOpts(r *RegisterMessage, opts *cluster.GrainCallOptions) (*Noop, error) { 460 fun := func() (*Noop, error) { 461 pid, statusCode := cluster.Get(g.ID, "Tracker") 462 if statusCode != remote.ResponseStatusCodeOK { 463 return nil, fmt.Errorf("get PID failed with StatusCode: %v", statusCode) 464 } 465 bytes, err := proto.Marshal(r) 466 if err != nil { 467 return nil, err 468 } 469 request := &cluster.GrainRequest{MethodIndex: 1, MessageData: bytes} 470 response, err := rootContext.RequestFuture(pid, request, opts.Timeout).Result() 471 if err != nil { 472 return nil, err 473 } 474 switch msg := response.(type) { 475 case *cluster.GrainResponse: 476 result := &Noop{} 477 err = proto.Unmarshal(msg.MessageData, result) 478 if err != nil { 479 return nil, err 480 } 481 return result, nil 482 case *cluster.GrainErrorResponse: 483 return nil, errors.New(msg.Err) 484 default: 485 return nil, errors.New("unknown response") 486 } 487 } 488 489 var res *Noop 490 var err error 491 for i := 0; i < opts.RetryCount; i++ { 492 res, err = fun() 493 if err == nil || err.Error() != "future: timeout" { 494 return res, err 495 } else if opts.RetryAction != nil { 496 opts.RetryAction(i) 497 } 498 } 499 return nil, err 500 } 501 502 // DeregisterGrainChan allows to use a channel to execute the method using default options 503 func (g *TrackerGrain) DeregisterGrainChan(r *RegisterMessage) (<-chan *Noop, <-chan error) { 504 return g.DeregisterGrainChanWithOpts(r, cluster.DefaultGrainCallOptions()) 505 } 506 507 // DeregisterGrainChanWithOpts allows to use a channel to execute the method 508 func (g *TrackerGrain) DeregisterGrainChanWithOpts(r *RegisterMessage, opts *cluster.GrainCallOptions) (<-chan *Noop, <-chan error) { 509 c := make(chan *Noop) 510 e := make(chan error) 511 go func() { 512 res, err := g.DeregisterGrainWithOpts(r, opts) 513 if err != nil { 514 e <- err 515 } else { 516 c <- res 517 } 518 close(c) 519 close(e) 520 }() 521 return c, e 522 } 523 524 // BroadcastGetCounts requests the execution on to the cluster using default options 525 func (g *TrackerGrain) BroadcastGetCounts(r *Noop) (*TotalsResponse, error) { 526 return g.BroadcastGetCountsWithOpts(r, cluster.DefaultGrainCallOptions()) 527 } 528 529 // BroadcastGetCountsWithOpts requests the execution on to the cluster 530 func (g *TrackerGrain) BroadcastGetCountsWithOpts(r *Noop, opts *cluster.GrainCallOptions) (*TotalsResponse, error) { 531 fun := func() (*TotalsResponse, error) { 532 pid, statusCode := cluster.Get(g.ID, "Tracker") 533 if statusCode != remote.ResponseStatusCodeOK { 534 return nil, fmt.Errorf("get PID failed with StatusCode: %v", statusCode) 535 } 536 bytes, err := proto.Marshal(r) 537 if err != nil { 538 return nil, err 539 } 540 request := &cluster.GrainRequest{MethodIndex: 2, MessageData: bytes} 541 response, err := rootContext.RequestFuture(pid, request, opts.Timeout).Result() 542 if err != nil { 543 return nil, err 544 } 545 switch msg := response.(type) { 546 case *cluster.GrainResponse: 547 result := &TotalsResponse{} 548 err = proto.Unmarshal(msg.MessageData, result) 549 if err != nil { 550 return nil, err 551 } 552 return result, nil 553 case *cluster.GrainErrorResponse: 554 return nil, errors.New(msg.Err) 555 default: 556 return nil, errors.New("unknown response") 557 } 558 } 559 560 var res *TotalsResponse 561 var err error 562 for i := 0; i < opts.RetryCount; i++ { 563 res, err = fun() 564 if err == nil || err.Error() != "future: timeout" { 565 return res, err 566 } else if opts.RetryAction != nil { 567 opts.RetryAction(i) 568 } 569 } 570 return nil, err 571 } 572 573 // BroadcastGetCountsChan allows to use a channel to execute the method using default options 574 func (g *TrackerGrain) BroadcastGetCountsChan(r *Noop) (<-chan *TotalsResponse, <-chan error) { 575 return g.BroadcastGetCountsChanWithOpts(r, cluster.DefaultGrainCallOptions()) 576 } 577 578 // BroadcastGetCountsChanWithOpts allows to use a channel to execute the method 579 func (g *TrackerGrain) BroadcastGetCountsChanWithOpts(r *Noop, opts *cluster.GrainCallOptions) (<-chan *TotalsResponse, <-chan error) { 580 c := make(chan *TotalsResponse) 581 e := make(chan error) 582 go func() { 583 res, err := g.BroadcastGetCountsWithOpts(r, opts) 584 if err != nil { 585 e <- err 586 } else { 587 c <- res 588 } 589 close(c) 590 close(e) 591 }() 592 return c, e 593 } 594 595 // TrackerActor represents the actor structure 596 type TrackerActor struct { 597 inner Tracker 598 Timeout *time.Duration 599 } 600 601 // Receive ensures the lifecycle of the actor for the received message 602 func (a *TrackerActor) Receive(ctx actor.Context) { 603 switch msg := ctx.Message().(type) { 604 case *actor.Started: 605 a.inner = xTrackerFactory() 606 id := ctx.Self().Id 607 a.inner.Init(id[7:]) // skip "remote$" 608 if a.Timeout != nil { 609 ctx.SetReceiveTimeout(*a.Timeout) 610 } 611 case *actor.ReceiveTimeout: 612 a.inner.Terminate() 613 rootContext.PoisonFuture(ctx.Self()).Wait() 614 615 case actor.AutoReceiveMessage: // pass 616 case actor.SystemMessage: // pass 617 618 case *cluster.GrainRequest: 619 switch msg.MethodIndex { 620 621 case 0: 622 req := &RegisterMessage{} 623 err := proto.Unmarshal(msg.MessageData, req) 624 if err != nil { 625 log.Fatalf("[GRAIN] proto.Unmarshal failed %v", err) 626 } 627 r0, err := a.inner.RegisterGrain(req, ctx) 628 if err == nil { 629 bytes, errMarshal := proto.Marshal(r0) 630 if errMarshal != nil { 631 log.Fatalf("[GRAIN] proto.Marshal failed %v", errMarshal) 632 } 633 resp := &cluster.GrainResponse{MessageData: bytes} 634 ctx.Respond(resp) 635 } else { 636 resp := &cluster.GrainErrorResponse{Err: err.Error()} 637 ctx.Respond(resp) 638 } 639 640 case 1: 641 req := &RegisterMessage{} 642 err := proto.Unmarshal(msg.MessageData, req) 643 if err != nil { 644 log.Fatalf("[GRAIN] proto.Unmarshal failed %v", err) 645 } 646 r0, err := a.inner.DeregisterGrain(req, ctx) 647 if err == nil { 648 bytes, errMarshal := proto.Marshal(r0) 649 if errMarshal != nil { 650 log.Fatalf("[GRAIN] proto.Marshal failed %v", errMarshal) 651 } 652 resp := &cluster.GrainResponse{MessageData: bytes} 653 ctx.Respond(resp) 654 } else { 655 resp := &cluster.GrainErrorResponse{Err: err.Error()} 656 ctx.Respond(resp) 657 } 658 659 case 2: 660 req := &Noop{} 661 err := proto.Unmarshal(msg.MessageData, req) 662 if err != nil { 663 log.Fatalf("[GRAIN] proto.Unmarshal failed %v", err) 664 } 665 r0, err := a.inner.BroadcastGetCounts(req, ctx) 666 if err == nil { 667 bytes, errMarshal := proto.Marshal(r0) 668 if errMarshal != nil { 669 log.Fatalf("[GRAIN] proto.Marshal failed %v", errMarshal) 670 } 671 resp := &cluster.GrainResponse{MessageData: bytes} 672 ctx.Respond(resp) 673 } else { 674 resp := &cluster.GrainErrorResponse{Err: err.Error()} 675 ctx.Respond(resp) 676 } 677 678 } 679 default: 680 log.Printf("Unknown message %v", msg) 681 } 682 }