8000 Fix DEVSUP-799 by jsteemann · Pull Request #14401 · arangodb/arangodb · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
devel
-----

* Fixed DEVSUP-799: unique vertex getter may point to invalid memory after being
resetted, resulting in undefined behavior for traversals returning unique
vertices from inner FOR loops.

* For cluster AQL queries, let the coordinator determine the query id to be
used on DB servers. This allows the coordinator to roll back AQL query setup
requests via the query id. Previously, the DB servers each generated a local
Expand Down
8 changes: 6 additions & 2 deletions arangod/Cluster/ClusterTraverser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,14 @@ void ClusterTraverser::setStartVertex(std::string const& vid) {
}

void ClusterTraverser::clear() {
traverserCache()->clear();

_vertexGetter->clear();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh this is a good catch, we have only cleared on reset, not on clear.

_vertices.clear();
_verticesToFetch.clear();

#ifdef ARANGODB_ENABLE_MAINTAINER_MODE
TRI_ASSERT(!_vertexGetter->pointsIntoTraverserCache());
#endif
traverserCache()->clear();
}

bool ClusterTraverser::getVertex(VPackSlice edge, arangodb::traverser::EnumeratedPath& path) {
Expand Down
8 changes: 7 additions & 1 deletion arangod/Graph/SingleServerTraverser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,13 @@ void SingleServerTraverser::setStartVertex(std::string const& vid) {
_done = false;
}

void SingleServerTraverser::clear() { traverserCache()->clear(); }
void SingleServerTraverser::clear() {
_vertexGetter->clear();
#ifdef ARANGODB_ENABLE_MAINTAINER_MODE
TRI_ASSERT(!_vertexGetter->pointsIntoTraverserCache());
#endif
traverserCache()->clear();
}

bool SingleServerTraverser::getVertex(VPackSlice edge, arangodb::traverser::EnumeratedPath& path) {
return _vertexGetter->getVertex(edge, path);
Expand Down
22 changes: 22 additions & 0 deletions arangod/Graph/Traverser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ bool Traverser::VertexGetter::getSingleVertex(arangodb::velocypack::Slice edge,

void Traverser::VertexGetter::reset(arangodb::velocypack::StringRef const&) {}

// nothing to do
void Traverser::VertexGetter::clear() {}

#ifdef ARANGODB_ENABLE_MAINTAINER_MODE
bool Traverser::VertexGetter::pointsIntoTraverserCache() const noexcept {
return false;
}
#endif

bool Traverser::VertexGetter::getVertex(arangodb::velocypack::StringRef vertex, size_t depth) {
return _traverser->vertexMatchesConditions(vertex, depth);
}
Expand Down Expand Up @@ -136,6 +145,19 @@ bool Traverser::UniqueVertexGetter::getSingleVertex(arangodb::velocypack::Slice
return true;
}

void Traverser::UniqueVertexGetter::clear() {
// we must make sure that we clear _returnedVertices, not only for
// correctness, but also because it may point into memory that is
// going to be freed after this call.
_returnedVertices.clear();
}

#ifdef ARANGODB_ENABLE_MAINTAINER_MODE
bool Traverser::UniqueVertexGetter::pointsIntoTraverserCache() const noexcept {
return !_returnedVertices.empty();
}
#endif

void Traverser::UniqueVertexGetter::reset(arangodb::velocypack::StringRef const& startVertex) {
_returnedVertices.clear();
// The startVertex always counts as visited!
Expand Down
12 changes: 12 additions & 0 deletions arangod/Graph/Traverser.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ class Traverser {

virtual void reset(arangodb::velocypack::StringRef const&);

virtual void clear();

#ifdef ARANGODB_ENABLE_MAINTAINER_MODE
virtual bool pointsIntoTraverserCache() const noexcept;
#endif

protected:
Traverser* _traverser;
};
Expand All @@ -158,6 +164,12 @@ class Traverser {

void reset(arangodb::velocypack::StringRef const&) override;

void clear() override;

#ifdef ARANGODB_ENABLE_MAINTAINER_MODE
bool pointsIntoTraverserCache() const noexcept override;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this.
This will help to track down those issues!

#endif

private:
std::unordered_set<arangodb::velocypack::StringRef> _returnedVertices;
};
Expand Down
2 changes: 1 addition & 1 deletion arangod/Graph/TraverserCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ TraverserCache::~TraverserCache() {
void TraverserCache::clear() {
_query.resourceMonitor().decreaseMemoryUsage(_persistedStrings.size() * ::costPerPersistedString);

_stringHeap.clear();
_persistedStrings.clear();
_mmdr.clear();
_stringHeap.clear();
}

VPackSlice TraverserCache::lookupToken(EdgeDocumentToken const& idToken) {
Expand Down
38 changes: 38 additions & 0 deletions tests/js/server/aql/aql-graph-traverser.js
Original file line number Diff line number Diff line change
Expand Up @@ -3301,6 +3301,44 @@ function subQuerySuite() {
cleanup();
},

testUniqueVertexGetterMemoryIssue: function () {
// test case: UniqueVertexGetter keeps track of which vertices
// were already visited. the vertex id tracked pointed to memory
// which was allocated only temporarily and that became invalid
// once TraverserCache::clear() got called.
let query = `WITH ${vn} FOR i IN 1..3 FOR v, e, p IN 1..3 OUTBOUND '${vertex.A}' ${en} OPTIONS { uniqueVertices: 'global', bfs: true } SORT v._key RETURN v._key`;
let result = db._query(query).toArray();
assertEqual([
"B", "B", "B",
"B1", "B1", "B1",
"B2", "B2", "B2",
"B3", "B3", "B3",
"B4", "B4", "B4",
"B5", "B5", "B5",
"C", "C", "C",
"C1", "C1", "C1",
"C2", "C2", "C2",
"C3", "C3", "C3",
"C4", "C4", "C4",
"C5", "C5", "C5",
"D", "D", "D",
"D1", "D1", "D1",
"D2", "D2", "D2",
"D3", "D3", "D3",
"D4", "D4", "D4",
"D5", "D5", "D5"
], result);

// while we are here, try a different query as well
query = `WITH ${vn} FOR i IN 1..3 LET sub = (FOR v, e, p IN 1..3 OUTBOUND '${vertex.A}' ${en} OPTIONS { uniqueVertices: 'global', bfs: true } SORT v._key RETURN v._key) RETURN sub`;
result = db._query(query).toArray();
assertEqual([
[ "B", "B1", "B2", "B3", "B4", "B5", "C", "C1", "C2", "C3", "C4", "C5", "D", "D1", "D2", "D3", "D4", "D5" ],
[ "B", "B1", "B2", "B3", "B4", "B5", "C", "C1", "C2", "C3", "C4", "C5", "D", "D1", "D2", "D3", "D4", "D5" ],
[ "B", "B1", "B2", "B3", "B4", "B5", "C", "C1", "C2", "C3", "C4", "C5", "D", "D1", "D2", "D3", "D4", "D5" ]
], result);
},

// The test is that the traversal in subquery has more then LIMIT many
// results. In case of a bug the cursor of the traversal is reused for the second
// iteration as well and does not reset.
Expand Down
0