From d63f47e8403d6ffc1fd6f81ff60e17b3e6299a11 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Fri, 20 Mar 2015 23:05:41 +0100 Subject: [PATCH] added possibility to kill JS traversals --- arangod/Aql/Executor.cpp | 26 +++++--- arangod/Aql/V8Expression.cpp | 51 ++++++++------- arangod/V8Server/ApplicationV8.cpp | 2 + arangod/V8Server/v8-vocbase.cpp | 16 +++++ .../modules/org/arangodb/graph/traversal.js | 34 +++++++++- .../modules/org/arangodb/graph/traversal.js | 34 +++++++++- js/server/modules/org/arangodb/aql.js | 62 +++++++------------ 7 files changed, 152 insertions(+), 73 deletions(-) diff --git a/arangod/Aql/Executor.cpp b/arangod/Aql/Executor.cpp index 24e6cf5c22..59382773a5 100644 --- a/arangod/Aql/Executor.cpp +++ b/arangod/Aql/Executor.cpp @@ -316,18 +316,27 @@ TRI_json_t* Executor::executeExpression (Query* query, TRI_ASSERT(query != nullptr); TRI_GET_GLOBALS(); + v8::Handle result; auto old = v8g->_query; - v8g->_query = static_cast(query); - TRI_ASSERT(v8g->_query != nullptr); + + try { + v8g->_query = static_cast(query); + TRI_ASSERT(v8g->_query != nullptr); - // execute the function - v8::Handle args; - v8::Handle result = v8::Handle::Cast(func)->Call(v8::Object::New(isolate), 0, &args); + // execute the function + v8::Handle args; + result = v8::Handle::Cast(func)->Call(v8::Object::New(isolate), 0, &args); - v8g->_query = old; + v8g->_query = old; - // exit if execution raised an error - HandleV8Error(tryCatch, result); + // exit if execution raised an error + HandleV8Error(tryCatch, result); + } + catch (...) { + v8g->_query = old; + throw; + } + if (result->IsUndefined()) { // undefined => null @@ -364,6 +373,7 @@ Function const* Executor::getFunctionByName (std::string const& name) { void Executor::HandleV8Error (v8::TryCatch& tryCatch, v8::Handle& result) { ISOLATE; + if (tryCatch.HasCaught()) { // caught a V8 exception if (! tryCatch.CanContinue()) { diff --git a/arangod/Aql/V8Expression.cpp b/arangod/Aql/V8Expression.cpp index 00ca5358a3..9dc6bbc18c 100644 --- a/arangod/Aql/V8Expression.cpp +++ b/arangod/Aql/V8Expression.cpp @@ -110,49 +110,52 @@ AqlValue V8Expression::execute (v8::Isolate* isolate, TRI_ASSERT(query != nullptr); TRI_GET_GLOBALS(); + + v8::Handle result; + auto old = v8g->_query; - v8g->_query = static_cast(query); - TRI_ASSERT(v8g->_query != nullptr); - // set function arguments - v8::Handle args[] = { values }; + try { + v8g->_query = static_cast(query); + TRI_ASSERT(v8g->_query != nullptr); - // execute the function - v8::TryCatch tryCatch; + // set function arguments + v8::Handle args[] = { values }; - auto func = v8::Local::New(isolate, _func); - v8::Handle result = func->Call(func, 1, args); + // execute the function + v8::TryCatch tryCatch; - v8g->_query = old; + auto func = v8::Local::New(isolate, _func); + result = func->Call(func, 1, args); - Executor::HandleV8Error(tryCatch, result); + v8g->_query = old; + + Executor::HandleV8Error(tryCatch, result); + } + catch (...) { + v8g->_query = old; + throw; + } // no exception was thrown if we get here - TRI_json_t* json = nullptr; + std::unique_ptr json; if (result->IsUndefined()) { // expression does not have any (defined) value. replace with null - json = TRI_CreateNullJson(TRI_UNKNOWN_MEM_ZONE); + json.reset(TRI_CreateNullJson(TRI_UNKNOWN_MEM_ZONE)); } else { // expression had a result. convert it to JSON - json = TRI_ObjectToJson(isolate, result); - // TODO: json = TRI_SimplifiedObjectToJson(isolate, result); + json.reset(TRI_ObjectToJson(isolate, result)); } - if (json == nullptr) { + if (json.get() == nullptr) { THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } - try { - auto j = new triagens::basics::Json(TRI_UNKNOWN_MEM_ZONE, json); - return AqlValue(j); - } - catch (...) { - // prevent memleak - TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); - throw; - } + auto j = new triagens::basics::Json(TRI_UNKNOWN_MEM_ZONE, json.get()); + json.release(); + return AqlValue(j); } // ----------------------------------------------------------------------------- diff --git a/arangod/V8Server/ApplicationV8.cpp b/arangod/V8Server/ApplicationV8.cpp index 23c0739c9e..d49ef5acb8 100644 --- a/arangod/V8Server/ApplicationV8.cpp +++ b/arangod/V8Server/ApplicationV8.cpp @@ -380,6 +380,7 @@ ApplicationV8::V8Context* ApplicationV8::enterContext (std::string const& name, v8g->_vocbase = vocbase; v8g->_allowUseDatabase = allowUseDatabase; + v8g->_query = nullptr; LOG_TRACE("entering V8 context %d", (int) context->_id); context->handleGlobalContextMethods(); @@ -415,6 +416,7 @@ void ApplicationV8::exitContext (V8Context* context) { // check for cancelation requests bool const canceled = v8g->_canceled; v8g->_canceled = false; + v8g->_query = nullptr; // exit the context { diff --git a/arangod/V8Server/v8-vocbase.cpp b/arangod/V8Server/v8-vocbase.cpp index 13984e461a..9abf35ce2d 100644 --- a/arangod/V8Server/v8-vocbase.cpp +++ b/arangod/V8Server/v8-vocbase.cpp @@ -1599,6 +1599,21 @@ static void JS_QueriesKillAql (const v8::FunctionCallbackInfo& args) TRI_V8_THROW_EXCEPTION(res); } +//////////////////////////////////////////////////////////////////////////////// +/// @brief whether or not a query is killed +//////////////////////////////////////////////////////////////////////////////// + +static void JS_QueryIsKilledAql (const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + v8::HandleScope scope(isolate); + + TRI_GET_GLOBALS(); + if (v8g->_query != nullptr && static_cast(v8g->_query)->killed()) { + TRI_V8_RETURN_TRUE(); + } + TRI_V8_RETURN_FALSE(); +} + //////////////////////////////////////////////////////////////////////////////// /// @brief sleeps and checks for query abortion in between //////////////////////////////////////////////////////////////////////////////// @@ -2913,6 +2928,7 @@ void TRI_InitV8VocBridge (v8::Isolate* isolate, TRI_AddGlobalFunctionVocbase(isolate, context, TRI_V8_ASCII_STRING("AQL_QUERIES_SLOW"), JS_QueriesSlowAql, true); TRI_AddGlobalFunctionVocbase(isolate, context, TRI_V8_ASCII_STRING("AQL_QUERIES_KILL"), JS_QueriesKillAql, true); TRI_AddGlobalFunctionVocbase(isolate, context, TRI_V8_ASCII_STRING("AQL_QUERY_SLEEP"), JS_QuerySleepAql, true); + TRI_AddGlobalFunctionVocbase(isolate, context, TRI_V8_ASCII_STRING("AQL_QUERY_IS_KILLED"), JS_QueryIsKilledAql, true); TRI_InitV8replication(isolate, context, server, vocbase, loader, threadNumber, v8g); diff --git a/js/apps/system/_admin/aardvark/APP/frontend/js/modules/org/arangodb/graph/traversal.js b/js/apps/system/_admin/aardvark/APP/frontend/js/modules/org/arangodb/graph/traversal.js index 25451e0588..27ed1f9b6c 100644 --- a/js/apps/system/_admin/aardvark/APP/frontend/js/modules/org/arangodb/graph/traversal.js +++ b/js/apps/system/_admin/aardvark/APP/frontend/js/modules/org/arangodb/graph/traversal.js @@ -1,6 +1,6 @@ module.define("org/arangodb/graph/traversal", function(exports, module) { /*jshint strict: false, unused: false */ -/*global require, exports, ArangoClusterComm */ +/*global require, exports, ArangoClusterComm, AQL_QUERY_IS_KILLED */ //////////////////////////////////////////////////////////////////////////////// /// @brief Traversal "classes" @@ -45,6 +45,30 @@ var ArangoTraverser; // --SECTION-- helper functions // ----------------------------------------------------------------------------- +//////////////////////////////////////////////////////////////////////////////// +/// @brief whether or not the query was aborted +/// use the AQL_QUERY_IS_KILLED function on the server side, and a dummy +/// function otherwise (ArangoShell etc.) +//////////////////////////////////////////////////////////////////////////////// + +var throwIfAborted = function () { +}; + +try { + if (typeof AQL_QUERY_IS_KILLED === "function") { + throwIfAborted = function () { + if (AQL_QUERY_IS_KILLED()) { + var err = new ArangoError(); + err.errorNum = arangodb.errors.ERROR_QUERY_KILLED.code; + err.errorMessage = arangodb.errors.ERROR_QUERY_KILLED.message; + throw err; + } + }; + } +} +catch (err) { +} + //////////////////////////////////////////////////////////////////////////////// /// @brief clone any object //////////////////////////////////////////////////////////////////////////////// @@ -900,6 +924,8 @@ function breadthFirstSearch () { throw err; } + throwIfAborted(); + if (current.visit === null || current.visit === undefined) { current.visit = false; @@ -1009,6 +1035,8 @@ function depthFirstSearch () { err.errorMessage = arangodb.errors.ERROR_GRAPH_TOO_MANY_ITERATIONS.message; throw err; } + + throwIfAborted(); // peek at the top of the stack var current = toVisit[toVisit.length - 1]; @@ -1153,6 +1181,8 @@ function dijkstraSearch () { err.errorMessage = arangodb.errors.ERROR_GRAPH_TOO_MANY_ITERATIONS.message; throw err; } + + throwIfAborted(); var currentNode = heap.pop(); var i, n; @@ -1291,6 +1321,8 @@ function astarSearch () { throw err; } + throwIfAborted(); + var currentNode = heap.pop(); var i, n; diff --git a/js/common/modules/org/arangodb/graph/traversal.js b/js/common/modules/org/arangodb/graph/traversal.js index 2eecb99dc2..af8494ed9c 100644 --- a/js/common/modules/org/arangodb/graph/traversal.js +++ b/js/common/modules/org/arangodb/graph/traversal.js @@ -1,5 +1,5 @@ /*jshint strict: false, unused: false */ -/*global require, exports, ArangoClusterComm */ +/*global require, exports, ArangoClusterComm, AQL_QUERY_IS_KILLED */ //////////////////////////////////////////////////////////////////////////////// /// @brief Traversal "classes" @@ -44,6 +44,30 @@ var ArangoTraverser; // --SECTION-- helper functions // ----------------------------------------------------------------------------- +//////////////////////////////////////////////////////////////////////////////// +/// @brief whether or not the query was aborted +/// use the AQL_QUERY_IS_KILLED function on the server side, and a dummy +/// function otherwise (ArangoShell etc.) +//////////////////////////////////////////////////////////////////////////////// + +var throwIfAborted = function () { +}; + +try { + if (typeof AQL_QUERY_IS_KILLED === "function") { + throwIfAborted = function () { + if (AQL_QUERY_IS_KILLED()) { + var err = new ArangoError(); + err.errorNum = arangodb.errors.ERROR_QUERY_KILLED.code; + err.errorMessage = arangodb.errors.ERROR_QUERY_KILLED.message; + throw err; + } + }; + } +} +catch (err) { +} + //////////////////////////////////////////////////////////////////////////////// /// @brief clone any object //////////////////////////////////////////////////////////////////////////////// @@ -899,6 +923,8 @@ function breadthFirstSearch () { throw err; } + throwIfAborted(); + if (current.visit === null || current.visit === undefined) { current.visit = false; @@ -1008,6 +1034,8 @@ function depthFirstSearch () { err.errorMessage = arangodb.errors.ERROR_GRAPH_TOO_MANY_ITERATIONS.message; throw err; } + + throwIfAborted(); // peek at the top of the stack var current = toVisit[toVisit.length - 1]; @@ -1152,6 +1180,8 @@ function dijkstraSearch () { err.errorMessage = arangodb.errors.ERROR_GRAPH_TOO_MANY_ITERATIONS.message; throw err; } + + throwIfAborted(); var currentNode = heap.pop(); var i, n; @@ -1290,6 +1320,8 @@ function astarSearch () { throw err; } + throwIfAborted(); + var currentNode = heap.pop(); var i, n; diff --git a/js/server/modules/org/arangodb/aql.js b/js/server/modules/org/arangodb/aql.js index 92018bc351..210a6f2d1a 100644 --- a/js/server/modules/org/arangodb/aql.js +++ b/js/server/modules/org/arangodb/aql.js @@ -5289,7 +5289,7 @@ function DOCUMENTS_BY_EXAMPLE (collectionList, example) { return res; } -function RESOLVE_GRAPH_TO_COLLECTIONS(graph, options) { +function RESOLVE_GRAPH_TO_COLLECTIONS (graph, options, funcname) { var collections = {}; collections.fromCollections = []; collections.toCollection = []; @@ -5329,7 +5329,7 @@ function RESOLVE_GRAPH_TO_COLLECTIONS(graph, options) { ); } else { - WARN("GRAPH_EDGES", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); + WARN(funcname, INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH); // TODO: check if we need to return more data here return collections; } @@ -5340,13 +5340,13 @@ function RESOLVE_GRAPH_TO_COLLECTIONS(graph, options) { return collections; } -function RESOLVE_GRAPH_TO_FROM_VERTICES (graphname, options) { - var graph = DOCUMENT_HANDLE("_graphs/" + graphname), collections ; +function RESOLVE_GRAPH_TO_FROM_VERTICES (graphname, options, funcname) { + var graph = DOCUMENT_HANDLE("_graphs/" + graphname), collections; if (! graph) { - THROW("GRAPH_EDGES", INTERNAL.errors.ERROR_GRAPH_INVALID_GRAPH, "GRAPH_EDGES"); + THROW(funcname, INTERNAL.errors.ERROR_GRAPH_INVALID_GRAPH, funcname); } - collections = RESOLVE_GRAPH_TO_COLLECTIONS(graph, options); + collections = RESOLVE_GRAPH_TO_COLLECTIONS(graph, options, funcname); var removeDuplicates = function(elem, pos, self) { return self.indexOf(elem) === pos; }; @@ -5358,13 +5358,13 @@ function RESOLVE_GRAPH_TO_FROM_VERTICES (graphname, options) { ); } -function RESOLVE_GRAPH_TO_TO_VERTICES (graphname, options) { +function RESOLVE_GRAPH_TO_TO_VERTICES (graphname, options, funcname) { var graph = DOCUMENT_HANDLE("_graphs/" + graphname), collections ; if (! graph) { - THROW("GRAPH_EDGES", INTERNAL.errors.ERROR_GRAPH_INVALID_GRAPH, "GRAPH_EDGES"); + THROW(funcname, INTERNAL.errors.ERROR_GRAPH_INVALID_GRAPH, funcname); } - collections = RESOLVE_GRAPH_TO_COLLECTIONS(graph, options); + collections = RESOLVE_GRAPH_TO_COLLECTIONS(graph, options, funcname); var removeDuplicates = function(elem, pos, self) { return self.indexOf(elem) === pos; }; @@ -5374,34 +5374,18 @@ function RESOLVE_GRAPH_TO_TO_VERTICES (graphname, options) { ); } -function RESOLVE_GRAPH_TO_EDGES (graphname, options) { - var graph = DOCUMENT_HANDLE("_graphs/" + graphname), collections ; - if (! graph) { - THROW("GRAPH_EDGES", INTERNAL.errors.ERROR_GRAPH_INVALID_GRAPH, "GRAPH_EDGES"); - } - - collections = RESOLVE_GRAPH_TO_COLLECTIONS(graph, options); - var removeDuplicates = function(elem, pos, self) { - return self.indexOf(elem) === pos; - }; - - return DOCUMENTS_BY_EXAMPLE( - collections.edgeCollections.filter(removeDuplicates), options.edgeExamples - ); -} - //////////////////////////////////////////////////////////////////////////////// /// @brief GET ALL EDGE and VERTEX COLLECTION ACCORDING TO DIRECTION //////////////////////////////////////////////////////////////////////////////// -function RESOLVE_GRAPH_START_VERTICES (graphName, options) { +function RESOLVE_GRAPH_START_VERTICES (graphName, options, funcname) { // check graph exists and load edgeDefintions var graph = DOCUMENT_HANDLE("_graphs/" + graphName), collections ; if (! graph) { - THROW("GRAPH_EDGES", INTERNAL.errors.ERROR_GRAPH_INVALID_GRAPH, "GRAPH_EDGES"); + THROW(funcname, INTERNAL.errors.ERROR_GRAPH_INVALID_GRAPH, funcname); } - collections = RESOLVE_GRAPH_TO_COLLECTIONS(graph, options); + collections = RESOLVE_GRAPH_TO_COLLECTIONS(graph, options, funcname); var removeDuplicates = function(elem, pos, self) { return self.indexOf(elem) === pos; }; @@ -5414,15 +5398,15 @@ function RESOLVE_GRAPH_START_VERTICES (graphName, options) { /// @brief GET ALL EDGE and VERTEX COLLECTION ACCORDING TO DIRECTION //////////////////////////////////////////////////////////////////////////////// -function RESOLVE_GRAPH_TO_DOCUMENTS (graphname, options) { +function RESOLVE_GRAPH_TO_DOCUMENTS (graphname, options, funcname) { // check graph exists and load edgeDefintions var graph = DOCUMENT_HANDLE("_graphs/" + graphname), collections ; if (! graph) { - THROW("GRAPH_EDGES", INTERNAL.errors.ERROR_GRAPH_INVALID_GRAPH, "GRAPH_EDGES"); + THROW(funcname, INTERNAL.errors.ERROR_GRAPH_INVALID_GRAPH, funcname); } - collections = RESOLVE_GRAPH_TO_COLLECTIONS(graph, options); + collections = RESOLVE_GRAPH_TO_COLLECTIONS(graph, options, funcname); var removeDuplicates = function(elem, pos, self) { return self.indexOf(elem) === pos; }; @@ -5862,7 +5846,7 @@ function CALCULATE_SHORTEST_PATHES_WITH_DIJKSTRA (graphName, options) { } } var result = [], fromVertices = RESOLVE_GRAPH_TO_FROM_VERTICES(graphName, options), - toVertices = RESOLVE_GRAPH_TO_TO_VERTICES(graphName, options); + toVertices = RESOLVE_GRAPH_TO_TO_VERTICES(graphName, options, "GRAPH_SHORTEST_PATH"); var calculated = {}; fromVertices.forEach(function (v) { @@ -6014,7 +5998,7 @@ function AQL_GRAPH_SHORTEST_PATH (graphName, } if (options.algorithm === "Floyd-Warshall") { - var graph = RESOLVE_GRAPH_TO_DOCUMENTS(graphName, options); + var graph = RESOLVE_GRAPH_TO_DOCUMENTS(graphName, options, "GRAPH_SHORTEST_PATH"); return CALCULATE_SHORTEST_PATHES_WITH_FLOYD_WARSHALL(graph, options); } @@ -6123,7 +6107,7 @@ function AQL_GRAPH_TRAVERSAL (graphName, options.fromVertexExample = startVertexExample; options.direction = direction; - var startVertices = RESOLVE_GRAPH_START_VERTICES(graphName, options); + var startVertices = RESOLVE_GRAPH_START_VERTICES(graphName, options, "GRAPH_TRAVERSAL"); var factory = TRAVERSAL.generalGraphDatasourceFactory(graphName); startVertices.forEach(function (f) { @@ -6330,7 +6314,7 @@ function AQL_GRAPH_TRAVERSAL_TREE (graphName, options.fromVertexExample = startVertexExample; options.direction = direction; - var startVertices = RESOLVE_GRAPH_START_VERTICES(graphName, options); + var startVertices = RESOLVE_GRAPH_START_VERTICES(graphName, options, "GRAPH_TRAVERSAL_TREE"); var factory = TRAVERSAL.generalGraphDatasourceFactory(graphName); startVertices.forEach(function (f) { @@ -6529,7 +6513,7 @@ function AQL_GRAPH_NEIGHBORS (graphName, params.visitor = TRAVERSAL_NEIGHBOR_VISITOR; } - var fromVertices = RESOLVE_GRAPH_TO_FROM_VERTICES(graphName, options); + var fromVertices = RESOLVE_GRAPH_TO_FROM_VERTICES(graphName, options, "GRAPH_NEIGHBORS"); if (options.edgeExamples) { params.followEdges = options.edgeExamples; } @@ -6730,7 +6714,7 @@ function AQL_GRAPH_VERTICES (graphName, } options.fromVertexExample = vertexExamples; - return RESOLVE_GRAPH_TO_FROM_VERTICES(graphName, options); + return RESOLVE_GRAPH_TO_FROM_VERTICES(graphName, options, "GRAPH_VERTICES"); } //////////////////////////////////////////////////////////////////////////////// @@ -6923,8 +6907,8 @@ function AQL_GRAPH_COMMON_PROPERTIES (graphName, options.startVertexCollectionRestriction = options.vertex1CollectionRestriction; options.endVertexCollectionRestriction = options.vertex2CollectionRestriction; - var g = RESOLVE_GRAPH_TO_FROM_VERTICES(graphName, options); - var g2 = RESOLVE_GRAPH_TO_TO_VERTICES(graphName, options); + var g = RESOLVE_GRAPH_TO_FROM_VERTICES(graphName, options, "GRAPH_COMMON_PROPERTIES"); + var g2 = RESOLVE_GRAPH_TO_TO_VERTICES(graphName, options, "GRAPH_COMMON_PROPERTIES"); var res = []; var t = {}; g.forEach(function (n1) {