Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

concurrency issues in types.Compare() #3793

Closed
markushinz opened this issue Sep 15, 2021 · 10 comments · Fixed by #3801 or #3805
Closed

concurrency issues in types.Compare() #3793

markushinz opened this issue Sep 15, 2021 · 10 comments · Fixed by #3801 or #3805
Assignees
Labels

Comments

@markushinz
Copy link

markushinz commented Sep 15, 2021

Expected Behavior

I can concurrently create multiple compilers and compile modules. More concrete, I can run a function as the following at the same time in multiple go routines.

func getCompiler(modules map[string]*ast.Module) (*ast.Compiler, error) {
	compiler := ast.NewCompiler()
	compiler.Compile(modules)
	if compiler.Failed() {
		return nil, compiler.Errors
	}
	return compiler, nil
}

Actual Behavior

The above code shows a race condition:

WARNING: DATA RACE
Read at 0x00c0001d8930 by goroutine 118:
  github.com/open-policy-agent/opa/types.typeSlice.Less()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/types/types.go:867 +0x70
  github.com/open-policy-agent/opa/types.(*typeSlice).Less()
      <autogenerated>:1 +0x24
  sort.insertionSort()
      /opt/homebrew/Cellar/go/1.17/libexec/src/sort/sort.go:40 +0xd4
  sort.quickSort()
      /opt/homebrew/Cellar/go/1.17/libexec/src/sort/sort.go:222 +0x1dc
  sort.Sort()
      /opt/homebrew/Cellar/go/1.17/libexec/src/sort/sort.go:231 +0x7c
  github.com/open-policy-agent/opa/types.Compare()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/types/types.go:628 +0xd44
  github.com/open-policy-agent/opa/ast.unify1()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/check.go:450 +0x17c
  github.com/open-policy-agent/opa/ast.(*typeChecker).checkExprBuiltin()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/check.go:336 +0x9cc
  github.com/open-policy-agent/opa/ast.(*typeChecker).checkExpr()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/check.go:287 +0x218
  github.com/open-policy-agent/opa/ast.(*typeChecker).CheckBody.func1()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/check.go:119 +0x3ac
  github.com/open-policy-agent/opa/ast.WalkExprs.func1()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/visit.go:208 +0x54
  github.com/open-policy-agent/opa/ast.(*GenericVisitor).Walk()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/visit.go:273 +0x5c
  github.com/open-policy-agent/opa/ast.(*GenericVisitor).Walk()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/visit.go:314 +0xe08
  github.com/open-policy-agent/opa/ast.WalkExprs()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/visit.go:212 +0x64
  github.com/open-policy-agent/opa/ast.(*typeChecker).CheckBody()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/check.go:102 +0xb8
  github.com/open-policy-agent/opa/ast.(*typeChecker).checkClosures.func1()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/check.go:161 +0x810
  github.com/open-policy-agent/opa/ast.WalkClosures.func1()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/visit.go:160 +0xc8
  github.com/open-policy-agent/opa/ast.(*GenericVisitor).Walk()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/visit.go:273 +0x5c
  github.com/open-policy-agent/opa/ast.(*GenericVisitor).Walk()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/visit.go:338 +0x70c
  github.com/open-policy-agent/opa/ast.(*GenericVisitor).Walk()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/visit.go:326 +0xef8
  github.com/open-policy-agent/opa/ast.WalkClosures()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/visit.go:164 +0x64
  github.com/open-policy-agent/opa/ast.(*typeChecker).checkClosures()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/check.go:158 +0x78
  github.com/open-policy-agent/opa/ast.(*typeChecker).CheckBody.func1()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/check.go:104 +0x60
  github.com/open-policy-agent/opa/ast.WalkExprs.func1()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/visit.go:208 +0x54
  github.com/open-policy-agent/opa/ast.(*GenericVisitor).Walk()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/visit.go:273 +0x5c
  github.com/open-policy-agent/opa/ast.(*GenericVisitor).Walk()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/visit.go:314 +0xe08
  github.com/open-policy-agent/opa/ast.WalkExprs()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/visit.go:212 +0x64
  github.com/open-policy-agent/opa/ast.(*typeChecker).CheckBody()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/check.go:102 +0xb8
  github.com/open-policy-agent/opa/ast.(*typeChecker).checkRule()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/check.go:209 +0x390
  github.com/open-policy-agent/opa/ast.(*typeChecker).CheckTypes()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/check.go:150 +0x1d4
  github.com/open-policy-agent/opa/ast.(*Compiler).checkTypes()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/compile.go:1067 +0x3d0
  github.com/open-policy-agent/opa/ast.(*Compiler).checkTypes-fm()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/compile.go:1060 +0x40
  github.com/open-policy-agent/opa/ast.(*Compiler).runStage()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/compile.go:1088 +0x104
  github.com/open-policy-agent/opa/ast.(*Compiler).compile()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/compile.go:1108 +0x124
  github.com/open-policy-agent/opa/ast.(*Compiler).Compile()
      /Users/markushinz/go/pkg/mod/github.com/open-policy-agent/[email protected]/ast/compile.go:365 +0x328

