Skip to content

Commit df61d04

Browse files
authored
Fix data race in regular expression processing (#4065)
1 parent 30de144 commit df61d04

3 files changed

Lines changed: 87 additions & 6 deletions

File tree

query/common_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,8 @@ previous_model : uid @reverse .
264264
created_at : datetime @index(hour) .
265265
updated_at : datetime @index(year) .
266266
number : int @index(int) .
267+
firstName : string .
268+
lastName : string .
267269
`
268270

269271
func populateCluster() {
@@ -548,6 +550,18 @@ func populateCluster() {
548550
<202> <model> "Prius" .
549551
<202> <model> "プリウス"@jp .
550552
<202> <dgraph.type> "CarModel" .
553+
554+
# data for regexp testing
555+
_:luke <firstName> "Luke" .
556+
_:luke <lastName> "Skywalker" .
557+
_:leia <firstName> "Princess" .
558+
_:leia <lastName> "Leia" .
559+
_:han <firstName> "Han" .
560+
_:han <lastName> "Solo" .
561+
_:har <firstName> "Harrison" .
562+
_:har <lastName> "Ford" .
563+
_:ss <firstName> "Steven" .
564+
_:ss <lastName> "Spielberg" .
551565
`)
552566

553567
addGeoPointToCluster(1, "loc", []float64{1.1, 2.0})

query/query3_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2137,3 +2137,61 @@ func TestQueryMultipleTypes(t *testing.T) {
21372137
{"name":"Person", "fields":[{"name":"name", "type": "default"},
21382138
{"name":"pet", "type":"default"}]}]}}`, js)
21392139
}
2140+
2141+
func TestRegexInFilterNoDataOnRoot(t *testing.T) {
2142+
query := `
2143+
{
2144+
q(func: has(nonExistent)) @filter(regexp(make, /.*han/i)) {
2145+
uid
2146+
firstName
2147+
lastName
2148+
}
2149+
}
2150+
`
2151+
res := processQueryNoErr(t, query)
2152+
require.JSONEq(t, `{"data":{"q":[]}}`, res)
2153+
}
2154+
2155+
func TestRegexInFilterIndexedPredOnRoot(t *testing.T) {
2156+
query := `
2157+
{
2158+
q(func: regexp(name, /.*nonExistent/i)) {
2159+
uid
2160+
firstName
2161+
lastName
2162+
}
2163+
}
2164+
`
2165+
res := processQueryNoErr(t, query)
2166+
require.JSONEq(t, `{"data":{"q":[]}}`, res)
2167+
}
2168+
2169+
func TestMultiRegexInFilter(t *testing.T) {
2170+
query := `
2171+
{
2172+
q(func: has(full_name)) @filter(regexp(full_name, /.*michonne/i) OR regexp(name, /.*michonne/i)) {
2173+
expand(_all_)
2174+
}
2175+
}
2176+
`
2177+
res := processQueryNoErr(t, query)
2178+
require.JSONEq(t, `{"data": {"q": [{"name": "Michonne"}]}}`, res)
2179+
}
2180+
2181+
func TestMultiRegexInFilter2(t *testing.T) {
2182+
query := `
2183+
{
2184+
q(func: has(firstName)) @filter(regexp(firstName, /.*han/i) OR regexp(lastName, /.*han/i)) {
2185+
firstName
2186+
lastName
2187+
}
2188+
}
2189+
`
2190+
2191+
// run 20 times ensure that there is no data race
2192+
// https://github.com/dgraph-io/dgraph/issues/4030
2193+
for i := 0; i < 20; i++ {
2194+
res := processQueryNoErr(t, query)
2195+
require.JSONEq(t, `{"data": {"q": [{"firstName": "Han", "lastName":"Solo"}]}}`, res)
2196+
}
2197+
}

worker/task.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -772,8 +772,9 @@ type queryState struct {
772772
cache *posting.LocalCache
773773
}
774774

775-
func (qs *queryState) helpProcessTask(
776-
ctx context.Context, q *pb.Query, gid uint32) (*pb.Result, error) {
775+
func (qs *queryState) helpProcessTask(ctx context.Context, q *pb.Query, gid uint32) (
776+
*pb.Result, error) {
777+
777778
span := otrace.FromContext(ctx)
778779
out := new(pb.Result)
779780
attr := q.Attr
@@ -854,8 +855,6 @@ func (qs *queryState) helpProcessTask(
854855
}
855856

856857
if srcFn.fnType == regexFn {
857-
// Go through the indexkeys for the predicate and match them with
858-
// the regex matcher.
859858
span.Annotate(nil, "handleRegexFunction")
860859
if err := qs.handleRegexFunction(ctx, funcArgs{q, gid, srcFn, out}); err != nil {
861860
return nil, err
@@ -961,8 +960,18 @@ func (qs *queryState) handleRegexFunction(ctx context.Context, arg funcArgs) err
961960
// Here we determine the list of uids to match.
962961
switch {
963962
// If this is a filter eval, use the given uid list (good)
964-
case arg.q.UidList != nil && len(arg.q.UidList.Uids) != 0:
965-
uids = arg.q.UidList
963+
case arg.q.UidList != nil:
964+
// These UIDs are copied into arg.out.UidMatrix which is later updated while
965+
// processing the query. The below trick makes a copy of the list to avoid the
966+
// race conditions later. I (Aman) did a race condition tests to ensure that we
967+
// do not have more race condition in similar code in the rest of the file.
968+
// The race condition was found only here because in filter condition, even when
969+
// predicates do not have indexes, we allow regexp queries (for example, we do
970+
// not support eq/gt/lt/le in @filter, see #4077), and this was new code that
971+
// was added just to support the aforementioned case, the race condition is only
972+
// in this part of the code.
973+
uids = &pb.List{}
974+
uids.Uids = append(arg.q.UidList.Uids[:0:0], arg.q.UidList.Uids...)
966975

967976
// Prefer to use an index (fast)
968977
case useIndex:

0 commit comments

Comments
 (0)