src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

task.go (2943B)


      1 // Copyright 2024 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package ssa
      6 
      7 import (
      8 	"sync/atomic"
      9 )
     10 
     11 // Each task has two states: it is initially "active",
     12 // and transitions to "done".
     13 //
     14 // tasks form a directed graph. An edge from x to y (with y in x.edges)
     15 // indicates that the task x waits on the task y to be done.
     16 // Cycles are permitted.
     17 //
     18 // Calling x.wait() blocks the calling goroutine until task x,
     19 // and all the tasks transitively reachable from x are done.
     20 //
     21 // The nil *task is always considered done.
     22 type task struct {
     23 	done       chan unit      // close when the task is done.
     24 	edges      map[*task]unit // set of predecessors of this task.
     25 	transitive atomic.Bool    // true once it is known all predecessors are done.
     26 }
     27 
     28 func (x *task) isTransitivelyDone() bool { return x == nil || x.transitive.Load() }
     29 
     30 // addEdge creates an edge from x to y, indicating that
     31 // x.wait() will not return before y is done.
     32 // All calls to x.addEdge(...) should happen before x.markDone().
     33 func (x *task) addEdge(y *task) {
     34 	if x == y || y.isTransitivelyDone() {
     35 		return // no work remaining
     36 	}
     37 
     38 	// heuristic done check
     39 	select {
     40 	case <-x.done:
     41 		panic("cannot add an edge to a done task")
     42 	default:
     43 	}
     44 
     45 	if x.edges == nil {
     46 		x.edges = make(map[*task]unit)
     47 	}
     48 	x.edges[y] = unit{}
     49 }
     50 
     51 // markDone changes the task's state to markDone.
     52 func (x *task) markDone() {
     53 	if x != nil {
     54 		close(x.done)
     55 	}
     56 }
     57 
     58 // wait blocks until x and all the tasks it can reach through edges are done.
     59 func (x *task) wait() {
     60 	if x.isTransitivelyDone() {
     61 		return // already known to be done. Skip allocations.
     62 	}
     63 
     64 	// Use BFS to wait on u.done to be closed, for all u transitively
     65 	// reachable from x via edges.
     66 	//
     67 	// This work can be repeated by multiple workers doing wait().
     68 	//
     69 	// Note: Tarjan's SCC algorithm is able to mark SCCs as transitively done
     70 	// as soon as the SCC has been visited. This is theoretically faster, but is
     71 	// a more complex algorithm. Until we have evidence, we need the more complex
     72 	// algorithm, the simpler algorithm BFS is implemented.
     73 	//
     74 	// In Go 1.23, ssa/TestStdlib reaches <=3 *tasks per wait() in most schedules
     75 	// On some schedules, there is a cycle building net/http and internal/trace/testtrace
     76 	// due to slices functions.
     77 	work := []*task{x}
     78 	enqueued := map[*task]unit{x: {}}
     79 	for i := 0; i < len(work); i++ {
     80 		u := work[i]
     81 		if u.isTransitivelyDone() { // already transitively done
     82 			work[i] = nil
     83 			continue
     84 		}
     85 		<-u.done // wait for u to be marked done.
     86 
     87 		for v := range u.edges {
     88 			if _, ok := enqueued[v]; !ok {
     89 				enqueued[v] = unit{}
     90 				work = append(work, v)
     91 			}
     92 		}
     93 	}
     94 
     95 	// work is transitively closed over dependencies.
     96 	// u in work is done (or transitively done and skipped).
     97 	// u is transitively done.
     98 	for _, u := range work {
     99 		if u != nil {
    100 			x.transitive.Store(true)
    101 		}
    102 	}
    103 }