[truncated]

Steps to Reproduce the Problem

  • OPA version: v0.32.0
  • Go version: 1.17

To reproduce one can use a test such as the following und run it with go test -race:

func TestGetCompiler(t *testing.T) {
	modules, err := getModules(regoCode) // returns map[string]*ast.Module
	assert.NoError(t, err)
	for i := 0; i < 10; i++ {
		t.Run(fmt.Sprintf("Iteration %v", i), func(t *testing.T) {
			t.Parallel()
			_, err = getCompiler(modules)
			assert.NoError(t, err)
		})
	}
}

Additional Info

The stack trace shows that types.Compare() causes the issue. It looks like it uses sorting algorithms that directly operate on slices. It uses

var A = NewAny()
which should not be accessed/modified by multiple go routines. If types.A is the only issue here, a solution would be to always use

opa/types/types.go

Lines 374 to 380 in 4747e22

func NewAny(of ...Type) Any {
sl := make(Any, len(of))
for i := range sl {
sl[i] = of[i]
}
return sl
}
instead.

@tsandall
Copy link
Member

@markushinz thanks for filing this.

Is it possible to share the policy that you used in your test case? I haven't bee able to reproduce the race with large -count values:

(fix-3793)[*]torin:~/go/src/github.com/open-policy-agent/opa$ go test -race ./ast -run TestCompileModules -count=1000
ok      github.com/open-policy-agent/opa/ast    3.860s
(fix-3793)[*]torin:~/go/src/github.com/open-policy-agent/opa$ go version
go version go1.17.1 linux/amd64

Test case:

package ast

import (
	"fmt"
	"testing"
)

func getCompiler(modules map[string]*Module) (*Compiler, error) {
	compiler := NewCompiler()
	compiler.Compile(modules)
	if compiler.Failed() {
		return nil, compiler.Errors
	}
	return compiler, nil
}

func getModules(s string) (map[string]*Module, error) {
	module, err := ParseModule("test.rego", s)
	if err != nil {
		return nil, err
	}
	return map[string]*Module{"test.rego": module}, nil
}

func TestCompileModules(t *testing.T) {
	modules, err := getModules(`package test

	p { input.bar == input.foo }`)
	if err != nil {
		t.Fatal(err)
	}
	for i := 0; i < 10; i++ {
		t.Run(fmt.Sprint(i), func(t *testing.T) {
			t.Parallel()
			_, err := getCompiler(modules)
			if err != nil {
				t.Fatal(err)
			}
		})
	}
}

If you can include the full stack trace, that might also be helpful.

@markushinz
Copy link
Author

Hi @tsandall,

thanks for the quick reply. Here's a concrete example which causes the issue. Just replace fileA and fileB with the contents from GitHub.

func moduleName(m *ast.Module) string {
	p := strings.Fields(m.Package.String())
	if len(p) != 2 {
		return ""
	}
	return p[len(p)-1]
}

func Test_GetCompiler(t *testing.T) {
	rules := map[string]string{
		"fileA": "https://github.com/open-policy-agent/library/blob/master/terraform/library.rego", //
		"fileB": "https://github.com/open-policy-agent/library/blob/master/terraform/input.rego",
	}
	modules := make(map[string]*ast.Module)
	for _, rule := range rules {
		module, err := ast.ParseModule("", rule)
		assert.NoError(t, err)
		modules[moduleName(module)] = module
	}

	for i := 0; i < 10; i++ {
		t.Run(fmt.Sprintf("compiler_%v", i), func(t *testing.T) {
			t.Parallel()
			compiler := ast.NewCompiler()
			compiler.Compile(modules)
			if compiler.Failed() {
				assert.NoError(t, compiler.Errors)
			}
			_, err := getCompiler(modules)
			assert.NoError(t, err)
		})
	}
}

@tsandall
Copy link
Member

@markushinz thanks. When I run that test case w/ the rego files inlined, I get a data race due to comments on the module not being copied: https://gist.github.com/tsandall/fc863b7a6aca1bfc6940f014a37e1a76. I'll submit a PR with a fix for that. I'm not able to reproduce the original stack trace you shared above. Can you share a stack trace from the full example you provided in your last comment?

@tsandall tsandall added the bug label Sep 16, 2021
@tsandall tsandall self-assigned this Sep 16, 2021
tsandall added a commit to tsandall/opa that referenced this issue Sep 16, 2021
Previously comments were not being deep copied which could lead to
data races from the transfomer and other places.

Fixes open-policy-agent#3793

Signed-off-by: Torin Sandall <[email protected]>
@tsandall
Copy link
Member

@markushinz if you could try running the test locally in your environment w/ my branch linked in that PR it would be helpful because I'm not able to reproduce the original data race that you showed above. If you post the full stack trace from the race detector that would be super helpful.

srenatus pushed a commit that referenced this issue Sep 16, 2021
Previously comments were not being deep copied which could lead to
data races from the transfomer and other places.

Fixes #3793

Signed-off-by: Torin Sandall <[email protected]>
@srenatus
Copy link
Contributor

Merging the PR closed this. I suppose #3793 (comment) would still be helpful, though @markushinz

@markushinz
Copy link
Author

@tsandall I did run the tests against your branch and it did not solve the problem. Luckily, I was able to except the rego file which causes the issues. I provided both the full go code (including rego) and the full output of go test -race in the following gists:

https://gist.github.com/markushinz/a46a48bf74eb8a7176c46502b89bd81c
https://gist.github.com/markushinz/e707e75036b389fcf2b4eaa72504f452

@srenatus srenatus reopened this Sep 17, 2021
tsandall added a commit that referenced this issue Sep 17, 2021
This changes updates the implementation of the any type to sort
elements during construction. This way the Compare() function does not
have to sort elements before recursing (which can result in data races
if global type instances from the built-in function declarations or
elsewhere are compared.)

Fixes #3793

Signed-off-by: Torin Sandall <[email protected]>
@tsandall
Copy link
Member

@markushinz thanks! that was super helpful. I've put up another PR to address the race. 🙏

@markushinz
Copy link
Author

@tsandall thanks a lot for addressing this so quickly. Looking forward to testing it once it's merged

srenatus pushed a commit that referenced this issue Sep 20, 2021
This changes updates the implementation of the any type to sort
elements during construction. This way the Compare() function does not
have to sort elements before recursing (which can result in data races
if global type instances from the built-in function declarations or
elsewhere are compared.)

Fixes #3793

Signed-off-by: Torin Sandall <[email protected]>
srenatus added a commit that referenced this issue Sep 20, 2021
* types: Sort any elements during construction

This changes updates the implementation of the any type to sort
elements during construction. This way the Compare() function does not
have to sort elements before recursing (which can result in data races
if global type instances from the built-in function declarations or
elsewhere are compared.)

Fixes #3793

* capabilities.json: changed ordering
* internal/presentation: fix json error output

Co-authored-by: Torin Sandall <[email protected]>
Co-authored-by: Stephan Renatus <[email protected]>
Signed-off-by: Torin Sandall <[email protected]>
@srenatus
Copy link
Contributor

✔️ @markushinz It's been merged. Please re-open if you find a chance to test this and the issue persists.

@markushinz
Copy link
Author

@srenatus @tsandall awesome all race conditions are gone! Thank you so much!

dolevf pushed a commit to dolevf/opa that referenced this issue Nov 4, 2021
)

Previously comments were not being deep copied which could lead to
data races from the transfomer and other places.

Fixes open-policy-agent#3793

Signed-off-by: Torin Sandall <[email protected]>
Signed-off-by: Dolev Farhi <[email protected]>
dolevf pushed a commit to dolevf/opa that referenced this issue Nov 4, 2021
* types: Sort any elements during construction

This changes updates the implementation of the any type to sort
elements during construction. This way the Compare() function does not
have to sort elements before recursing (which can result in data races
if global type instances from the built-in function declarations or
elsewhere are compared.)

Fixes open-policy-agent#3793

* capabilities.json: changed ordering
* internal/presentation: fix json error output

Co-authored-by: Torin Sandall <[email protected]>
Co-authored-by: Stephan Renatus <[email protected]>
Signed-off-by: Torin Sandall <[email protected]>
Signed-off-by: Dolev Farhi <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Archived in project
3 participants