go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/llx/llx.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package llx 5 6 //go:generate protoc --proto_path=. --go_out=. --go_opt=paths=source_relative --rangerrpc_out=. llx.proto 7 8 import ( 9 "errors" 10 "sort" 11 "strconv" 12 "sync" 13 14 uuid "github.com/gofrs/uuid" 15 "github.com/rs/zerolog/log" 16 "go.mondoo.com/cnquery/providers-sdk/v1/resources" 17 "go.mondoo.com/cnquery/types" 18 "go.mondoo.com/cnquery/utils/multierr" 19 ) 20 21 // ResultCallback function type 22 type ResultCallback func(*RawResult) 23 24 var emptyFunction = Function{} 25 26 // RawResult wraps RawData to code and refs 27 type RawResult struct { 28 Data *RawData 29 CodeID string 30 Ref uint64 31 } 32 33 type stepCache struct { 34 Result *RawData 35 IsStatic bool 36 } 37 38 // Calls is a map connecting call-refs with each other 39 type Calls struct { 40 locker sync.Mutex 41 calls map[uint64][]uint64 42 } 43 44 // Store a new call connection. 45 // Returns true if this connection already exists. 46 // Returns false if this is a new connection. 47 func (c *Calls) Store(k uint64, v uint64) bool { 48 c.locker.Lock() 49 defer c.locker.Unlock() 50 51 calls, ok := c.calls[k] 52 if !ok { 53 calls = []uint64{} 54 } else { 55 for k := range calls { 56 if calls[k] == v { 57 return true 58 } 59 } 60 } 61 62 calls = append(calls, v) 63 c.calls[k] = calls 64 return false 65 } 66 67 // Load a call connection 68 func (c *Calls) Load(k uint64) ([]uint64, bool) { 69 c.locker.Lock() 70 v, ok := c.calls[k] 71 c.locker.Unlock() 72 return v, ok 73 } 74 75 // cache is a map containing stepCache values 76 type cache struct { 77 data map[uint64]*stepCache 78 lock sync.Mutex 79 } 80 81 func newCache() *cache { 82 return &cache{ 83 data: map[uint64]*stepCache{}, 84 lock: sync.Mutex{}, 85 } 86 } 87 88 // Store a new call connection 89 func (c *cache) Store(k uint64, v *stepCache) { 90 c.lock.Lock() 91 c.data[k] = v 92 c.lock.Unlock() 93 } 94 95 // Load a call connection 96 func (c *cache) Load(k uint64) (*stepCache, bool) { 97 c.lock.Lock() 98 res, ok := c.data[k] 99 c.lock.Unlock() 100 101 return res, ok 102 } 103 104 type blockExecutor struct { 105 id string 106 blockRef uint64 107 entrypoints map[uint64]struct{} 108 callback ResultCallback 109 callbackPoints map[uint64]string 110 cache *cache 111 stepTracker *cache 112 calls *Calls 113 block *Block 114 parent *blockExecutor 115 ctx *MQLExecutorV2 116 watcherIds *types.StringSet 117 } 118 119 // MQLExecutorV2 is the runtime of MQL codestructure 120 type MQLExecutorV2 struct { 121 id string 122 runtime Runtime 123 code *CodeV2 124 starts []uint64 125 props map[string]*Primitive 126 127 lock sync.Mutex 128 blockExecutors []*blockExecutor 129 unregistered bool 130 } 131 132 func (c *blockExecutor) watcherUID(ref uint64) string { 133 return c.id + "\x00" + strconv.FormatInt(int64(ref), 10) 134 } 135 136 func errorResult(err error, codeID string) *RawResult { 137 return &RawResult{ 138 Data: &RawData{Error: err}, 139 CodeID: codeID, 140 } 141 } 142 143 func errorResultMsg(msg string, codeID string) *RawResult { 144 return &RawResult{ 145 Data: &RawData{Error: errors.New(msg)}, 146 CodeID: codeID, 147 } 148 } 149 150 // NewExecutor will create a code runner from code, running in a runtime, calling 151 // callback whenever we get a result 152 func NewExecutorV2(code *CodeV2, runtime Runtime, props map[string]*Primitive, callback ResultCallback) (*MQLExecutorV2, error) { 153 if runtime == nil { 154 return nil, errors.New("cannot exec MQL without a runtime") 155 } 156 157 if code == nil { 158 return nil, errors.New("cannot run executor without code") 159 } 160 161 res := &MQLExecutorV2{ 162 id: uuid.Must(uuid.NewV4()).String(), 163 runtime: runtime, 164 code: code, 165 props: props, 166 blockExecutors: []*blockExecutor{}, 167 } 168 169 exec, err := res._newBlockExecutor(1<<32, callback, nil) 170 if err != nil { 171 return nil, err 172 } 173 174 res.blockExecutors = append(res.blockExecutors, exec) 175 176 return res, nil 177 } 178 179 func (c *MQLExecutorV2) _newBlockExecutor(blockRef uint64, callback ResultCallback, parent *blockExecutor) (*blockExecutor, error) { 180 block := c.code.Block(blockRef) 181 182 if block == nil { 183 return nil, errors.New("cannot find block " + strconv.FormatUint(blockRef, 10)) 184 } 185 186 callbackPoints := map[uint64]string{} 187 188 res := &blockExecutor{ 189 id: uuid.Must(uuid.NewV4()).String() + "/" + strconv.FormatUint(blockRef>>32, 10), 190 blockRef: blockRef, 191 callback: callback, 192 callbackPoints: callbackPoints, 193 cache: newCache(), 194 stepTracker: newCache(), 195 calls: &Calls{ 196 locker: sync.Mutex{}, 197 calls: map[uint64][]uint64{}, 198 }, 199 block: block, 200 ctx: c, 201 parent: parent, 202 watcherIds: &types.StringSet{}, 203 entrypoints: map[uint64]struct{}{}, 204 } 205 206 for _, ref := range block.Entrypoints { 207 id := c.code.Checksums[ref] 208 if id == "" { 209 return nil, errors.New("llx.executor> cannot execute with invalid ref ID in entrypoint") 210 } 211 if ref < 1 { 212 return nil, errors.New("llx.executor> cannot execute with invalid ref number in entrypoint") 213 } 214 res.entrypoints[ref] = struct{}{} 215 res.callbackPoints[ref] = id 216 } 217 218 for _, ref := range block.Datapoints { 219 id := c.code.Checksums[ref] 220 if id == "" { 221 return nil, errors.New("llx.executor> cannot execute with invalid ref ID in datapoint") 222 } 223 if ref < 1 { 224 return nil, errors.New("llx.executor> cannot execute with invalid ref number in datapoint") 225 } 226 res.callbackPoints[ref] = id 227 } 228 229 if len(res.callbackPoints) == 0 { 230 panic("no callback points") 231 } 232 233 return res, nil 234 } 235 236 // NoRun returns error for all callbacks and don't run code 237 func (c *MQLExecutorV2) NoRun(err error) { 238 callback := c.blockExecutors[0].callback 239 240 for ref := range c.blockExecutors[0].callbackPoints { 241 if codeID, ok := c.blockExecutors[0].callbackPoints[ref]; ok { 242 callback(errorResult(err, codeID)) 243 } 244 } 245 } 246 247 func (c *MQLExecutorV2) addBlockExecutor(be *blockExecutor) bool { 248 c.lock.Lock() 249 defer c.lock.Unlock() 250 if c.unregistered { 251 return false 252 } 253 c.blockExecutors = append(c.blockExecutors, be) 254 return true 255 } 256 257 func (c *MQLExecutorV2) Unregister() error { 258 log.Trace().Str("id", c.id).Msg("exec> unregister") 259 c.lock.Lock() 260 defer c.lock.Unlock() 261 if c.unregistered { 262 return nil 263 } 264 c.unregistered = true 265 266 var errs []error 267 for i := range c.blockExecutors { 268 be := c.blockExecutors[i] 269 errs = append(errs, be.unregister()...) 270 } 271 272 if len(errs) > 0 { 273 return errors.New("multiple errors unregistering") 274 } 275 276 return nil 277 } 278 279 // Run a given set of code. Only returns an error if the couldn't be started. 280 func (c *MQLExecutorV2) Run() error { 281 if len(c.blockExecutors) == 0 { 282 return errors.New("cannot find initial block executor for running this code") 283 } 284 285 core := c.blockExecutors[0] 286 core.run() 287 return nil 288 } 289 290 func (b *blockExecutor) newBlockExecutor(blockRef uint64, callback ResultCallback) (*blockExecutor, error) { 291 return b.ctx._newBlockExecutor(blockRef, callback, b) 292 } 293 294 func (e *blockExecutor) unregister() []error { 295 var errs []error 296 297 e.watcherIds.Range(func(key string) bool { 298 if err := e.ctx.runtime.Unregister(key); err != nil { 299 log.Error().Err(err).Msg("exec> unregister error") 300 errs = append(errs, err) 301 } 302 return true 303 }) 304 305 return errs 306 } 307 308 func (b *blockExecutor) isInMyBlock(ref uint64) bool { 309 return (ref >> 32) == (b.blockRef >> 32) 310 } 311 312 func (b *blockExecutor) mustLookup(ref uint64) *RawData { 313 d, _, err := b.parent.lookupValue(ref) 314 if err != nil { 315 panic(err) 316 } 317 if d == nil { 318 panic("did not lookup datapoint") 319 } 320 return d 321 } 322 323 // run code with a runtime and return results 324 func (b *blockExecutor) run() { 325 for ref, codeID := range b.callbackPoints { 326 if !b.isInMyBlock(ref) { 327 v := b.mustLookup(ref) 328 b.callback(&RawResult{ 329 CodeID: codeID, 330 Data: v, 331 }) 332 } 333 } 334 // work down all entrypoints 335 refs := make([]uint64, len(b.block.Entrypoints)+len(b.block.Datapoints)) 336 i := 0 337 for _, ref := range b.block.Entrypoints { 338 refs[i] = ref 339 i++ 340 } 341 for _, ref := range b.block.Datapoints { 342 refs[i] = ref 343 i++ 344 } 345 sort.Slice(refs, func(i, j int) bool { return refs[i] > refs[j] }) 346 347 for _, ref := range refs { 348 // if this entrypoint is already connected, don't add it again 349 if _, ok := b.stepTracker.Load(ref); ok { 350 continue 351 } 352 353 log.Trace().Uint64("entrypoint", ref).Str("exec-ID", b.ctx.id).Msg("exec.Run>") 354 b.runChain(ref) 355 } 356 } 357 358 func (b *blockExecutor) ensureArgsResolved(args []*Primitive, ref uint64) (uint64, error) { 359 for _, arg := range args { 360 _, dref, err := b.resolveValue(arg, ref) 361 if dref != 0 || err != nil { 362 return dref, err 363 } 364 } 365 return 0, nil 366 } 367 368 func reportSync(cb ResultCallback) ResultCallback { 369 lock := sync.Mutex{} 370 return func(rr *RawResult) { 371 lock.Lock() 372 defer lock.Unlock() 373 cb(rr) 374 } 375 } 376 377 type arrayBlockCallResults struct { 378 lock sync.Mutex 379 results []arrayBlockCallResult 380 errors []error 381 waiting []int 382 unfinishedBlockCalls int 383 onComplete func([]arrayBlockCallResult, []error) 384 entrypoints map[string]struct{} 385 datapoints map[string]struct{} 386 } 387 388 type arrayBlockCallResult struct { 389 entrypoints map[string]interface{} 390 datapoints map[string]interface{} 391 } 392 393 func (a arrayBlockCallResult) toRawData() *RawData { 394 v := make(map[string]interface{}, len(a.entrypoints)+len(a.datapoints)) 395 396 for checksum, res := range a.entrypoints { 397 v[checksum] = res 398 } 399 400 for checksum, res := range a.datapoints { 401 v[checksum] = res 402 } 403 404 v["__t"] = &RawData{ 405 Type: types.Bool, 406 Value: a.isTruthy(), 407 } 408 409 success, ok := a.isSuccess() 410 if ok { 411 v["__s"] = &RawData{ 412 Type: types.Bool, 413 Value: success, 414 } 415 } else { 416 v["__s"] = &RawData{ 417 Type: types.Nil, 418 } 419 } 420 421 return &RawData{ 422 Type: types.Block, 423 Value: v, 424 } 425 } 426 427 func (a arrayBlockCallResult) isTruthy() bool { 428 for _, res := range a.entrypoints { 429 rd := &RawData{ 430 Type: types.Any, 431 Value: res, 432 } 433 isT, isValid := rd.IsTruthy() 434 if isValid && !isT { 435 return false 436 } 437 } 438 return true 439 } 440 441 func (a arrayBlockCallResult) isSuccess() (bool, bool) { 442 valid := false 443 for _, res := range a.entrypoints { 444 rd := &RawData{ 445 Type: types.Any, 446 Value: res, 447 } 448 isS, isValid := rd.IsSuccess() 449 if isValid && !isS { 450 return false, true 451 } 452 valid = valid || isValid 453 } 454 return true, valid 455 } 456 457 func (a *arrayBlockCallResults) update(i int, res *RawResult) { 458 a.lock.Lock() 459 defer a.lock.Unlock() 460 461 _, isEntrypoint := a.entrypoints[res.CodeID] 462 _, isDatapoint := a.datapoints[res.CodeID] 463 464 if !(isEntrypoint || isDatapoint) { 465 return 466 } 467 468 _, hasEntrypointResult := a.results[i].entrypoints[res.CodeID] 469 _, hasDatapointResult := a.results[i].datapoints[res.CodeID] 470 471 if !(hasEntrypointResult || hasDatapointResult) { 472 a.waiting[i]-- 473 if a.waiting[i] == 0 { 474 a.unfinishedBlockCalls-- 475 } 476 } 477 478 if isEntrypoint { 479 a.results[i].entrypoints[res.CodeID] = res.Data 480 } 481 482 if isDatapoint { 483 a.results[i].datapoints[res.CodeID] = res.Data 484 } 485 486 if res.Data.Error != nil { 487 a.errors = append(a.errors, res.Data.Error) 488 } 489 490 if a.unfinishedBlockCalls == 0 { 491 a.onComplete(a.results, a.errors) 492 } 493 } 494 495 func newArrayBlockCallResultsV2(expectedBlockCalls int, code *CodeV2, blockRef uint64, onComplete func([]arrayBlockCallResult, []error)) (*arrayBlockCallResults, bool) { 496 results := make([]arrayBlockCallResult, expectedBlockCalls) 497 waiting := make([]int, expectedBlockCalls) 498 499 codepoints := map[string]struct{}{} 500 entrypoints := map[string]struct{}{} 501 502 b := code.Block(blockRef) 503 504 for _, ep := range b.Entrypoints { 505 checksum := code.Checksums[ep] 506 codepoints[checksum] = struct{}{} 507 entrypoints[checksum] = struct{}{} 508 } 509 510 datapoints := map[string]struct{}{} 511 for _, dp := range b.Datapoints { 512 checksum := code.Checksums[dp] 513 codepoints[checksum] = struct{}{} 514 datapoints[checksum] = struct{}{} 515 } 516 517 expectedCodepoints := len(codepoints) 518 if expectedCodepoints == 0 { 519 results := make([]arrayBlockCallResult, expectedBlockCalls) 520 onComplete(results, nil) 521 return nil, false 522 } else { 523 for i := range waiting { 524 waiting[i] = expectedCodepoints 525 } 526 } 527 528 for i := range results { 529 results[i] = arrayBlockCallResult{ 530 entrypoints: map[string]interface{}{}, 531 datapoints: map[string]interface{}{}, 532 } 533 } 534 535 return &arrayBlockCallResults{ 536 lock: sync.Mutex{}, 537 results: results, 538 waiting: waiting, 539 unfinishedBlockCalls: expectedBlockCalls, 540 onComplete: onComplete, 541 entrypoints: entrypoints, 542 datapoints: datapoints, 543 }, true 544 } 545 546 func (c *blockExecutor) runFunctionBlocks(argList [][]*RawData, blockRef uint64, 547 onComplete func([]arrayBlockCallResult, []error), 548 ) error { 549 callResults, shouldRun := newArrayBlockCallResultsV2(len(argList), c.ctx.code, blockRef, onComplete) 550 if !shouldRun { 551 return nil 552 } 553 for idx := range argList { 554 i := idx 555 args := argList[i] 556 err := c.runFunctionBlock(args, blockRef, func(rr *RawResult) { 557 callResults.update(i, rr) 558 }) 559 if err != nil { 560 return err 561 } 562 } 563 return nil 564 } 565 566 func (b *blockExecutor) runFunctionBlock(args []*RawData, blockRef uint64, cb ResultCallback) error { 567 executor, err := b.newBlockExecutor(blockRef, reportSync(cb)) 568 if err != nil { 569 return err 570 } 571 572 b.ctx.addBlockExecutor(executor) 573 574 if len(args) < int(executor.block.Parameters) { 575 panic("not enough arguments") 576 } 577 578 for i := int32(0); i < executor.block.Parameters; i++ { 579 executor.cache.Store(blockRef|uint64(i+1), &stepCache{ 580 Result: args[i], 581 IsStatic: true, 582 }) 583 } 584 585 executor.run() 586 return nil 587 } 588 589 func (b *blockExecutor) runBlock(bind *RawData, functionRef *Primitive, args []*Primitive, ref uint64) (*RawData, uint64, error) { 590 if bind != nil && bind.Value == nil && bind.Type != types.Nil { 591 return &RawData{Type: bind.Type, Value: nil}, 0, nil 592 } 593 594 typ := types.Type(functionRef.Type) 595 if !typ.IsFunction() { 596 return nil, 0, errors.New("called block with wrong function type") 597 } 598 fref, ok := functionRef.RefV2() 599 if !ok { 600 return nil, 0, errors.New("cannot retrieve function reference on block call") 601 } 602 603 block := b.ctx.code.Block(fref) 604 if block == nil { 605 return nil, 0, errors.New("block function is nil") 606 } 607 608 fargs := []*RawData{} 609 if bind != nil { 610 fargs = append(fargs, bind) 611 } 612 for i := range args { 613 a, b, c := b.resolveValue(args[i], ref) 614 if c != nil || b != 0 { 615 return a, b, c 616 } 617 fargs = append(fargs, a) 618 } 619 620 err := b.runFunctionBlocks([][]*RawData{fargs}, fref, func(results []arrayBlockCallResult, errs []error) { 621 var err multierr.Errors 622 err.Add(errs...) 623 624 if len(results) > 0 { 625 fun := b.ctx.code.Block(fref) 626 if fun.SingleValue { 627 res := results[0].entrypoints[b.ctx.code.Checksums[fun.Entrypoints[0]]].(*RawData) 628 b.cache.Store(ref, &stepCache{ 629 Result: res, 630 }) 631 b.triggerChain(ref, res) 632 return 633 } 634 } 635 636 data := results[0].toRawData() 637 data.Error = err.Deduplicate() 638 blockResult := data.Value.(map[string]interface{}) 639 640 if bind != nil && bind.Type.IsResource() { 641 rr, ok := bind.Value.(Resource) 642 if !ok { 643 log.Warn().Msg("cannot cast resource to resource type") 644 } else { 645 blockResult["_"] = &RawData{ 646 Type: bind.Type, 647 Value: rr, 648 } 649 } 650 } 651 652 b.cache.Store(ref, &stepCache{ 653 Result: data, 654 IsStatic: true, 655 }) 656 b.triggerChain(ref, data) 657 }) 658 659 return nil, 0, err 660 } 661 662 type resourceInterface interface { 663 MqlResource() Resource 664 } 665 666 func pargs2argmap(b *blockExecutor, ref uint64, args []*Primitive) (map[string]*Primitive, uint64, error) { 667 if len(args) == 0 { 668 return nil, 0, nil 669 } 670 671 res := make(map[string]*Primitive, len(args)) 672 var x *RawData 673 var rref uint64 674 var err error 675 for i := 0; i+1 < len(args); i += 2 { 676 k := args[i] 677 if types.Type(k.Type) != types.String { 678 return nil, 0, errors.New("incorrect argument type (caller keys should always be strings)") 679 } 680 681 key := k.RawData().Value.(string) 682 683 // TODO: this is a tedious and slow approach, speed it up... 684 x, rref, err = b.resolveValue(args[i+1], ref) 685 if rref != 0 || err != nil { 686 return nil, rref, err 687 } 688 res[key] = x.Result().Data 689 } 690 691 return res, 0, nil 692 } 693 694 func (b *blockExecutor) createResource(name string, binding uint64, f *Function, ref uint64) (*RawData, uint64, error) { 695 runtime := b.ctx.runtime 696 if binding != 0 { 697 panic("NOT SURE HOW TO RESOLVE THIS") 698 // res, dref, err := b.resolveRef(binding, ref) 699 // if dref != 0 || err != nil { 700 // return res, dref, err 701 // } 702 // mqlResource := res.Value.(resourceInterface).MqlResource() 703 // runtime = mqlResource.MqlRuntime 704 } 705 706 args, rref, err := pargs2argmap(b, ref, f.Args) 707 if err != nil || rref != 0 { 708 return nil, rref, err 709 } 710 711 resource, err := runtime.CreateResource(name, args) 712 if err != nil { 713 // in case it's not something that requires later loading, store the error 714 // so that consecutive steps can retrieve it cached 715 if _, ok := err.(resources.NotReadyError); !ok { 716 res := stepCache{ 717 Result: &RawData{ 718 Type: types.Resource(name), 719 Value: nil, 720 Error: err, 721 }, 722 IsStatic: true, 723 } 724 b.cache.Store(ref, &res) 725 } 726 727 return nil, 0, err 728 } 729 730 res := stepCache{ 731 Result: &RawData{ 732 Type: types.Resource(name), 733 Value: resource, 734 }, 735 IsStatic: true, 736 } 737 b.cache.Store(ref, &res) 738 return res.Result, 0, nil 739 } 740 741 func (b *blockExecutor) runGlobalFunction(chunk *Chunk, f *Function, ref uint64) (*RawData, uint64, error) { 742 h, ok := handleGlobalV2(chunk.Id) 743 if ok { 744 if h == nil { 745 return nil, 0, errors.New("found function " + chunk.Id + " but no handler. this should not happen and points to an implementation error") 746 } 747 748 res, dref, err := h(b, f, ref) 749 log.Trace().Msgf("exec> global: %s %+v = %#v", chunk.Id, f.Args, res) 750 if res != nil { 751 b.cache.Store(ref, &stepCache{Result: res}) 752 } 753 return res, dref, err 754 } 755 756 return b.createResource(chunk.Id, 0, f, ref) 757 } 758 759 // connect references, calling `dst` if `src` is updated 760 func (b *blockExecutor) connectRef(src uint64, dst uint64) (*RawData, uint64, error) { 761 if !b.isInMyBlock(src) || !b.isInMyBlock(dst) { 762 panic("cannot connect refs across block boundaries") 763 } 764 // connect the ref. If it is already connected, someone else already made this 765 // call, so we don't have to follow up anymore 766 if exists := b.calls.Store(src, dst); exists { 767 return nil, 0, nil 768 } 769 770 // if the ref was not yet connected, we must run the src ref after we connected it 771 return nil, src, nil 772 } 773 774 func (e *blockExecutor) runFunction(chunk *Chunk, ref uint64) (*RawData, uint64, error) { 775 f := chunk.Function 776 if f == nil { 777 f = &emptyFunction 778 } 779 780 // global functions, for now only resources 781 if f.Binding == 0 { 782 return e.runGlobalFunction(chunk, f, ref) 783 } 784 785 // check if the bound value exists, otherwise connect it 786 res, dref, err := e.resolveRef(f.Binding, ref) 787 if res == nil { 788 return res, dref, err 789 } 790 791 if res.Error != nil { 792 e.cache.Store(ref, &stepCache{Result: res}) 793 return nil, 0, res.Error 794 } 795 796 return e.runBoundFunction(res, chunk, ref) 797 } 798 799 func (e *blockExecutor) runChunk(chunk *Chunk, ref uint64) (*RawData, uint64, error) { 800 switch chunk.Call { 801 case Chunk_PRIMITIVE: 802 res, dref, err := e.resolveValue(chunk.Primitive, ref) 803 if res != nil { 804 e.cache.Store(ref, &stepCache{Result: res}) 805 } else if err != nil { 806 e.cache.Store(ref, &stepCache{Result: &RawData{ 807 Error: err, 808 }}) 809 } 810 811 return res, dref, err 812 case Chunk_FUNCTION: 813 return e.runFunction(chunk, ref) 814 815 case Chunk_PROPERTY: 816 property, ok := e.ctx.props[chunk.Id] 817 if !ok { 818 return nil, 0, errors.New("cannot find property '" + chunk.Id + "'") 819 } 820 821 res, dref, err := e.resolveValue(property, ref) 822 if dref != 0 || err != nil { 823 return res, dref, err 824 } 825 e.cache.Store(ref, &stepCache{Result: res}) 826 return res, dref, err 827 828 default: 829 return nil, 0, errors.New("Tried to run a chunk which has an unknown type: " + chunk.Call.String()) 830 } 831 } 832 833 func (e *blockExecutor) runRef(ref uint64) (*RawData, uint64, error) { 834 chunk := e.ctx.code.Chunk(ref) 835 if chunk == nil { 836 return nil, 0, errors.New("Called a chunk that doesn't exist, ref = " + strconv.FormatInt(int64(ref), 10)) 837 } 838 return e.runChunk(chunk, ref) 839 } 840 841 // runChain starting at a ref of the code, follow it down and report 842 // jever result it has at the end of its execution. this will register 843 // async callbacks against referenced chunks too 844 func (e *blockExecutor) runChain(start uint64) { 845 var res *RawData 846 var err error 847 nextRef := start 848 var curRef uint64 849 var remaining []uint64 850 851 for nextRef != 0 { 852 curRef = nextRef 853 e.stepTracker.Store(curRef, nil) 854 // log.Trace().Uint64("ref", curRef).Msg("exec> run chain") 855 856 // Try to load the result from cache if it already exists. This was added 857 // so that blocks that are called on top of a binding, where the results 858 // for the binding are pre-loaded, are actually read from cache. Typically 859 // follow-up calls would try to load from cache and would get the correct 860 // value, however if there are no follow-up calls we still want to return 861 // the correct value. 862 // This may be optimized in a way that we don't have to check loading it 863 // on every call. 864 cached, ok := e.cache.Load(curRef) 865 if ok { 866 res = cached.Result 867 nextRef = 0 868 err = nil 869 } else { 870 res, nextRef, err = e.runRef(curRef) 871 } 872 873 // stop this chain of execution, if it didn't return anything and 874 // there is nothing else left to process 875 // we need more data ie an event to provide info 876 if res == nil && nextRef == 0 && err == nil && len(remaining) == 0 { 877 log.Trace().Uint64("ref", curRef).Msg("exec> stop chain") 878 return 879 } 880 881 // if this is a result for a callback (entry- or datapoint) send it 882 if res != nil { 883 if codeID, ok := e.callbackPoints[curRef]; ok { 884 e.callback(&RawResult{Data: res, CodeID: codeID}) 885 } 886 } else if err != nil { 887 if codeID, ok := e.callbackPoints[curRef]; ok { 888 e.callback(errorResult(err, codeID)) 889 } 890 if _, isNotReadyError := err.(resources.NotReadyError); !isNotReadyError { 891 if sc, _ := e.cache.Load(curRef); sc == nil { 892 e.cache.Store(curRef, &stepCache{ 893 Result: &RawData{ 894 Type: types.Unset, 895 Value: nil, 896 Error: err, 897 }, 898 }) 899 } 900 } 901 } 902 903 // get the next reference, if we are not directed anywhere 904 if nextRef == 0 { 905 // note: if the call cannot be retrieved it will use the 906 // zero value, which is 0 in this case; i.e. if !ok => ref = 0 907 nextRefs, _ := e.calls.Load(curRef) 908 cnt := len(nextRefs) 909 if cnt != 0 { 910 nextRef = nextRefs[0] 911 remaining = append(remaining, nextRefs[1:]...) 912 continue 913 } 914 915 cnt = len(remaining) 916 if cnt == 0 { 917 break 918 } 919 nextRef = remaining[0] 920 remaining = remaining[1:] 921 } 922 } 923 } 924 925 // triggerChain when a reference has a new value set 926 // unlike runChain this will not execute the ref chunk, but rather 927 // try to move to the next called chunk - or if it's not available 928 // handle the result 929 func (e *blockExecutor) triggerChain(ref uint64, data *RawData) { 930 // before we do anything else, we may have to provide the value from 931 // this callback point 932 if codeID, ok := e.callbackPoints[ref]; ok { 933 e.callback(&RawResult{Data: data, CodeID: codeID}) 934 } 935 936 nxt, ok := e.calls.Load(ref) 937 if ok { 938 if len(nxt) == 0 { 939 panic("internal state error: cannot trigger next call on chain because it points to a zero ref") 940 } 941 for i := range nxt { 942 e.runChain(nxt[i]) 943 } 944 return 945 } 946 947 codeID := e.callbackPoints[ref] 948 res, ok := e.cache.Load(ref) 949 if !ok { 950 e.callback(errorResultMsg("exec> cannot find results to chunk reference "+strconv.FormatInt(int64(ref), 10), codeID)) 951 return 952 } 953 954 log.Trace().Uint64("ref", ref).Msgf("exec> trigger callback") 955 e.callback(&RawResult{Data: res.Result, CodeID: codeID}) 956 } 957 958 func (e *blockExecutor) triggerChainError(ref uint64, err error) { 959 cur := ref 960 var remaining []uint64 961 for cur > 0 { 962 if codeID, ok := e.callbackPoints[cur]; ok { 963 e.callback(&RawResult{ 964 Data: &RawData{ 965 Error: err, 966 }, 967 CodeID: codeID, 968 }) 969 } 970 971 nxt, ok := e.calls.Load(cur) 972 if !ok { 973 if len(remaining) == 0 { 974 break 975 } 976 cur = remaining[0] 977 remaining = remaining[1:] 978 } 979 if len(nxt) == 0 { 980 panic("internal state error: cannot trigger next call on chain because it points to a zero ref") 981 } 982 cur = nxt[0] 983 remaining = append(remaining, nxt[1:]...) 984 } 985 }