Skip to content

Simple Fix: Set channel size to 1 for datalog Run goroutine #166

@txross1993

Description

@txross1993

The following goroutine is leaky by design and can be fixed by simply making the channel size 1.

func (w *World) Run(syms *SymbolTable) error {

func (w *World) Run(syms *SymbolTable) error {
	done := make(chan error) // Unbuffered channel makes the goroutine leaky
	ctx, cancel := context.WithTimeout(context.Background(), w.runLimits.maxDuration)
	defer cancel()

	go func() {
		for i := 0; i < w.runLimits.maxIterations; i++ {
			select {
			case <-ctx.Done():
				return
			default:
				var newFacts FactSet
				for _, r := range w.rules {
					select {
					case <-ctx.Done():
						return
					default:
						if err := r.Apply(w.facts, &newFacts, syms); err != nil {
							done <- err
							return
						}
					}
				}

				prevCount := len(*w.facts)
				w.facts.InsertAll([]Fact(newFacts))

				newCount := len(*w.facts)
				if newCount >= w.runLimits.maxFacts {
					done <- ErrWorldRunLimitMaxFacts  // Can block and run goroutine forever if context.Done() returns first
					return
				}

				// last iteration did not generate any new facts, so we can stop here
				if newCount == prevCount {
					done <- nil // Can block and run goroutine forever if context.Done() returns first
					return
				}
			}
		}
		done <- ErrWorldRunLimitMaxIterations
	}()

	select {
	case <-ctx.Done():
		return ErrWorldRunLimitTimeout
	case err := <-done:
		return err
	}
}

The simple fix is to set the channel size to 1:

done := make(chan error, 1) 

Playground can demonstrate the issue: https://goplay.tools/snippet/5j4Bkegd1dj
(change the channel size from unbuffered to 1 to see the result)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions