Merge pull request #5401 from jsternberg/scheduler-edge-simplify

solver: simplify edge-related functions in the solver
This commit is contained in:
Tõnis Tiigi
2024-10-11 10:02:31 -07:00
committed by GitHub

View File

@@ -329,17 +329,7 @@ func (e *edge) skipPhase2FastCache(dep *dep) bool {
// incoming requests
func (e *edge) unpark(incoming []pipeSender, updates, allPipes []pipeReceiver, f *pipeFactory) {
// process all incoming changes
depChanged := false
for _, upt := range updates {
if changed := e.processUpdate(upt); changed {
depChanged = true
}
}
if depChanged {
// the dep responses had changes. need to reevaluate edge state
e.recalcCurrentState()
}
e.processUpdates(updates)
desiredState, done := e.respondToIncoming(incoming, allPipes)
if done {
@@ -387,156 +377,45 @@ func (e *edge) markFailed(f *pipeFactory, err error) {
})
}
func (e *edge) processUpdates(updates []pipe.Receiver[*edgeRequest, any]) {
depChanged := false
for _, upt := range updates {
depChanged = e.processUpdate(upt) || depChanged
}
if depChanged {
e.recalcCurrentState()
}
}
// processUpdate is called by unpark for every updated pipe request
func (e *edge) processUpdate(upt pipeReceiver) (depChanged bool) {
// response for cachemap request
if upt == e.cacheMapReq && upt.Status().Completed {
if err := upt.Status().Err; err != nil {
e.cacheMapReq = nil
if !upt.Status().Canceled && e.err == nil {
e.err = err
}
} else {
resp := upt.Status().Value.(*cacheMapResp)
e.cacheMap = resp.CacheMap
e.cacheMapDone = resp.complete
e.cacheMapIndex++
if len(e.deps) == 0 {
e.cacheMapDigests = append(e.cacheMapDigests, e.cacheMap.Digest)
if !e.op.IgnoreCache() {
keys, err := e.op.Cache().Query(nil, 0, e.cacheMap.Digest, e.edge.Index)
if err != nil {
bklog.G(context.TODO()).Error(errors.Wrap(err, "invalid query response")) // make the build fail for this error
} else {
for _, k := range keys {
k.vtx = e.edge.Vertex.Digest()
records, err := e.op.Cache().Records(context.Background(), k)
if err != nil {
bklog.G(context.TODO()).Errorf("error receiving cache records: %v", err)
continue
}
for _, r := range records {
e.cacheRecords[r.ID] = r
}
e.keys = append(e.keys, e.makeExportable(k, records))
}
}
}
e.state = edgeStatusCacheSlow
}
if e.allDepsHaveKeys(false) {
e.keysDidChange = true
}
// probe keys that were loaded before cache map
for i, dep := range e.deps {
e.probeCache(dep, withSelector(dep.keys, e.cacheMap.Deps[i].Selector))
e.checkDepMatchPossible(dep)
}
if !e.cacheMapDone {
e.cacheMapReq = nil
}
}
e.processCacheMapReq()
return true
}
// response for exec request
if upt == e.execReq && upt.Status().Completed {
if err := upt.Status().Err; err != nil {
e.execReq = nil
if e.execCacheLoad {
for k := range e.cacheRecordsLoaded {
delete(e.cacheRecords, k)
}
} else if !upt.Status().Canceled && e.err == nil {
e.err = err
}
} else {
e.result = NewSharedCachedResult(upt.Status().Value.(CachedResult))
e.state = edgeStatusComplete
}
e.processExecReq()
return true
}
// response for requests to dependencies
if dep, ok := e.depRequests[upt]; ok {
if err := upt.Status().Err; !upt.Status().Canceled && upt.Status().Completed && err != nil {
if e.err == nil {
e.err = err
}
dep.err = err
}
if upt.Status().Value == nil {
return
}
state, isEdgeState := upt.Status().Value.(*edgeState)
if !isEdgeState {
bklog.G(context.TODO()).Warnf("invalid edgeState value for update: %T", state)
return
}
if len(dep.keys) < len(state.keys) {
newKeys := state.keys[len(dep.keys):]
if e.cacheMap != nil {
e.probeCache(dep, withSelector(newKeys, e.cacheMap.Deps[dep.index].Selector))
dep.edgeState.keys = state.keys
if e.allDepsHaveKeys(false) {
e.keysDidChange = true
}
}
depChanged = true
}
if dep.state != edgeStatusComplete && state.state == edgeStatusComplete {
e.keysDidChange = true
}
recheck := state.state != dep.state
dep.edgeState = *state
if recheck && e.cacheMap != nil {
e.checkDepMatchPossible(dep)
depChanged = true
}
return
return e.processDepReq(dep)
}
// response for result based cache function
for i, dep := range e.deps {
if upt == dep.slowCacheReq && upt.Status().Completed {
if err := upt.Status().Err; err != nil {
dep.slowCacheReq = nil
if !upt.Status().Canceled && e.err == nil {
e.err = upt.Status().Err
}
} else if !dep.slowCacheComplete {
dgst := upt.Status().Value.(digest.Digest)
if e.cacheMap.Deps[int(dep.index)].ComputeDigestFunc != nil && dgst != "" {
k := NewCacheKey(dgst, "", -1)
dep.slowCacheKey = &ExportableCacheKey{CacheKey: k, Exporter: &exporter{k: k}}
slowKeyExp := CacheKeyWithSelector{CacheKey: *dep.slowCacheKey}
defKeys := make([]CacheKeyWithSelector, 0, len(dep.result.CacheKeys()))
for _, dk := range dep.result.CacheKeys() {
defKeys = append(defKeys, CacheKeyWithSelector{CacheKey: dk, Selector: e.cacheMap.Deps[i].Selector})
}
dep.slowCacheFoundKey = e.probeCache(dep, []CacheKeyWithSelector{slowKeyExp})
// connect def key to slow key
e.op.Cache().Query(append(defKeys, slowKeyExp), dep.index, e.cacheMap.Digest, e.edge.Index)
}
dep.slowCacheComplete = true
e.keysDidChange = true
e.checkDepMatchPossible(dep) // not matching key here doesn't set nocachematch possible to true
}
e.processDepSlowCacheReq(i, dep)
return true
}
}
return
return false
}
// recalcCurrentState is called by unpark to recompute internal state after
@@ -689,6 +568,151 @@ func (e *edge) recalcCurrentState() {
}
}
func (e *edge) processCacheMapReq() {
upt := e.cacheMapReq
if err := upt.Status().Err; err != nil {
e.cacheMapReq = nil
if !upt.Status().Canceled && e.err == nil {
e.err = err
}
return
}
resp := upt.Status().Value.(*cacheMapResp)
e.cacheMap = resp.CacheMap
e.cacheMapDone = resp.complete
e.cacheMapIndex++
if len(e.deps) == 0 {
e.cacheMapDigests = append(e.cacheMapDigests, e.cacheMap.Digest)
if !e.op.IgnoreCache() {
keys, err := e.op.Cache().Query(nil, 0, e.cacheMap.Digest, e.edge.Index)
if err != nil {
bklog.G(context.TODO()).Error(errors.Wrap(err, "invalid query response")) // make the build fail for this error
} else {
for _, k := range keys {
k.vtx = e.edge.Vertex.Digest()
records, err := e.op.Cache().Records(context.Background(), k)
if err != nil {
bklog.G(context.TODO()).Errorf("error receiving cache records: %v", err)
continue
}
for _, r := range records {
e.cacheRecords[r.ID] = r
}
e.keys = append(e.keys, e.makeExportable(k, records))
}
}
}
e.state = edgeStatusCacheSlow
}
if e.allDepsHaveKeys(false) {
e.keysDidChange = true
}
// probe keys that were loaded before cache map
for i, dep := range e.deps {
e.probeCache(dep, withSelector(dep.keys, e.cacheMap.Deps[i].Selector))
e.checkDepMatchPossible(dep)
}
if !e.cacheMapDone {
e.cacheMapReq = nil
}
}
func (e *edge) processExecReq() {
upt := e.execReq
if err := upt.Status().Err; err != nil {
e.execReq = nil
if e.execCacheLoad {
for k := range e.cacheRecordsLoaded {
delete(e.cacheRecords, k)
}
} else if !upt.Status().Canceled && e.err == nil {
e.err = err
}
return
}
e.result = NewSharedCachedResult(upt.Status().Value.(CachedResult))
e.state = edgeStatusComplete
}
func (e *edge) processDepReq(dep *dep) (depChanged bool) {
upt := dep.req
if err := upt.Status().Err; !upt.Status().Canceled && upt.Status().Completed && err != nil {
if e.err == nil {
e.err = err
}
dep.err = err
}
if upt.Status().Value == nil {
return false
}
state, isEdgeState := upt.Status().Value.(*edgeState)
if !isEdgeState {
bklog.G(context.TODO()).Warnf("invalid edgeState value for update: %T", state)
return false
}
if len(dep.keys) < len(state.keys) {
newKeys := state.keys[len(dep.keys):]
if e.cacheMap != nil {
e.probeCache(dep, withSelector(newKeys, e.cacheMap.Deps[dep.index].Selector))
dep.edgeState.keys = state.keys
if e.allDepsHaveKeys(false) {
e.keysDidChange = true
}
}
depChanged = true
}
if dep.state != edgeStatusComplete && state.state == edgeStatusComplete {
e.keysDidChange = true
}
recheck := state.state != dep.state
dep.edgeState = *state
if recheck && e.cacheMap != nil {
e.checkDepMatchPossible(dep)
depChanged = true
}
return depChanged
}
func (e *edge) processDepSlowCacheReq(index int, dep *dep) {
upt := dep.slowCacheReq
if err := upt.Status().Err; err != nil {
dep.slowCacheReq = nil
if !upt.Status().Canceled && e.err == nil {
e.err = upt.Status().Err
}
} else if !dep.slowCacheComplete {
dgst := upt.Status().Value.(digest.Digest)
if e.cacheMap.Deps[int(dep.index)].ComputeDigestFunc != nil && dgst != "" {
k := NewCacheKey(dgst, "", -1)
dep.slowCacheKey = &ExportableCacheKey{CacheKey: k, Exporter: &exporter{k: k}}
slowKeyExp := CacheKeyWithSelector{CacheKey: *dep.slowCacheKey}
defKeys := make([]CacheKeyWithSelector, 0, len(dep.result.CacheKeys()))
for _, dk := range dep.result.CacheKeys() {
defKeys = append(defKeys, CacheKeyWithSelector{CacheKey: dk, Selector: e.cacheMap.Deps[index].Selector})
}
dep.slowCacheFoundKey = e.probeCache(dep, []CacheKeyWithSelector{slowKeyExp})
// connect def key to slow key
e.op.Cache().Query(append(defKeys, slowKeyExp), dep.index, e.cacheMap.Digest, e.edge.Index)
}
dep.slowCacheComplete = true
e.keysDidChange = true
e.checkDepMatchPossible(dep) // not matching key here doesn't set nocachematch possible to true
}
}
// respondToIncoming responds to all incoming requests. completing or
// updating them when possible
func (e *edge) respondToIncoming(incoming []pipeSender, allPipes []pipeReceiver) (edgeStatusType, bool) {
@@ -768,89 +792,119 @@ func (e *edge) respondToIncoming(incoming []pipeSender, allPipes []pipeReceiver)
// createInputRequests creates new requests for dependencies or async functions
// that need to complete to continue processing the edge
func (e *edge) createInputRequests(desiredState edgeStatusType, f *pipeFactory, force bool) bool {
addedNew := false
func (e *edge) createInputRequests(desiredState edgeStatusType, f *pipeFactory, force bool) (addedNew bool) {
// initialize deps state
if e.deps == nil {
e.depRequests = make(map[pipeReceiver]*dep)
e.deps = make([]*dep, 0, len(e.edge.Vertex.Inputs()))
for i := range e.edge.Vertex.Inputs() {
e.deps = append(e.deps, newDep(Index(i)))
}
}
e.ensureDepsInitialized()
// cycle all dependencies. set up outgoing requests if needed
for _, dep := range e.deps {
desiredStateDep := dep.state
if e.noCacheMatchPossible || force {
desiredStateDep = edgeStatusComplete
} else if dep.state == edgeStatusInitial && desiredState > dep.state {
desiredStateDep = edgeStatusCacheFast
} else if dep.state == edgeStatusCacheFast && desiredState > dep.state {
// wait all deps to complete cache fast before continuing with slow cache
if (e.allDepsCompletedCacheFast && len(e.keys) == 0) || len(dep.keyMap) == 0 || e.allDepsHaveKeys(true) {
if !e.skipPhase2FastCache(dep) && e.cacheMap != nil {
desiredStateDep = edgeStatusCacheSlow
}
}
} else if e.cacheMap != nil && dep.state == edgeStatusCacheSlow && desiredState == edgeStatusComplete {
// if all deps have completed cache-slow or content based cache for input is available
if (len(dep.keyMap) == 0 || e.allDepsCompletedCacheSlow || (!e.skipPhase2FastCache(dep) && e.slowCacheFunc(dep) != nil)) && (len(e.cacheRecords) == 0) {
if len(dep.keyMap) == 0 || !e.skipPhase2SlowCache(dep) {
desiredStateDep = edgeStatusComplete
}
}
} else if e.cacheMap != nil && dep.state == edgeStatusCacheSlow && e.slowCacheFunc(dep) != nil && desiredState == edgeStatusCacheSlow {
if len(dep.keyMap) == 0 || !e.skipPhase2SlowCache(dep) {
desiredStateDep = edgeStatusComplete
}
}
desiredStateDep := e.desiredStateDep(dep, desiredState, force)
// outgoing request is needed
if dep.state < desiredStateDep {
addNew := true
if dep.req != nil && !dep.req.Status().Completed {
if dep.req.Request().desiredState != desiredStateDep {
debugSchedulerCancelInputRequest(e, dep, desiredStateDep)
dep.req.Cancel()
} else {
debugSchedulerSkipInputRequestBasedOnExistingRequest(e, dep, desiredStateDep)
addNew = false
}
}
if addNew {
debugSchedulerAddInputRequest(e, dep, desiredStateDep)
req := f.NewInputRequest(e.edge.Vertex.Inputs()[int(dep.index)], &edgeRequest{
currentState: dep.edgeState,
desiredState: desiredStateDep,
currentKeys: len(dep.keys),
})
e.depRequests[req] = dep
dep.req = req
addedNew = true
}
addedNew = e.createOutgoingRequest(dep, desiredStateDep, f) || addedNew
} else {
debugSchedulerSkipInputRequestBasedOnDepState(e, dep, desiredStateDep)
}
// initialize function to compute cache key based on dependency result
if dep.state == edgeStatusComplete && dep.slowCacheReq == nil && (e.slowCacheFunc(dep) != nil || e.preprocessFunc(dep) != nil) && e.cacheMap != nil {
pfn := e.preprocessFunc(dep)
fn := e.slowCacheFunc(dep)
res := dep.result
func(pfn PreprocessFunc, fn ResultBasedCacheFunc, res Result, index Index) {
dep.slowCacheReq = f.NewFuncRequest(func(ctx context.Context) (interface{}, error) {
v, err := e.op.CalcSlowCache(ctx, index, pfn, fn, res)
return v, errors.Wrap(err, "failed to compute cache key")
})
}(pfn, fn, res, dep.index)
addedNew = true
}
addedNew = e.computeCacheKeyFromDep(dep, f) || addedNew
}
return addedNew
}
func (e *edge) ensureDepsInitialized() {
if e.deps != nil {
return
}
e.depRequests = make(map[pipeReceiver]*dep)
e.deps = make([]*dep, 0, len(e.edge.Vertex.Inputs()))
for i := range e.edge.Vertex.Inputs() {
e.deps = append(e.deps, newDep(Index(i)))
}
}
func (e *edge) desiredStateDep(dep *dep, desiredState edgeStatusType, force bool) edgeStatusType {
if e.noCacheMatchPossible || force {
return edgeStatusComplete
}
if dep.state == edgeStatusInitial && desiredState > dep.state {
return edgeStatusCacheFast
}
if dep.state == edgeStatusCacheFast && desiredState > dep.state {
// wait all deps to complete cache fast before continuing with slow cache
if (e.allDepsCompletedCacheFast && len(e.keys) == 0) || len(dep.keyMap) == 0 || e.allDepsHaveKeys(true) {
if !e.skipPhase2FastCache(dep) && e.cacheMap != nil {
return edgeStatusCacheSlow
}
}
return dep.state
}
if e.cacheMap != nil && dep.state == edgeStatusCacheSlow && desiredState == edgeStatusComplete {
// if all deps have completed cache-slow or content based cache for input is available
if (len(dep.keyMap) == 0 || e.allDepsCompletedCacheSlow || (!e.skipPhase2FastCache(dep) && e.slowCacheFunc(dep) != nil)) && (len(e.cacheRecords) == 0) {
if len(dep.keyMap) == 0 || !e.skipPhase2SlowCache(dep) {
return edgeStatusComplete
}
}
return dep.state
}
if e.cacheMap != nil && dep.state == edgeStatusCacheSlow && e.slowCacheFunc(dep) != nil && desiredState == edgeStatusCacheSlow {
if len(dep.keyMap) == 0 || !e.skipPhase2SlowCache(dep) {
return edgeStatusComplete
}
return dep.state
}
return dep.state
}
func (e *edge) createOutgoingRequest(dep *dep, desiredStateDep edgeStatusType, f *pipeFactory) (addedNew bool) {
if dep.req != nil && !dep.req.Status().Completed {
if dep.req.Request().desiredState == desiredStateDep {
debugSchedulerSkipInputRequestBasedOnExistingRequest(e, dep, desiredStateDep)
return false
}
debugSchedulerCancelInputRequest(e, dep, desiredStateDep)
dep.req.Cancel()
}
debugSchedulerAddInputRequest(e, dep, desiredStateDep)
req := f.NewInputRequest(e.edge.Vertex.Inputs()[int(dep.index)], &edgeRequest{
currentState: dep.edgeState,
desiredState: desiredStateDep,
currentKeys: len(dep.keys),
})
e.depRequests[req] = dep
dep.req = req
return true
}
func (e *edge) computeCacheKeyFromDep(dep *dep, f *pipeFactory) (addedNew bool) {
if dep.state != edgeStatusComplete || dep.slowCacheReq != nil || e.cacheMap == nil {
return false
}
pfn := e.preprocessFunc(dep)
fn := e.slowCacheFunc(dep)
if pfn == nil && fn == nil {
return false
}
res := dep.result
index := dep.index
dep.slowCacheReq = f.NewFuncRequest(func(ctx context.Context) (interface{}, error) {
v, err := e.op.CalcSlowCache(ctx, index, pfn, fn, res)
return v, errors.Wrap(err, "failed to compute cache key")
})
return true
}
// execIfPossible creates a request for getting the edge result if there is
// enough state
func (e *edge) execIfPossible(f *pipeFactory) bool {