From 76c792045787720eb8edeadc91136394b68f0b7d Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Wed, 23 Jul 2014 17:07:58 +0200 Subject: [PATCH 01/34] Basic implementation. --- js/server/modules/org/arangodb/foxx.js | 2 + js/server/modules/org/arangodb/foxx/queues.js | 195 ++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 js/server/modules/org/arangodb/foxx/queues.js diff --git a/js/server/modules/org/arangodb/foxx.js b/js/server/modules/org/arangodb/foxx.js index a8f48ee800..20943cee86 100644 --- a/js/server/modules/org/arangodb/foxx.js +++ b/js/server/modules/org/arangodb/foxx.js @@ -32,8 +32,10 @@ var Controller = require("org/arangodb/foxx/controller").Controller, Model = require("org/arangodb/foxx/model").Model, Repository = require("org/arangodb/foxx/repository").Repository, manager = require("org/arangodb/foxx/manager"), + queues = require("org/arangodb/foxx/queues"), arangodb = require("org/arangodb"); +exports.queues = queues; exports.Controller = Controller; exports.Model = Model; exports.Repository = Repository; diff --git a/js/server/modules/org/arangodb/foxx/queues.js b/js/server/modules/org/arangodb/foxx/queues.js new file mode 100644 index 0000000000..007196435c --- /dev/null +++ b/js/server/modules/org/arangodb/foxx/queues.js @@ -0,0 +1,195 @@ +/*jslint es5: true, indent: 2, nomen: true, maxlen: 120 */ +/*global module, require */ + +//////////////////////////////////////////////////////////////////////////////// +/// @brief Foxx queues +/// +/// @file +/// +/// DISCLAIMER +/// +/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is triAGENS GmbH, Cologne, Germany +/// +/// @author Alan Plum +/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany +//////////////////////////////////////////////////////////////////////////////// + +var _ = require('underscore'), + db = require('org/arangodb').db, + jobs = require('org/arangodb/tasks'), + tasks, + taskDefaults, + queues, + worker, + Queue; + +tasks = {}; + +taskDefaults = { + period: 0.1, + maxFailures: 0 +}; + +queues = Object.create({ + get _tasks() { + 'use strict'; + return tasks; + }, + set _tasks(value) { + 'use strict'; + throw new Error('_tasks is not mutable'); + }, + _create: function (name) { + 'use strict'; + var instance = new Queue(name); + queues[name] = instance; + return instance; + }, + _registerTask: function (name, opts) { + 'use strict'; + if (typeof opts === 'function') { + opts = {execute: opts}; + } + if (typeof opts.execute !== 'function') { + throw new Error('Must provide a function to execute!'); + } + var task = _.extend({}, taskDefaults, opts); + if (task.maxFailures < 0) { + task.maxFailures = Infinity; + } + tasks[name] = task; + } +}); + +Queue = function Queue(name) { + 'use strict'; + Object.defineProperty(this, 'name', { + get: function () { + return name; + }, + set: function (value) { + throw new Error('name is not mutable'); + }, + configurable: false, + enumerable: true + }); + this._workers = {}; +}; + +Queue.prototype = { + _period: 0.1, // 100ms + addWorker: function (name, count) { + 'use strict'; + var workers; + if (count === undefined) { + count = 1; + } + workers = this._workers[name]; + if (!_.isArray(workers)) { + workers = []; + this._workers[name] = workers; + } + _.times(count, function () { + workers.push(jobs.register({ + command: worker, + params: [this._name, name], + period: this._period + })); + }, this); + }, + push: function (name, data) { + 'use strict'; + db._queue.save({ + status: 'pending', + queue: this.name, + task: name, + failures: 0, + data: data + }); + } +}; + +worker = function (queueName, taskName) { + 'use strict'; + var queues = require('org/arangodb/foxx/queue'), + db = require('org/arangodb').db, + console = require('console'), + tasks = queues._tasks, + numWorkers = queues[queueName]._workers[taskName].length, + job = null, + task; + + db._executeTransaction({ + collections: { + read: ['_queue'], + write: ['_queue'] + }, + action: function () { + var numBusy = db._queue.byExample({ + queue: queueName, + task: taskName, + status: 'progress' + }).count(); + + if (numBusy < numWorkers) { + job = db._queue.firstExample({ + queue: queueName, + task: taskName, + status: 'pending' + }); + } + + if (!job) { + return; + } + + db._queue.update(job._key, { + status: 'progress' + }); + } + }); + + if (!job) { + return; + } + + task = tasks[job.task]; + + try { + task.execute(job.data); + db._queue.update(job._key, {status: 'complete'}); + } catch (err) { + console.error(err.stack || String(err)); + db._queue.update(job._key, { + status: (job.failures >= task.maxFailures) ? 'failed' : 'pending', + failures: job.failures + 1 + }); + } +}; + +queues._create('default'); + +module.exports = queues; + +// ----------------------------------------------------------------------------- +// --SECTION-- END-OF-FILE +// ----------------------------------------------------------------------------- + +// Local Variables: +// mode: outline-minor +// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @}\\|/\\*jslint" +// End: From 698fd3d8ec1ce997f097a44d7994d64139a65d70 Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Thu, 24 Jul 2014 16:18:35 +0200 Subject: [PATCH 02/34] Revised implementation of Foxx queues. --- js/server/modules/org/arangodb/foxx/queues.js | 194 +++++++++--------- js/server/upgrade-database.js | 38 ++++ 2 files changed, 136 insertions(+), 96 deletions(-) diff --git a/js/server/modules/org/arangodb/foxx/queues.js b/js/server/modules/org/arangodb/foxx/queues.js index 007196435c..58a3e783b9 100644 --- a/js/server/modules/org/arangodb/foxx/queues.js +++ b/js/server/modules/org/arangodb/foxx/queues.js @@ -30,36 +30,57 @@ var _ = require('underscore'), db = require('org/arangodb').db, - jobs = require('org/arangodb/tasks'), - tasks, + workers = require('org/arangodb/tasks'), taskDefaults, queues, - worker, - Queue; + run, + Queue, + worker; -tasks = {}; - -taskDefaults = { - period: 0.1, - maxFailures: 0 -}; - -queues = Object.create({ - get _tasks() { +queues = { + _tasks: Object.create(null), + get: function (key) { 'use strict'; - return tasks; + if (!db._queues.exists(key)) { + throw new Error('Queue does not exist: ' + key); + } + return new Queue(key); }, - set _tasks(value) { + create: function (key, maxWorkers) { 'use strict'; - throw new Error('_tasks is not mutable'); + db._executeTransaction({ + collections: { + read: ['_queues'], + write: ['_queues'] + }, + action: function () { + if (db._queues.exists(key)) { + db._queues.update(key, {maxWorkers: maxWorkers}); + } else { + db._queues.save({_key: key, maxWorkers: maxWorkers}); + } + } + }); + return new Queue(key); }, - _create: function (name) { + destroy: function (key) { 'use strict'; - var instance = new Queue(name); - queues[name] = instance; - return instance; + var result = false; + db._executeTransaction({ + collections: { + read: ['_queues'], + write: ['_queues'] + }, + action: function () { + if (db._queues.exists(key)) { + db._queues.delete(key); + result = true; + } + } + }); + return result; }, - _registerTask: function (name, opts) { + registerTask: function (name, opts) { 'use strict'; if (typeof opts === 'function') { opts = {execute: opts}; @@ -71,9 +92,9 @@ queues = Object.create({ if (task.maxFailures < 0) { task.maxFailures = Infinity; } - tasks[name] = task; + queues._tasks[name] = task; } -}); +}; Queue = function Queue(name) { 'use strict'; @@ -87,101 +108,82 @@ Queue = function Queue(name) { configurable: false, enumerable: true }); - this._workers = {}; }; -Queue.prototype = { - _period: 0.1, // 100ms - addWorker: function (name, count) { - 'use strict'; - var workers; - if (count === undefined) { - count = 1; - } - workers = this._workers[name]; - if (!_.isArray(workers)) { - workers = []; - this._workers[name] = workers; - } - _.times(count, function () { - workers.push(jobs.register({ - command: worker, - params: [this._name, name], - period: this._period - })); - }, this); - }, - push: function (name, data) { - 'use strict'; - db._queue.save({ - status: 'pending', - queue: this.name, - task: name, - failures: 0, - data: data - }); - } -}; - -worker = function (queueName, taskName) { +Queue.prototype.push = function (name, data) { 'use strict'; - var queues = require('org/arangodb/foxx/queue'), - db = require('org/arangodb').db, - console = require('console'), - tasks = queues._tasks, - numWorkers = queues[queueName]._workers[taskName].length, - job = null, - task; + db._queue.save({ + status: 'pending', + queue: this.name, + task: name, + failures: 0, + data: data + }); +}; + +run = function () { + 'use strict'; + var db = require('org/arangodb').db, + console = require('console'); db._executeTransaction({ collections: { - read: ['_queue'], - write: ['_queue'] + read: ['_queues', '_jobs'], + write: ['_jobs'] }, action: function () { - var numBusy = db._queue.byExample({ - queue: queueName, - task: taskName, - status: 'progress' - }).count(); - - if (numBusy < numWorkers) { - job = db._queue.firstExample({ - queue: queueName, - task: taskName, + var queues = db._queues.all().toArray(); + queues.forEach(function (queue) { + var numBusy = db._jobs.byExample({ + queue: queue._key, + status: 'progress' + }).count(); + if (numBusy >= queue.maxWorkers) { + return; + } + db._jobs.byExample({ + queue: queue._key, status: 'pending' + }).limit(queue.maxWorkers - numBusy).toArray().forEach(function (job) { + db._jobs.update(job, {status: 'progress'}); + workers.register({ + command: worker, + params: _.extend(job._shallowCopy, {status: 'progress'}), + offset: 0 + }); }); - } - - if (!job) { - return; - } - - db._queue.update(job._key, { - status: 'progress' }); } }); +}; - if (!job) { +worker = function (job) { + 'use strict'; + var db = require('org/arangodb').db, + queues = require('org/arangodb/foxx').queues, + console = require('console'), + task = queues._tasks[job.task], + failed = false; + + if (!task) { + console.warn('Unknown task for job ' + job._key + ':', job.task); + db._jobs.update(job, {status: 'pending'}); return; } - task = tasks[job.task]; - try { - task.execute(job.data); - db._queue.update(job._key, {status: 'complete'}); + queues._tasks[job.task].execute(job.data); } catch (err) { - console.error(err.stack || String(err)); - db._queue.update(job._key, { - status: (job.failures >= task.maxFailures) ? 'failed' : 'pending', - failures: job.failures + 1 - }); + console.error('Job ' + job._key + ' failed:', err); + failed = true; } + db._jobs.update(job, failed ? { + failures: job.failures + 1, + status: (job.failures + 1 > task.maxFailures) ? 'failed' : 'pending' + } : {status: 'complete'}); }; -queues._create('default'); +queues._create('default', 1); module.exports = queues; diff --git a/js/server/upgrade-database.js b/js/server/upgrade-database.js index ed5f55aa94..eb5350e6ec 100644 --- a/js/server/upgrade-database.js +++ b/js/server/upgrade-database.js @@ -1298,6 +1298,44 @@ } }); +//////////////////////////////////////////////////////////////////////////////// +/// @brief setupQueues +/// +/// set up the collection _queues +//////////////////////////////////////////////////////////////////////////////// + + addTask({ + name: "setupQueues", + description: "setup _queues collection", + + mode: [ MODE_PRODUCTION, MODE_DEVELOPMENT ], + cluster: [ CLUSTER_NONE, CLUSTER_COORDINATOR ], + database: [ DATABASE_INIT, DATABASE_UPGRADE ], + + task: function () { + return createSystemCollection("_queues", { waitForSync : true }); + } + }); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief setupJobs +/// +/// set up the collection _jobs +//////////////////////////////////////////////////////////////////////////////// + + addTask({ + name: "setupJobs", + description: "setup _jobs collection", + + mode: [ MODE_PRODUCTION, MODE_DEVELOPMENT ], + cluster: [ CLUSTER_NONE, CLUSTER_COORDINATOR ], + database: [ DATABASE_INIT, DATABASE_UPGRADE ], + + task: function () { + return createSystemCollection("_jobs", { waitForSync : true }); + } + }); + // ----------------------------------------------------------------------------- // --SECTION-- public methods // ----------------------------------------------------------------------------- From 123127e57d13e072cf6ce6b65b7c47300b6904c6 Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Thu, 24 Jul 2014 16:27:09 +0200 Subject: [PATCH 03/34] Exposed Foxx.queues._manager.run. --- js/server/modules/org/arangodb/foxx/queues.js | 65 ++++++++++--------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/js/server/modules/org/arangodb/foxx/queues.js b/js/server/modules/org/arangodb/foxx/queues.js index 58a3e783b9..4e17870194 100644 --- a/js/server/modules/org/arangodb/foxx/queues.js +++ b/js/server/modules/org/arangodb/foxx/queues.js @@ -33,7 +33,6 @@ var _ = require('underscore'), workers = require('org/arangodb/tasks'), taskDefaults, queues, - run, Queue, worker; @@ -121,40 +120,42 @@ Queue.prototype.push = function (name, data) { }); }; -run = function () { - 'use strict'; - var db = require('org/arangodb').db, - console = require('console'); +queues._manager = { + run: function () { + 'use strict'; + var db = require('org/arangodb').db, + console = require('console'); - db._executeTransaction({ - collections: { - read: ['_queues', '_jobs'], - write: ['_jobs'] - }, - action: function () { - var queues = db._queues.all().toArray(); - queues.forEach(function (queue) { - var numBusy = db._jobs.byExample({ - queue: queue._key, - status: 'progress' - }).count(); - if (numBusy >= queue.maxWorkers) { - return; - } - db._jobs.byExample({ - queue: queue._key, - status: 'pending' - }).limit(queue.maxWorkers - numBusy).toArray().forEach(function (job) { - db._jobs.update(job, {status: 'progress'}); - workers.register({ - command: worker, - params: _.extend(job._shallowCopy, {status: 'progress'}), - offset: 0 + db._executeTransaction({ + collections: { + read: ['_queues', '_jobs'], + write: ['_jobs'] + }, + action: function () { + var queues = db._queues.all().toArray(); + queues.forEach(function (queue) { + var numBusy = db._jobs.byExample({ + queue: queue._key, + status: 'progress' + }).count(); + if (numBusy >= queue.maxWorkers) { + return; + } + db._jobs.byExample({ + queue: queue._key, + status: 'pending' + }).limit(queue.maxWorkers - numBusy).toArray().forEach(function (job) { + db._jobs.update(job, {status: 'progress'}); + workers.register({ + command: worker, + params: _.extend(job._shallowCopy, {status: 'progress'}), + offset: 0 + }); }); }); - }); - } - }); + } + }); + } }; worker = function (job) { From 2093c28359468f6c41a8c148f0fe5656411cd3d7 Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Thu, 24 Jul 2014 16:39:06 +0200 Subject: [PATCH 04/34] Fixed Foxx.queues. --- js/server/modules/org/arangodb/foxx/queues.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/js/server/modules/org/arangodb/foxx/queues.js b/js/server/modules/org/arangodb/foxx/queues.js index 4e17870194..1f43bc19dc 100644 --- a/js/server/modules/org/arangodb/foxx/queues.js +++ b/js/server/modules/org/arangodb/foxx/queues.js @@ -87,7 +87,7 @@ queues = { if (typeof opts.execute !== 'function') { throw new Error('Must provide a function to execute!'); } - var task = _.extend({}, taskDefaults, opts); + var task = _.extend({maxFailures: 0}, opts); if (task.maxFailures < 0) { task.maxFailures = Infinity; } @@ -111,7 +111,10 @@ Queue = function Queue(name) { Queue.prototype.push = function (name, data) { 'use strict'; - db._queue.save({ + if (typeof name !== 'string') { + throw new Error('Must pass a task name!'); + } + db._jobs.save({ status: 'pending', queue: this.name, task: name, @@ -184,7 +187,7 @@ worker = function (job) { } : {status: 'complete'}); }; -queues._create('default', 1); +queues.create('default', 1); module.exports = queues; From 191b3fc88a69726f5f4f352cd047ad1234c520a7 Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Thu, 24 Jul 2014 17:29:14 +0200 Subject: [PATCH 05/34] Implemented worker/manager internals. --- .../modules/org/arangodb/foxx/manager.js | 3 +- js/server/modules/org/arangodb/foxx/queues.js | 107 ++++++++++-------- 2 files changed, 61 insertions(+), 49 deletions(-) diff --git a/js/server/modules/org/arangodb/foxx/manager.js b/js/server/modules/org/arangodb/foxx/manager.js index 2a5d3b7312..74ed9e3f7f 100644 --- a/js/server/modules/org/arangodb/foxx/manager.js +++ b/js/server/modules/org/arangodb/foxx/manager.js @@ -1487,7 +1487,8 @@ exports.appRoutes = function () { return arangodb.db._executeTransaction({ collections: { - read: [ aal.name() ] + read: [ '_queues', '_jobs', aal.name() ], + write: [ '_queues', '_jobs' ] }, params: { aal : aal diff --git a/js/server/modules/org/arangodb/foxx/queues.js b/js/server/modules/org/arangodb/foxx/queues.js index 1f43bc19dc..063b42472d 100644 --- a/js/server/modules/org/arangodb/foxx/queues.js +++ b/js/server/modules/org/arangodb/foxx/queues.js @@ -28,9 +28,11 @@ /// @author Copyright 2014, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// -var _ = require('underscore'), - db = require('org/arangodb').db, +var QUEUE_MANAGER_PERIOD = 1000, // in ms + _ = require('underscore'), workers = require('org/arangodb/tasks'), + arangodb = require('org/arangodb'), + db = arangodb.db, taskDefaults, queues, Queue, @@ -47,19 +49,15 @@ queues = { }, create: function (key, maxWorkers) { 'use strict'; - db._executeTransaction({ - collections: { - read: ['_queues'], - write: ['_queues'] - }, - action: function () { - if (db._queues.exists(key)) { - db._queues.update(key, {maxWorkers: maxWorkers}); - } else { - db._queues.save({_key: key, maxWorkers: maxWorkers}); - } + try { + db._queues.save({_key: key, maxWorkers: maxWorkers}); + } catch (err) { + if (!err instanceof arangodb.ArangoError || + err.errorNum !== arangodb.ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED) { + throw err; } - }); + db._queues.update(key, {maxWorkers: maxWorkers}); + } return new Queue(key); }, destroy: function (key) { @@ -123,11 +121,41 @@ Queue.prototype.push = function (name, data) { }); }; -queues._manager = { - run: function () { +queues._worker = { + work: function (job) { 'use strict'; var db = require('org/arangodb').db, - console = require('console'); + queues = require('org/arangodb/foxx').queues, + console = require('console'), + task = queues._tasks[job.task], + failed = false; + + if (!task) { + console.warn('Unknown task for job ' + job._key + ':', job.task); + db._jobs.update(job._key, {status: 'pending'}); + return; + } + + try { + queues._tasks[job.task].execute(job.data); + } catch (err) { + console.error('Job ' + job._key + ' failed:', err); + failed = true; + } + db._jobs.update(job._key, failed ? { + failures: job.failures + 1, + status: (job.failures + 1 > task.maxFailures) ? 'failed' : 'pending' + } : {status: 'complete'}); + } +}; + +queues._manager = { + manage: function () { + 'use strict'; + var _ = require('underscore'), + db = require('org/arangodb').db, + queues = require('org/arangodb/foxx').queues, + workers = require('org/arangodb/tasks'); db._executeTransaction({ collections: { @@ -135,8 +163,8 @@ queues._manager = { write: ['_jobs'] }, action: function () { - var queues = db._queues.all().toArray(); - queues.forEach(function (queue) { + db._queues.all().toArray() + .forEach(function (queue) { var numBusy = db._jobs.byExample({ queue: queue._key, status: 'progress' @@ -147,44 +175,27 @@ queues._manager = { db._jobs.byExample({ queue: queue._key, status: 'pending' - }).limit(queue.maxWorkers - numBusy).toArray().forEach(function (job) { - db._jobs.update(job, {status: 'progress'}); + }).limit(queue.maxWorkers - numBusy).toArray().forEach(function (doc) { + db._jobs.update(doc, {status: 'progress'}); workers.register({ - command: worker, - params: _.extend(job._shallowCopy, {status: 'progress'}), + command: queues._worker.work, + params: _.extend({}, doc, {status: 'progress'}), offset: 0 }); }); }); } }); - } -}; + }, + run: function () { + 'use strict'; + var db = require('org/arangodb').db, + queues = require('org/arangodb/foxx').queues, + workers = require('org/arangodb/tasks'); -worker = function (job) { - 'use strict'; - var db = require('org/arangodb').db, - queues = require('org/arangodb/foxx').queues, - console = require('console'), - task = queues._tasks[job.task], - failed = false; - - if (!task) { - console.warn('Unknown task for job ' + job._key + ':', job.task); - db._jobs.update(job, {status: 'pending'}); - return; + db._jobs.updateByExample({status: 'progress'}, {status: 'pending'}); + workers.register({command: queues._manager.manage, period: QUEUE_MANAGER_PERIOD / 1000}); } - - try { - queues._tasks[job.task].execute(job.data); - } catch (err) { - console.error('Job ' + job._key + ' failed:', err); - failed = true; - } - db._jobs.update(job, failed ? { - failures: job.failures + 1, - status: (job.failures + 1 > task.maxFailures) ? 'failed' : 'pending' - } : {status: 'complete'}); }; queues.create('default', 1); From cc6a141d6630e385837f9ab096e9382e66fa5885 Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Thu, 24 Jul 2014 17:51:51 +0200 Subject: [PATCH 06/34] Linting. :boom: --- js/server/modules/org/arangodb/foxx/queues.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/server/modules/org/arangodb/foxx/queues.js b/js/server/modules/org/arangodb/foxx/queues.js index 063b42472d..8d43047f42 100644 --- a/js/server/modules/org/arangodb/foxx/queues.js +++ b/js/server/modules/org/arangodb/foxx/queues.js @@ -163,8 +163,7 @@ queues._manager = { write: ['_jobs'] }, action: function () { - db._queues.all().toArray() - .forEach(function (queue) { + db._queues.all().toArray().forEach(function (queue) { var numBusy = db._jobs.byExample({ queue: queue._key, status: 'progress' From aaefbfb79fe29611e54be0a2d6733c003edd0df5 Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Fri, 25 Jul 2014 15:32:52 +0200 Subject: [PATCH 07/34] Removed unused variable. --- js/server/modules/org/arangodb/foxx/queues.js | 1 - 1 file changed, 1 deletion(-) diff --git a/js/server/modules/org/arangodb/foxx/queues.js b/js/server/modules/org/arangodb/foxx/queues.js index 8d43047f42..bc97f08d27 100644 --- a/js/server/modules/org/arangodb/foxx/queues.js +++ b/js/server/modules/org/arangodb/foxx/queues.js @@ -33,7 +33,6 @@ var QUEUE_MANAGER_PERIOD = 1000, // in ms workers = require('org/arangodb/tasks'), arangodb = require('org/arangodb'), db = arangodb.db, - taskDefaults, queues, Queue, worker; From 19578891fb4950b030cf6c78d3bd394baac1c9ec Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Fri, 25 Jul 2014 16:35:05 +0200 Subject: [PATCH 08/34] More consistent naming. --- js/server/modules/org/arangodb/foxx/queues.js | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/js/server/modules/org/arangodb/foxx/queues.js b/js/server/modules/org/arangodb/foxx/queues.js index bc97f08d27..c577323a84 100644 --- a/js/server/modules/org/arangodb/foxx/queues.js +++ b/js/server/modules/org/arangodb/foxx/queues.js @@ -30,7 +30,7 @@ var QUEUE_MANAGER_PERIOD = 1000, // in ms _ = require('underscore'), - workers = require('org/arangodb/tasks'), + tasks = require('org/arangodb/tasks'), arangodb = require('org/arangodb'), db = arangodb.db, queues, @@ -38,7 +38,7 @@ var QUEUE_MANAGER_PERIOD = 1000, // in ms worker; queues = { - _tasks: Object.create(null), + _jobTypes: Object.create(null), get: function (key) { 'use strict'; if (!db._queues.exists(key)) { @@ -76,7 +76,7 @@ queues = { }); return result; }, - registerTask: function (name, opts) { + registerJobType: function (type, opts) { 'use strict'; if (typeof opts === 'function') { opts = {execute: opts}; @@ -84,11 +84,11 @@ queues = { if (typeof opts.execute !== 'function') { throw new Error('Must provide a function to execute!'); } - var task = _.extend({maxFailures: 0}, opts); - if (task.maxFailures < 0) { - task.maxFailures = Infinity; + var cfg = _.extend({maxFailures: 0}, opts); + if (cfg.maxFailures < 0) { + cfg.maxFailures = Infinity; } - queues._tasks[name] = task; + queues._jobTypes[type] = cfg; } }; @@ -106,15 +106,15 @@ Queue = function Queue(name) { }); }; -Queue.prototype.push = function (name, data) { +Queue.prototype.push = function (type, data) { 'use strict'; - if (typeof name !== 'string') { - throw new Error('Must pass a task name!'); + if (typeof type !== 'string') { + throw new Error('Must pass a job type!'); } db._jobs.save({ status: 'pending', queue: this.name, - task: name, + type: type, failures: 0, data: data }); @@ -126,24 +126,24 @@ queues._worker = { var db = require('org/arangodb').db, queues = require('org/arangodb/foxx').queues, console = require('console'), - task = queues._tasks[job.task], + cfg = queues._jobTypes[job.type], failed = false; - if (!task) { - console.warn('Unknown task for job ' + job._key + ':', job.task); + if (!cfg) { + console.warn('Unknown job type for job ' + job._key + ':', job.type); db._jobs.update(job._key, {status: 'pending'}); return; } try { - queues._tasks[job.task].execute(job.data); + cfg.execute(job.data); } catch (err) { console.error('Job ' + job._key + ' failed:', err); failed = true; } db._jobs.update(job._key, failed ? { failures: job.failures + 1, - status: (job.failures + 1 > task.maxFailures) ? 'failed' : 'pending' + status: (job.failures + 1 > cfg.maxFailures) ? 'failed' : 'pending' } : {status: 'complete'}); } }; @@ -154,7 +154,7 @@ queues._manager = { var _ = require('underscore'), db = require('org/arangodb').db, queues = require('org/arangodb/foxx').queues, - workers = require('org/arangodb/tasks'); + tasks = require('org/arangodb/tasks'); db._executeTransaction({ collections: { @@ -175,7 +175,7 @@ queues._manager = { status: 'pending' }).limit(queue.maxWorkers - numBusy).toArray().forEach(function (doc) { db._jobs.update(doc, {status: 'progress'}); - workers.register({ + tasks.register({ command: queues._worker.work, params: _.extend({}, doc, {status: 'progress'}), offset: 0 @@ -187,12 +187,8 @@ queues._manager = { }, run: function () { 'use strict'; - var db = require('org/arangodb').db, - queues = require('org/arangodb/foxx').queues, - workers = require('org/arangodb/tasks'); - db._jobs.updateByExample({status: 'progress'}, {status: 'pending'}); - workers.register({command: queues._manager.manage, period: QUEUE_MANAGER_PERIOD / 1000}); + tasks.register({command: queues._manager.manage, period: QUEUE_MANAGER_PERIOD / 1000}); } }; From f4f201872c5513d9d5e016c1c1dbbf06e746233a Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Thu, 31 Jul 2014 15:21:24 +0200 Subject: [PATCH 09/34] Cache queue objects. --- js/server/modules/org/arangodb/foxx/queues.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/js/server/modules/org/arangodb/foxx/queues.js b/js/server/modules/org/arangodb/foxx/queues.js index c577323a84..03093b93b4 100644 --- a/js/server/modules/org/arangodb/foxx/queues.js +++ b/js/server/modules/org/arangodb/foxx/queues.js @@ -33,10 +33,13 @@ var QUEUE_MANAGER_PERIOD = 1000, // in ms tasks = require('org/arangodb/tasks'), arangodb = require('org/arangodb'), db = arangodb.db, + queueMap, queues, Queue, worker; +queueMap = Object.create(null); + queues = { _jobTypes: Object.create(null), get: function (key) { @@ -44,7 +47,10 @@ queues = { if (!db._queues.exists(key)) { throw new Error('Queue does not exist: ' + key); } - return new Queue(key); + if (!queueMap[key]) { + queueMap[key] = new Queue(key); + } + return queueMap[key]; }, create: function (key, maxWorkers) { 'use strict'; @@ -57,7 +63,10 @@ queues = { } db._queues.update(key, {maxWorkers: maxWorkers}); } - return new Queue(key); + if (!queueMap[key]) { + queueMap[key] = new Queue(key); + } + return queueMap[key]; }, destroy: function (key) { 'use strict'; From 88d684e1d6e83d1471663c1b8bbf02b8c955d096 Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Thu, 31 Jul 2014 15:22:18 +0200 Subject: [PATCH 10/34] Allow creating queues with default number of workers, don't update queue if maxWorkers is not set. --- js/server/modules/org/arangodb/foxx/queues.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/server/modules/org/arangodb/foxx/queues.js b/js/server/modules/org/arangodb/foxx/queues.js index 03093b93b4..af78875871 100644 --- a/js/server/modules/org/arangodb/foxx/queues.js +++ b/js/server/modules/org/arangodb/foxx/queues.js @@ -55,13 +55,15 @@ queues = { create: function (key, maxWorkers) { 'use strict'; try { - db._queues.save({_key: key, maxWorkers: maxWorkers}); + db._queues.save({_key: key, maxWorkers: maxWorkers || 1}); } catch (err) { if (!err instanceof arangodb.ArangoError || err.errorNum !== arangodb.ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED) { throw err; } - db._queues.update(key, {maxWorkers: maxWorkers}); + if (maxWorkers) { + db._queues.update(key, {maxWorkers: maxWorkers}); + } } if (!queueMap[key]) { queueMap[key] = new Queue(key); From f5ad50d9305101a438a2d63f0b093fdb4407aa67 Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Thu, 31 Jul 2014 15:23:19 +0200 Subject: [PATCH 11/34] Return number of pending jobs in queue on push. --- js/server/modules/org/arangodb/foxx/queues.js | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/js/server/modules/org/arangodb/foxx/queues.js b/js/server/modules/org/arangodb/foxx/queues.js index af78875871..a4efec7e84 100644 --- a/js/server/modules/org/arangodb/foxx/queues.js +++ b/js/server/modules/org/arangodb/foxx/queues.js @@ -117,19 +117,25 @@ Queue = function Queue(name) { }); }; -Queue.prototype.push = function (type, data) { - 'use strict'; - if (typeof type !== 'string') { - throw new Error('Must pass a job type!'); +_.extend(Queue.prototype, { + push: function (type, data) { + 'use strict'; + if (typeof type !== 'string') { + throw new Error('Must pass a job type!'); + } + db._jobs.save({ + status: 'pending', + queue: this.name, + type: type, + failures: 0, + data: data + }); + return db._jobs.byExample({ + status: 'pending', + queue: this.name + }).count(); } - db._jobs.save({ - status: 'pending', - queue: this.name, - type: type, - failures: 0, - data: data - }); -}; +}); queues._worker = { work: function (job) { From 300ebaf53ed7bc267e2169a4c32ef448c549d90e Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Thu, 31 Jul 2014 15:23:40 +0200 Subject: [PATCH 12/34] Add chapter on Foxx Job Queues. --- .../Books/Users/Foxx/FoxxQueues.mdpp | 144 ++++++++++++++++++ Documentation/Books/Users/SUMMARY.md | 1 + 2 files changed, 145 insertions(+) create mode 100644 Documentation/Books/Users/Foxx/FoxxQueues.mdpp diff --git a/Documentation/Books/Users/Foxx/FoxxQueues.mdpp b/Documentation/Books/Users/Foxx/FoxxQueues.mdpp new file mode 100644 index 0000000000..0a18479d49 --- /dev/null +++ b/Documentation/Books/Users/Foxx/FoxxQueues.mdpp @@ -0,0 +1,144 @@ +!CHAPTER Foxx Job Queues + +*Examples* + +The following Foxx route handler will enqueue a job whenever the *"/log"* route is accessed that prints "Hello World!" to the server log. + +```js +var Foxx = require("org/arangodb/foxx"); +var ctrl = new Foxx.Controller(applicationContext); +var queue = Foxx.queues.create("my-queue"); + +Foxx.queues.registerJobType("log", function (data) { + print(data); +}); + +ctrl.get("/log", function () { + queue.push("log", "Hello World!"); +}); +``` + +!SECTION Creating or updating a queue + +Creates a queue with the given name and maximum number of workers. + +`Foxx.queues.create(name, [maxWorkers])` + +Returns the *Queue* instance for the given *name*. If the queue does not exist, a new queue with the given *name* will be created. If a queue with the given *name* already exists and *maxWorkers* is set, the queue's maximum number of workers will be updated. + +*Parameter* + +* *name*: the name of the queue to create. +* *maxWorkers* (optional): the maximum number of workers. Default: *1*. + +*Examples* + +```js +// Create a queue with the default number of workers (i.e. one) +var queue1 = Foxx.queues.create("my-queue"); +// Create a queue with a given number of workers +var queue2 = Foxx.queues.create("another-queue", 2); +// Update the number of workers of an existing queue +var queue3 = Foxx.queues.create("my-queue", 10); +// queue1 and queue3 refer to the same queue +assertEqual(queue1, queue3); +``` + +!SECTION Fetching an existing queue + +Fetches a queue with the given name. + +`Foxx.queues.get(name)` + +Returns the *Queue* instance for the given *name*. If the queue does not exist, an exception is thrown instead. + +*Parameter* + +* *name*: the name of the queue to fetch. + +*Examples* + +If the queue does not yet exist, an exception is thrown: + +```js +Foxx.queues.get("some-queue"); +// Error: Queue does not exist: some-queue +// at ... +``` + +Otherwise, the *Queue* instance will be returned: + +```js +var queue1 = Foxx.queues.create("some-queue"); +var queue2 = Foxx.queues.get("some-queue"); +assertEqual(queue1, queue2); +``` + +!SECTION Deleting a queue + +Deletes the queue with the given name from the database. + +`Foxx.queues.delete(name)` + +Returns *true* if the queue was deleted successfully. If the queue did not exist, it returns *false* instead. + +When a queue is deleted, jobs on that queue will no longer be executed. + +Deleting a queue will not delete any jobs on that queue. + +*Parameter* + +* *name*: the name of the queue to delete. + +*Examples* + +```js +var queue = Foxx.queues.create("my-queue"); +Foxx.queues.delete("my-queue"); // true +Foxx.queues.delete("my-queue"); // false +``` + +!SECTION Registering a job type + +Registers a job type with the queue manager. + +`Foxx.queues.registerJobType(name, opts)` + +If *opts* is a function, it will be treated as the *execute* function. + +*Parameter* + +* *name*: the name of the job type to register. +* *opts*: an object with the following properties: + * *execute*: a function to pass the job data to when a job is executed. + * *maxFailures* (optional): the number of times a job will be re-tried before it is marked as "failed". A negative value or *Infinity* means that the job will be re-tried on failure indefinitely. Default: *0*. + +*Examples* + +```js +var Foxx = require("org/arangodb/foxx"); +Foxx.queues.registerJobType("log", function (data) { + print(data); +}); +``` + +!SECTION Adding a job to a queue + +Adds a job of the given type to the given queue. + +`Queue::push(name, data)` + +Returns the number of pending jobs in the given queue. + +*Parameter* + +* *name*: the name of the job's job type. +* *data*: the job data of the job; must be serializable to JSON. + +*Examples* + +```js +var Foxx = require("org/arangodb/foxx"); +var queue = Foxx.queues.create("my-queue"); +queue.push("log", "Hello World!"); +``` diff --git a/Documentation/Books/Users/SUMMARY.md b/Documentation/Books/Users/SUMMARY.md index 7f4b8078cc..87376280d2 100644 --- a/Documentation/Books/Users/SUMMARY.md +++ b/Documentation/Books/Users/SUMMARY.md @@ -95,6 +95,7 @@ * [Developing Applications](Foxx/DevelopingAnApplication.md) * [Dependency Injection](Foxx/FoxxInjection.md) * [Foxx Exports](Foxx/FoxxExports.md) + * [Foxx Job Queues](Foxx/FoxxQueues.md) * [Optional Functionality](Foxx/FoxxOptional.md) * [Foxx Manager](FoxxManager/README.md) From 359073aad1d4aa13b6ce88dfb1e5466dadf4e6da Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Thu, 31 Jul 2014 15:31:34 +0200 Subject: [PATCH 13/34] Added intro. --- Documentation/Books/Users/Foxx/FoxxQueues.mdpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/Books/Users/Foxx/FoxxQueues.mdpp b/Documentation/Books/Users/Foxx/FoxxQueues.mdpp index 0a18479d49..58c9fbaa9c 100644 --- a/Documentation/Books/Users/Foxx/FoxxQueues.mdpp +++ b/Documentation/Books/Users/Foxx/FoxxQueues.mdpp @@ -1,5 +1,9 @@ !CHAPTER Foxx Job Queues +Foxx allows defining job queues that let you perform slow or expensive actions asynchronously. These queues can be used to send e-mails, call external APIs or perform other actions that you do not want to perform directly or want to retry on failure. + +For the low-level functionality see the section *Task Management* in the chapter *JavaScript Modules*. + *Examples* The following Foxx route handler will enqueue a job whenever the *"/log"* route is accessed that prints "Hello World!" to the server log. From fce8ab70f249a0aadfb09e439f14dfb9a21f04a9 Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Fri, 1 Aug 2014 17:44:14 +0200 Subject: [PATCH 14/34] Renamed queues.destroy => queues.delete. --- js/server/modules/org/arangodb/foxx/queues.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/server/modules/org/arangodb/foxx/queues.js b/js/server/modules/org/arangodb/foxx/queues.js index a4efec7e84..3acb70bb61 100644 --- a/js/server/modules/org/arangodb/foxx/queues.js +++ b/js/server/modules/org/arangodb/foxx/queues.js @@ -70,7 +70,7 @@ queues = { } return queueMap[key]; }, - destroy: function (key) { + delete: function (key) { 'use strict'; var result = false; db._executeTransaction({ From 1913a7b231b807b1e70c3e771f2bc17f8f1a4302 Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Fri, 1 Aug 2014 20:02:09 +0200 Subject: [PATCH 15/34] Extracted worker, manager modules from queue. Implemented backOff, delayUntil, success/failure callbacks, push(maxFailures). --- .../Books/Users/Foxx/FoxxQueues.mdpp | 130 +++++++++- js/server/modules/org/arangodb/foxx/queues.js | 226 +++++++++++------- .../org/arangodb/foxx/queues/manager.js | 91 +++++++ .../org/arangodb/foxx/queues/worker.js | 132 ++++++++++ 4 files changed, 495 insertions(+), 84 deletions(-) create mode 100644 js/server/modules/org/arangodb/foxx/queues/manager.js create mode 100644 js/server/modules/org/arangodb/foxx/queues/worker.js diff --git a/Documentation/Books/Users/Foxx/FoxxQueues.mdpp b/Documentation/Books/Users/Foxx/FoxxQueues.mdpp index 58c9fbaa9c..4705b1ffc4 100644 --- a/Documentation/Books/Users/Foxx/FoxxQueues.mdpp +++ b/Documentation/Books/Users/Foxx/FoxxQueues.mdpp @@ -115,7 +115,8 @@ If *opts* is a function, it will be treated as the *execute* function. * *name*: the name of the job type to register. * *opts*: an object with the following properties: * *execute*: a function to pass the job data to when a job is executed. - * *maxFailures* (optional): the number of times a job will be re-tried before it is marked as "failed". A negative value or *Infinity* means that the job will be re-tried on failure indefinitely. Default: *0*. + * *maxFailures* (optional): the number of times a job will be re-tried before it is marked as *"failed"*. A negative value or *Infinity* means that the job will be re-tried on failure indefinitely. Default: *0*. + * *backOff* (optional): either a function that takes the number of times the job has failed before as input and returns the number of milliseconds to wait before trying the job again, or the delay to be used to calculate an [exponential back-off](https://en.wikipedia.org/wiki/Exponential_backoff), or *0* for no delay. Default: *1000*. *Examples* @@ -130,19 +131,142 @@ Foxx.queues.registerJobType("log", function (data) { Adds a job of the given type to the given queue. -`Queue::push(name, data)` +`Queue::push(name, data, [opts])` -Returns the number of pending jobs in the given queue. +Returns the job id. *Parameter* * *name*: the name of the job's job type. * *data*: the job data of the job; must be serializable to JSON. +* *opts* (optional): an object with any of the following properties: + * *success* (optional): a function to be called after the job has been completed successfully. + * *failure* (optional): a function to be called after the job has failed too many times. + * *delayUntil* (optional): a timestamp in milliseconds until which the execution of the job should be delayed. Default: *Date.now()*. + * *backOff* (optional): either a function that takes the number of times the job has failed before as input and returns the number of milliseconds to wait before trying the job again, or the delay to be used to calculate an [exponential back-off](https://en.wikipedia.org/wiki/Exponential_backoff), or *0* for no delay. See [Registering a job type](#registering-a-job-type). + * *maxFailures* (optional): the number of times the job will be re-tried before it is marked as *"failed"*. A negative value or *Infinity* means that the job will be re-tried on failure indefinitely. See [Registering a job type](#registering-a-job-type). + Note that if you pass a function for the *backOff* calculation, *success* callback or *failure* callback options the function must not rely on any external scope or external variables. *Examples* +Basic usage example: + ```js var Foxx = require("org/arangodb/foxx"); var queue = Foxx.queues.create("my-queue"); queue.push("log", "Hello World!"); ``` + +This will **not** work, because *console* was defined outside the callback function: + +```js +var Foxx = require("org/arangodb/foxx"); +var queue = Foxx.queues.create("my-queue"); +var console = require("console"); // outside the callback's function scope +queue.push("log", "Hello World!", { + success: function () { + console.log("Yay!"); // throws "console is not defined" + } +}); +``` + +!SECTION Fetching a job from the queue + +Creates a proxy object representing a job with the given job id. + +`Queue::get(jobId)` + +Returns the *Job* instance for the given *jobId*. Properties of the job object will be fetched whenever they are referenced and can not be modified. + +*Parameter* + +* *jobId*: the id of the job to create a proxy object for. + +*Examples* +```js +var jobId = queue.push("log", "Hello World!"); +var job = queue.get(jobId); +assertEqual(job.id, jobId); +``` + +!SECTION Deleting a job from the queue + +Deletes a job with the given job id. + +`Queue::delete(jobId)` + +Returns *true* if the job was deleted successfully. If the job did not exist, it returns *false* instead. + +!SECTION Fetching a list of jobs in a queue + +*Examples* + +```js +queue.push("log", "Hello World!", {delayUntil: Date.now() + 50}); +assertEqual(queue.pending("log").length, 1); +// 50 ms later... +assertEqual(queue.pending("log").length, 0); +assertEqual(queue.progress("log").length, 1); +// even later... +assertEqual(queue.progress("log").length, 0); +assertEqual(queue.complete("log").length, 1); +``` + +!SUBSECTION Fetching a list of pending jobs in a queue + +`Queue::pending([type])` + +Returns an array of job ids of jobs in the given queue with the status *"pending"*, optionally filtered by the given job type. + +*Parameter* + +* *type* (optional): the name of the job type to filter the results. + +!SUBSECTION Fetching a list of jobs that are currently in progress + +`Queue::progress([type])` + +Returns an array of job ids of jobs in the given queue with the status *"progress"*, optionally filtered by the given job type. + +*Parameter* + +* *type* (optional): the name of the job type to filter the results. + +!SUBSECTION Fetching a list of completed jobs in a queue + +`Queue::complete([type])` + +Returns an array of job ids of jobs in the given queue with the status *"complete"*, optionally filtered by the given job type. + +*Parameter* + +* *type* (optional): the name of the job type to filter the results. + +!SUBSECTION Fetching a list of failed jobs in a queue + +`Queue::failed([type])` + +Returns an array of job ids of jobs in the given queue with the status *"failed"*, optionally filtered by the given job type. + +*Parameter* + +* *type* (optional): the name of the job type to filter the results. + +!SUBSECTION Fetching a list of all jobs in a queue + +`Queue::all([type])` + +Returns an array of job ids of all jobs in the given queue, optionally filtered by the given job type. + +*Parameter* + +* *type* (optional): the name of the job type to filter the results. + +!SECTION Aborting a job + +Aborts a non-completed job. + +`Job::abort()` + +Sets a job's status to *"failed"* if it is not already *"complete"*, without calling the job's *onFailure* callback. + diff --git a/js/server/modules/org/arangodb/foxx/queues.js b/js/server/modules/org/arangodb/foxx/queues.js index 3acb70bb61..63f79391ea 100644 --- a/js/server/modules/org/arangodb/foxx/queues.js +++ b/js/server/modules/org/arangodb/foxx/queues.js @@ -28,17 +28,27 @@ /// @author Copyright 2014, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// -var QUEUE_MANAGER_PERIOD = 1000, // in ms - _ = require('underscore'), - tasks = require('org/arangodb/tasks'), +var _ = require('underscore'), + flatten = require('internal').flatten, arangodb = require('org/arangodb'), db = arangodb.db, + failImmutable, queueMap, + jobMap, queues, - Queue, - worker; + getJobs, + Job, + Queue; + +failImmutable = function (name) { + 'use strict'; + return function () { + throw new Error(name + ' is not mutable'); + }; +}; queueMap = Object.create(null); +jobMap = Object.create(null); queues = { _jobTypes: Object.create(null), @@ -96,120 +106,174 @@ queues = { throw new Error('Must provide a function to execute!'); } var cfg = _.extend({maxFailures: 0}, opts); - if (cfg.maxFailures < 0) { - cfg.maxFailures = Infinity; - } queues._jobTypes[type] = cfg; } }; +getJobs = function (queue, status, type) { + 'use strict'; + var vars = {}, + aql = 'FOR job IN _jobs'; + if (queue !== undefined) { + aql += ' FILTER job.queue == @queue'; + vars.queue = queue; + } + if (status !== undefined) { + aql += ' FILTER job.status == @status'; + vars.status = status; + } + if (type !== undefined) { + aql += ' FILTER job.type == @type'; + vars.type = type; + } + aql += ' SORT job.delayUntil ASC RETURN job._id'; + return db._createStatement({ + query: aql, + bindVars: vars + }).execute().toArray(); +}; + +Job = function Job(id) { + 'use strict'; + var self = this; + Object.defineProperty(self, 'id', { + get: function () { + return id; + }, + configurable: false, + enumerable: true + }); + _.each(['data', 'status', 'type', 'failures'], function (key) { + Object.defineProperty(self, key, { + get: function () { + var value = db._jobs.document(this.id)[key]; + return (value && typeof value === 'object') ? Object.freeze(value) : value; + }, + set: failImmutable(key), + configurable: false, + enumerable: true + }); + }); +}; + +_.extend(Job.prototype, { + abort: function () { + 'use strict'; + var self = this; + db._executeTransaction({ + collections: { + read: ['_jobs'], + write: ['_jobs'] + }, + action: function () { + var job = db._jobs.document(self.id); + if (job.status !== 'completed') { + db._jobs.update(job, { + status: 'failed', + modified: Date.now(), + failures: job.failures.concat([ + flatten(new Error('Job aborted.')) + ]) + }); + } + } + }); + }, + reset: function () { + 'use strict'; + db._jobs.update(this.id, { + status: 'pending' + }); + } +}); + Queue = function Queue(name) { 'use strict'; Object.defineProperty(this, 'name', { get: function () { return name; }, - set: function (value) { - throw new Error('name is not mutable'); - }, + set: failImmutable('name'), configurable: false, enumerable: true }); }; _.extend(Queue.prototype, { - push: function (type, data) { + push: function (type, data, opts) { 'use strict'; if (typeof type !== 'string') { throw new Error('Must pass a job type!'); } - db._jobs.save({ + if (!opts) { + opts = {}; + } + var now = Date.now(); + return db._jobs.save({ status: 'pending', queue: this.name, type: type, - failures: 0, - data: data - }); - return db._jobs.byExample({ - status: 'pending', - queue: this.name - }).count(); - } -}); - -queues._worker = { - work: function (job) { + failures: [], + data: data, + created: now, + modified: now, + maxFailures: opts.maxFailures === Infinity ? -1 : opts.maxFailures, + backOff: typeof opts.backOff === 'function' ? opts.backOff.toString() : opts.backOff, + delayUntil: opts.delayUntil || now, + onSuccess: opts.success ? opts.success.toString() : null, + onFailure: opts.failure ? opts.failure.toString() : null + })._id; + }, + get: function (id) { 'use strict'; - var db = require('org/arangodb').db, - queues = require('org/arangodb/foxx').queues, - console = require('console'), - cfg = queues._jobTypes[job.type], - failed = false; - - if (!cfg) { - console.warn('Unknown job type for job ' + job._key + ':', job.type); - db._jobs.update(job._key, {status: 'pending'}); - return; + if (!id.match(/^_jobs\//)) { + id = '_jobs/' + id; } - - try { - cfg.execute(job.data); - } catch (err) { - console.error('Job ' + job._key + ' failed:', err); - failed = true; + if (!jobMap[id]) { + jobMap[id] = new Job(id); } - db._jobs.update(job._key, failed ? { - failures: job.failures + 1, - status: (job.failures + 1 > cfg.maxFailures) ? 'failed' : 'pending' - } : {status: 'complete'}); - } -}; - -queues._manager = { - manage: function () { + return jobMap[id]; + }, + delete: function (id) { 'use strict'; - var _ = require('underscore'), - db = require('org/arangodb').db, - queues = require('org/arangodb/foxx').queues, - tasks = require('org/arangodb/tasks'); - + var result = false; db._executeTransaction({ collections: { - read: ['_queues', '_jobs'], + read: ['_jobs'], write: ['_jobs'] }, action: function () { - db._queues.all().toArray().forEach(function (queue) { - var numBusy = db._jobs.byExample({ - queue: queue._key, - status: 'progress' - }).count(); - if (numBusy >= queue.maxWorkers) { - return; - } - db._jobs.byExample({ - queue: queue._key, - status: 'pending' - }).limit(queue.maxWorkers - numBusy).toArray().forEach(function (doc) { - db._jobs.update(doc, {status: 'progress'}); - tasks.register({ - command: queues._worker.work, - params: _.extend({}, doc, {status: 'progress'}), - offset: 0 - }); - }); - }); + if (db._jobs.exists(id)) { + db._jobs.delete(id); + result = true; + } } }); + return result; }, - run: function () { + pending: function (jobType) { 'use strict'; - db._jobs.updateByExample({status: 'progress'}, {status: 'pending'}); - tasks.register({command: queues._manager.manage, period: QUEUE_MANAGER_PERIOD / 1000}); + return getJobs(this.name, 'pending', jobType); + }, + complete: function (jobType) { + 'use strict'; + return getJobs(this.name, 'complete', jobType); + }, + failed: function (jobType) { + 'use strict'; + return getJobs(this.name, 'failed', jobType); + }, + progress: function (jobType) { + 'use strict'; + return getJobs(this.name, 'progress', jobType); + }, + all: function (jobType) { + 'use strict'; + return getJobs(this.name, undefined, jobType); } -}; +}); -queues.create('default', 1); +queues.create('default'); module.exports = queues; diff --git a/js/server/modules/org/arangodb/foxx/queues/manager.js b/js/server/modules/org/arangodb/foxx/queues/manager.js new file mode 100644 index 0000000000..84250daeab --- /dev/null +++ b/js/server/modules/org/arangodb/foxx/queues/manager.js @@ -0,0 +1,91 @@ +/*jslint es5: true, indent: 2, nomen: true, maxlen: 120 */ +/*global exports, require */ + +//////////////////////////////////////////////////////////////////////////////// +/// @brief Foxx queues manager +/// +/// @file +/// +/// DISCLAIMER +/// +/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is triAGENS GmbH, Cologne, Germany +/// +/// @author Alan Plum +/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany +//////////////////////////////////////////////////////////////////////////////// + +var QUEUE_MANAGER_PERIOD = 1000, // in ms + _ = require('underscore'), + tasks = require('org/arangodb/tasks'), + db = require('org/arangodb').db; + +exports.manage = function () { + 'use strict'; + db._executeTransaction({ + collections: { + read: ['_queues', '_jobs'], + write: ['_jobs'] + }, + action: function () { + db._queues.all().toArray().forEach(function (queue) { + var numBusy = db._jobs.byExample({ + queue: queue._key, + status: 'progress' + }).count(); + if (numBusy >= queue.maxWorkers) { + return; + } + db._createStatement({ + query: ( + 'FOR job IN _jobs' + + ' FILTER job.queue == @queue' + + ' && job.status == "pending"' + + ' && job.delayUntil <= @now' + + ' SORT job.delayUntil ASC' + + ' LIMIT @max' + + ' RETURN job' + ), + bindVars: { + queue: queue._key, + now: Date.now(), + max: queue.maxWorkers - numBusy + } + }).execute().toArray().forEach(function (doc) { + db._jobs.update(doc, {status: 'progress'}); + tasks.register({ + command: function (job) { + require('org/arangodb/foxx/queues/worker').work(job); + }, + params: _.extend({}, doc, {status: 'progress'}), + offset: 0 + }); + }); + }); + } + }); +}; + +exports.run = function () { + 'use strict'; + db._jobs.updateByExample({status: 'progress'}, {status: 'pending'}); + return tasks.register({ + command: function () { + require('org/arangodb/foxx/queues/manager').manage(); + }, + period: QUEUE_MANAGER_PERIOD / 1000 + }); +}; \ No newline at end of file diff --git a/js/server/modules/org/arangodb/foxx/queues/worker.js b/js/server/modules/org/arangodb/foxx/queues/worker.js new file mode 100644 index 0000000000..a2eceb2eab --- /dev/null +++ b/js/server/modules/org/arangodb/foxx/queues/worker.js @@ -0,0 +1,132 @@ +/*jslint es5: true, indent: 2, nomen: true, maxlen: 120, evil: true */ +/*global exports, require */ + +//////////////////////////////////////////////////////////////////////////////// +/// @brief Foxx queues workers +/// +/// @file +/// +/// DISCLAIMER +/// +/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is triAGENS GmbH, Cologne, Germany +/// +/// @author Alan Plum +/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany +//////////////////////////////////////////////////////////////////////////////// + +var db = require('org/arangodb').db, + flatten = require('internal').flatten, + exponentialBackOff = require('internal').exponentialBackOff, + console = require('console'), + queues = require('org/arangodb/foxx').queues, + getBackOffDelay; + +getBackOffDelay = function (job, cfg) { + 'use strict'; + var n = job.failures.length - 1; + if (typeof job.backOff === 'string') { + try { + return eval('(' + job.backOff + ')(' + n + ')'); + } catch (jobStrErr) { + console.error('Failed to call backOff function of job ' + job._key, jobStrErr); + } + } else if (typeof job.backOff === 'number' && job.backOff >= 0 && job.backOff < Infinity) { + return exponentialBackOff(n, job.backOff); + } + if (typeof cfg.backOff === 'function') { + try { + return cfg.backOff(n); + } catch (cfgFnErr) { + console.error('Failed to call backOff function of job type ' + job.type, cfgFnErr); + } + } else if (typeof cfg.backOff === 'string') { + try { + return eval('(' + cfg.backOff + ')(' + n + ')'); + } catch (cfgStrErr) { + console.error('Failed to call backOff function of job type ' + job.type, cfgStrErr); + } + } + if (typeof cfg.backOff === 'number' && cfg.backOff >= 0 && cfg.backOff < Infinity) { + return exponentialBackOff(n, cfg.backOff); + } + return exponentialBackOff(n, 1000); +}; + +exports.work = function (job) { + 'use strict'; + var cfg = queues._jobTypes[job.type], + success = true, + callback = null, + now = Date.now(), + maxFailures = ( + typeof job.maxFailures === 'number' + ? (job.maxFailures < 0 ? Infinity : job.maxFailures) + : (cfg.maxFailures < 0 ? Infinity : cfg.maxFailures) + ) || 0; + + if (!cfg) { + console.warn('Unknown job type for job ' + job._key + ':', job.type); + db._jobs.update(job._key, {status: 'pending'}); + return; + } + + try { + cfg.execute(job.data, job._id); + } catch (executeErr) { + console.error('Job ' + job._key + ' failed:', executeErr); + job.failures.push(flatten(executeErr)); + success = false; + } + if (success) { + // mark complete + callback = job.onSuccess; + db._jobs.update(job._key, {modified: Date.now(), status: 'complete'}); + } else if (job.failures.length > maxFailures) { + // mark failed + callback = job.onFailure; + db._jobs.update(job._key, { + modified: now, + failures: job.failures, + status: 'failed' + }); + } else { + // queue for retry + db._jobs.update(job._key, { + modified: now, + delayUntil: now + getBackOffDelay(job, cfg), + failures: job.failures, + status: 'pending' + }); + } + + if (callback) { + try { + eval('(' + callback + ')(' + [ + JSON.stringify(job._id), + JSON.stringify(job.data), + JSON.stringify(job.failures) + ].join(', ') + ')'); + } catch (callbackErr) { + console.error( + 'Failed to execute ' + + (success ? 'success' : 'failure') + + ' callback for job ' + job._key + ':', + callbackErr + ); + } + } +}; \ No newline at end of file From 9350b5a9750cb1915f691b518ac2193b1df7a5b0 Mon Sep 17 00:00:00 2001 From: Frank Celler Date: Mon, 11 Aug 2014 12:59:03 +0200 Subject: [PATCH 16/34] start the queue manager once during boot --- js/server/bootstrap/coordinator.js | 3 +++ js/server/bootstrap/db-server.js | 3 +++ js/server/server.js | 7 ++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/js/server/bootstrap/coordinator.js b/js/server/bootstrap/coordinator.js index ef3079fd72..82678de414 100644 --- a/js/server/bootstrap/coordinator.js +++ b/js/server/bootstrap/coordinator.js @@ -52,6 +52,9 @@ // autoload all modules and reload routing information in all threads internal.executeGlobalContextFunction("bootstrapCoordinator"); + // start the queue manager once + require('org/arangodb/foxx/queues/manager').run(); + console.info("bootstraped coordinator %s", ArangoServerState.id()); return true; }; diff --git a/js/server/bootstrap/db-server.js b/js/server/bootstrap/db-server.js index 6dfd918568..5ac6b6a052 100644 --- a/js/server/bootstrap/db-server.js +++ b/js/server/bootstrap/db-server.js @@ -57,6 +57,9 @@ require("org/arangodb/statistics").startup(); } + // start the queue manager once + require('org/arangodb/foxx/queues/manager').run(); + console.info("bootstraped DB server %s", ArangoServerState.id()); return true; }; diff --git a/js/server/server.js b/js/server/server.js index 62147f97f3..44a9f4d8cd 100644 --- a/js/server/server.js +++ b/js/server/server.js @@ -45,7 +45,7 @@ var internal = require("internal"); var db = internal.db; - // one the cluster the kickstarter will call boostrap-role.js + // in the cluster the kickstarter will call boostrap-role.js if (ArangoAgency.prefix() !== "") { return true; } @@ -66,6 +66,11 @@ // reload routing information internal.loadStartup("server/bootstrap/routing.js").startup(); + // start the queue manager once + if (internal.enableStatistics && internal.threadNumber === 0) { + require('org/arangodb/foxx/queues/manager').run(); + } + return true; }()); From 4f5caa805e1c7c3f3b8a7819f2a248999bbc51e2 Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Tue, 12 Aug 2014 19:32:26 +0200 Subject: [PATCH 17/34] Fixed undefined CLUSTER_COORDINATOR. --- js/server/upgrade-database.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/server/upgrade-database.js b/js/server/upgrade-database.js index eb5350e6ec..6e22754bd0 100644 --- a/js/server/upgrade-database.js +++ b/js/server/upgrade-database.js @@ -1309,7 +1309,7 @@ description: "setup _queues collection", mode: [ MODE_PRODUCTION, MODE_DEVELOPMENT ], - cluster: [ CLUSTER_NONE, CLUSTER_COORDINATOR ], + cluster: [ CLUSTER_NONE, CLUSTER_COORDINATOR_GLOBAL ], database: [ DATABASE_INIT, DATABASE_UPGRADE ], task: function () { @@ -1328,7 +1328,7 @@ description: "setup _jobs collection", mode: [ MODE_PRODUCTION, MODE_DEVELOPMENT ], - cluster: [ CLUSTER_NONE, CLUSTER_COORDINATOR ], + cluster: [ CLUSTER_NONE, CLUSTER_COORDINATOR_GLOBAL ], database: [ DATABASE_INIT, DATABASE_UPGRADE ], task: function () { From e0db77b07017db32d860ddeec5d54905a308c22e Mon Sep 17 00:00:00 2001 From: Frank Celler Date: Wed, 13 Aug 2014 12:17:37 +0200 Subject: [PATCH 18/34] fixed tests, log can contain more entries --- UnitTests/HttpInterface/api-replication-spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitTests/HttpInterface/api-replication-spec.rb b/UnitTests/HttpInterface/api-replication-spec.rb index a32e42bb8b..dc13db1aa6 100644 --- a/UnitTests/HttpInterface/api-replication-spec.rb +++ b/UnitTests/HttpInterface/api-replication-spec.rb @@ -204,7 +204,7 @@ describe ArangoDB do doc.headers["x-arango-replication-lastincluded"].should_not eq("0") doc.headers["content-type"].should eq("application/x-arango-dump; charset=utf-8") - body = doc.response.body + body = doc.response.body.split("\n")[0] document = JSON.parse(body) document.should have_key("tick") From da36a1d42e1956ed6d6be8ebb5bc6b6940559cf2 Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Wed, 13 Aug 2014 15:18:07 +0200 Subject: [PATCH 19/34] Added support for job type schemas. --- .../Books/Users/Foxx/FoxxQueues.mdpp | 4 ++- js/server/modules/org/arangodb/foxx/queues.js | 28 ++++++++++++++++--- .../org/arangodb/foxx/queues/worker.js | 12 ++++---- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/Documentation/Books/Users/Foxx/FoxxQueues.mdpp b/Documentation/Books/Users/Foxx/FoxxQueues.mdpp index 4705b1ffc4..9c37ff8245 100644 --- a/Documentation/Books/Users/Foxx/FoxxQueues.mdpp +++ b/Documentation/Books/Users/Foxx/FoxxQueues.mdpp @@ -116,7 +116,8 @@ If *opts* is a function, it will be treated as the *execute* function. * *opts*: an object with the following properties: * *execute*: a function to pass the job data to when a job is executed. * *maxFailures* (optional): the number of times a job will be re-tried before it is marked as *"failed"*. A negative value or *Infinity* means that the job will be re-tried on failure indefinitely. Default: *0*. - * *backOff* (optional): either a function that takes the number of times the job has failed before as input and returns the number of milliseconds to wait before trying the job again, or the delay to be used to calculate an [exponential back-off](https://en.wikipedia.org/wiki/Exponential_backoff), or *0* for no delay. Default: *1000*. + * *schema* (optional): a [Joi](https://github.com/hapijs/joi) schema to validate a job's data against before accepting it. + * *backOff* (optional): either a function that takes the number of times the job has failed before as input and returns the number of milliseconds to wait before trying the job again, or the delay to be used to calculate an [exponential back-off](https://en.wikipedia.org/wiki/Exponential_backoff), or *0* for no delay. Default: *1000*. *Examples* @@ -144,6 +145,7 @@ Returns the job id. * *failure* (optional): a function to be called after the job has failed too many times. * *delayUntil* (optional): a timestamp in milliseconds until which the execution of the job should be delayed. Default: *Date.now()*. * *backOff* (optional): either a function that takes the number of times the job has failed before as input and returns the number of milliseconds to wait before trying the job again, or the delay to be used to calculate an [exponential back-off](https://en.wikipedia.org/wiki/Exponential_backoff), or *0* for no delay. See [Registering a job type](#registering-a-job-type). + * *allowUnknown* (optional): whether the job should be queued even if the job type name does not match a currently registered job type. Default: *false*. * *maxFailures* (optional): the number of times the job will be re-tried before it is marked as *"failed"*. A negative value or *Infinity* means that the job will be re-tried on failure indefinitely. See [Registering a job type](#registering-a-job-type). Note that if you pass a function for the *backOff* calculation, *success* callback or *failure* callback options the function must not rely on any external scope or external variables. diff --git a/js/server/modules/org/arangodb/foxx/queues.js b/js/server/modules/org/arangodb/foxx/queues.js index 63f79391ea..9f6bfc7af0 100644 --- a/js/server/modules/org/arangodb/foxx/queues.js +++ b/js/server/modules/org/arangodb/foxx/queues.js @@ -31,6 +31,7 @@ var _ = require('underscore'), flatten = require('internal').flatten, arangodb = require('org/arangodb'), + console = require('console'), db = arangodb.db, failImmutable, queueMap, @@ -105,6 +106,9 @@ queues = { if (typeof opts.execute !== 'function') { throw new Error('Must provide a function to execute!'); } + if (opts.schema && typeof opts.schema.validate !== 'function') { + throw new Error('Schema must be a joi schema!'); + } var cfg = _.extend({maxFailures: 0}, opts); queues._jobTypes[type] = cfg; } @@ -200,19 +204,35 @@ Queue = function Queue(name) { }; _.extend(Queue.prototype, { - push: function (type, data, opts) { + push: function (name, data, opts) { 'use strict'; - if (typeof type !== 'string') { + var type, result, now; + if (typeof name !== 'string') { throw new Error('Must pass a job type!'); } if (!opts) { opts = {}; } - var now = Date.now(); + + type = queues._jobTypes[name]; + if (type !== undefined) { + if (type.schema) { + result = type.schema.validate(data); + if (result.error) { + throw result.error; + } + data = result.value; + } + } else if (opts.allowUnknown) { + console.warn('Unknown job type: ' + name); + } else { + throw new Error('Unknown job type: ' + name); + } + now = Date.now(); return db._jobs.save({ status: 'pending', queue: this.name, - type: type, + type: name, failures: [], data: data, created: now, diff --git a/js/server/modules/org/arangodb/foxx/queues/worker.js b/js/server/modules/org/arangodb/foxx/queues/worker.js index a2eceb2eab..b6146e79ea 100644 --- a/js/server/modules/org/arangodb/foxx/queues/worker.js +++ b/js/server/modules/org/arangodb/foxx/queues/worker.js @@ -72,11 +72,7 @@ exports.work = function (job) { success = true, callback = null, now = Date.now(), - maxFailures = ( - typeof job.maxFailures === 'number' - ? (job.maxFailures < 0 ? Infinity : job.maxFailures) - : (cfg.maxFailures < 0 ? Infinity : cfg.maxFailures) - ) || 0; + maxFailures; if (!cfg) { console.warn('Unknown job type for job ' + job._key + ':', job.type); @@ -84,6 +80,12 @@ exports.work = function (job) { return; } + maxFailures = ( + typeof job.maxFailures === 'number' + ? (job.maxFailures < 0 ? Infinity : job.maxFailures) + : (cfg.maxFailures < 0 ? Infinity : cfg.maxFailures) + ) || 0; + try { cfg.execute(job.data, job._id); } catch (executeErr) { From 277290fc0ed6d0b50a37f7eb265bb6d79a50aa45 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Wed, 13 Aug 2014 16:22:17 +0200 Subject: [PATCH 20/34] unification with aql2 code --- arangod/V8Server/v8-vocbase.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/arangod/V8Server/v8-vocbase.cpp b/arangod/V8Server/v8-vocbase.cpp index 9d2bf413da..5f06e16fe9 100644 --- a/arangod/V8Server/v8-vocbase.cpp +++ b/arangod/V8Server/v8-vocbase.cpp @@ -9339,7 +9339,7 @@ static v8::Handle CreateDatabaseCoordinator (v8::Arguments const& arg TRI_json_t* json = TRI_CreateArrayJson(TRI_UNKNOWN_MEM_ZONE); - if (0 == json) { + if (nullptr == json) { TRI_V8_EXCEPTION_MEMORY(scope); } @@ -9371,16 +9371,16 @@ static v8::Handle CreateDatabaseCoordinator (v8::Arguments const& arg // database was created successfully in agency - TRI_v8_global_t* v8g = (TRI_v8_global_t*) v8::Isolate::GetCurrent()->GetData(); + TRI_v8_global_t* v8g = static_cast(v8::Isolate::GetCurrent()->GetData()); // now wait for heartbeat thread to create the database object - TRI_vocbase_t* database = 0; + TRI_vocbase_t* vocbase = nullptr; int tries = 0; while (++tries <= 6000) { - database = TRI_UseByIdCoordinatorDatabaseServer((TRI_server_t*) v8g->_server, id); + vocbase = TRI_UseByIdCoordinatorDatabaseServer(static_cast(v8g->_server), id); - if (database != 0) { + if (vocbase != nullptr) { break; } @@ -9388,7 +9388,7 @@ static v8::Handle CreateDatabaseCoordinator (v8::Arguments const& arg usleep(10000); } - if (database == 0) { + if (vocbase == nullptr) { TRI_V8_EXCEPTION(scope, TRI_ERROR_INTERNAL); } @@ -9407,7 +9407,7 @@ static v8::Handle CreateDatabaseCoordinator (v8::Arguments const& arg TRI_vocbase_t* orig = v8g->_vocbase; TRI_ASSERT(orig != nullptr); - v8g->_vocbase = database; + v8g->_vocbase = vocbase; // initalise database bool allowUseDatabase = v8g->_allowUseDatabase; @@ -9420,7 +9420,7 @@ static v8::Handle CreateDatabaseCoordinator (v8::Arguments const& arg // and switch back v8g->_vocbase = orig; - TRI_ReleaseVocBase(database); + TRI_ReleaseVocBase(vocbase); return scope.Close(v8::True()); } From 4979513dc844ca9d58c26cf61108487d2d1fcae2 Mon Sep 17 00:00:00 2001 From: Heiko Kernbach Date: Thu, 14 Aug 2014 14:03:15 +0200 Subject: [PATCH 21/34] added validation rules for modal views --- .../frontend/js/templates/documentsView.ejs | 26 ------- .../frontend/js/views/applicationsView.js | 12 +-- .../frontend/js/views/collectionsItemView.js | 14 ++-- .../frontend/js/views/collectionsView.js | 6 +- .../frontend/js/views/databaseView.js | 15 +++- .../frontend/js/views/documentsView.js | 73 ++++++++++++++----- .../frontend/js/views/foxxActiveView.js | 10 ++- .../aardvark/frontend/js/views/modalView.js | 19 ++++- .../aardvark/frontend/js/views/queryView.js | 9 ++- .../frontend/js/views/userManagementView.js | 17 ++++- .../aardvark/frontend/scss/_buttons.scss | 3 + .../aardvark/frontend/scss/generated.css | 4 +- 12 files changed, 138 insertions(+), 70 deletions(-) diff --git a/js/apps/system/aardvark/frontend/js/templates/documentsView.ejs b/js/apps/system/aardvark/frontend/js/templates/documentsView.ejs index 809770fb13..6fbf8f4e39 100644 --- a/js/apps/system/aardvark/frontend/js/templates/documentsView.ejs +++ b/js/apps/system/aardvark/frontend/js/templates/documentsView.ejs @@ -323,30 +323,4 @@ - - diff --git a/js/apps/system/aardvark/frontend/js/views/applicationsView.js b/js/apps/system/aardvark/frontend/js/views/applicationsView.js index 5666b962aa..34dd88ee92 100644 --- a/js/apps/system/aardvark/frontend/js/views/applicationsView.js +++ b/js/apps/system/aardvark/frontend/js/views/applicationsView.js @@ -31,16 +31,16 @@ window.ApplicationsView = Backbone.View.extend({ 'Github information', '', 'Your Github link comes here: username/application-name', - undefined, + "username/application-name", false, [ - { - rule: Joi.string().required(), - msg: "No github link given." - }, { rule: Joi.string().regex(/^[a-zA-Z0-9]+[\/]/), msg: "No valid github link given." + }, + { + rule: Joi.string().required(), + msg: "No github link given." } ] )); @@ -50,7 +50,7 @@ window.ApplicationsView = Backbone.View.extend({ 'Version (optional)', '', 'Example: v1.1.2 for Version 1.1.2 - if no version is commited, master is used', - undefined, + 'master', false, /[<>&'"]/ )); diff --git a/js/apps/system/aardvark/frontend/js/views/collectionsItemView.js b/js/apps/system/aardvark/frontend/js/views/collectionsItemView.js index c3d42e1116..ae9cffdcde 100644 --- a/js/apps/system/aardvark/frontend/js/views/collectionsItemView.js +++ b/js/apps/system/aardvark/frontend/js/views/collectionsItemView.js @@ -172,16 +172,20 @@ "", true, [ - { - rule: Joi.string().required(), - msg: "No collection name given." - }, { rule: Joi.string().regex(/^[a-zA-Z]/), msg: "Collection name must always start with a letter." + }, + { + rule: Joi.string().regex(/^[a-zA-Z0-9\-_]*$/), + msg: 'Only Symbols "_" and "-" are allowed.' + }, + { + rule: Joi.string().required(), + msg: "No collection name given." } ] - ) + ) ); } diff --git a/js/apps/system/aardvark/frontend/js/views/collectionsView.js b/js/apps/system/aardvark/frontend/js/views/collectionsView.js index 173d3a8c90..ce2fda7d6b 100644 --- a/js/apps/system/aardvark/frontend/js/views/collectionsView.js +++ b/js/apps/system/aardvark/frontend/js/views/collectionsView.js @@ -37,7 +37,7 @@ }, this); //if type in collectionsDropdown2 is changed, - // the page will be rerendered, so check the toggel button + //the page will be rerendered, so check the toggel button if($('#collectionsDropdown2').css('display') === 'none') { $('#collectionsToggle').removeClass('activated'); @@ -304,6 +304,10 @@ rule: Joi.string().regex(/^[a-zA-Z]/), msg: "Collection name must always start with a letter." }, + { + rule: Joi.string().regex(/^[a-zA-Z0-9\-_]*$/), + msg: 'Only Symbols "_" and "-" are allowed.' + }, { rule: Joi.string().required(), msg: "No collection name given." diff --git a/js/apps/system/aardvark/frontend/js/views/databaseView.js b/js/apps/system/aardvark/frontend/js/views/databaseView.js index f32438b68b..7523dd4d6e 100644 --- a/js/apps/system/aardvark/frontend/js/views/databaseView.js +++ b/js/apps/system/aardvark/frontend/js/views/databaseView.js @@ -246,14 +246,17 @@ [ { rule: Joi.string().regex(/^[a-zA-Z]/), - msg: "Database name must always start with a letter." + msg: "Database name must start with a letter." + }, + { + rule: Joi.string().regex(/^[a-zA-Z0-9\-_]*$/), + msg: 'Only Symbols "_" and "-" are allowed.' }, { rule: Joi.string().required(), msg: "No database name given." } ] - ) ); tableContent.push( @@ -266,7 +269,13 @@ + "you will not be able to see the database. " + "If there is a failure you will be informed.", "Database Owner", - true + true, + [ + { + rule: Joi.string().required(), + msg: "No username given." + } + ] ) ); tableContent.push( diff --git a/js/apps/system/aardvark/frontend/js/views/documentsView.js b/js/apps/system/aardvark/frontend/js/views/documentsView.js index e43cdc7dcc..c9c4653e2c 100644 --- a/js/apps/system/aardvark/frontend/js/views/documentsView.js +++ b/js/apps/system/aardvark/frontend/js/views/documentsView.js @@ -1,5 +1,5 @@ /*jslint indent: 2, nomen: true, maxlen: 100, vars: true, white: true, plusplus: true */ -/*global require, arangoHelper, _, $, window, arangoHelper, templateEngine */ +/*global require, arangoHelper, _, $, window, arangoHelper, templateEngine, Joi*/ (function() { "use strict"; @@ -50,7 +50,6 @@ "click #filterSend" : "sendFilter", "click #addFilterItem" : "addFilterItem", "click .removeFilterItem" : "removeFilterItem", - "click #confirmCreateEdge" : "addEdge", "click #documentsTableID tr" : "clicked", "click #deleteDoc" : "remove", "click #addDocumentButton" : "addDocument", @@ -108,9 +107,6 @@ }, listenKey: function (e) { - if(e.keyCode === 13){ - this.addEdge(); - } }, resetView: function () { @@ -342,8 +338,56 @@ // second parameter is "true" to disable caching of collection type doctype = arangoHelper.collectionApiType(collid, true); if (doctype === 'edge') { - $('#edgeCreateModal').modal('show'); - arangoHelper.fixTooltips(".modalTooltips", "left"); + //$('#edgeCreateModal').modal('show'); + //arangoHelper.fixTooltips(".modalTooltips", "left"); + + var buttons = [], tableContent = []; + + tableContent.push( + window.modalView.createTextEntry( + 'new-edge-from-attr', + '_from', + '', + "document _id: document handle of the linked vertex (incoming relation)", + undefined, + false, + [ + { + rule: Joi.string().required(), + msg: "No _from attribute given." + } + ] + ) + ); + + tableContent.push( + window.modalView.createTextEntry( + 'new-edge-to', + '_to', + '', + "document _id: document handle of the linked vertex (outgoing relation)", + undefined, + false, + [ + { + rule: Joi.string().required(), + msg: "No _to attribute given." + } + ] + ) + ); + + buttons.push( + window.modalView.createSuccessButton('Create', this.addEdge.bind(this)) + ); + + window.modalView.show( + 'modalTable.ejs', + 'Create edge', + buttons, + tableContent + ); + return; } @@ -359,20 +403,13 @@ addEdge: function () { var collid = window.location.hash.split("/")[1]; - var from = $('#new-document-from').val(); - var to = $('#new-document-to').val(); - if (from === '') { - //Heiko: Form-Validator - from is missing - return; - } - if (to === '') { - //Heiko: Form-Validator - to is missing - return; - } + var from = $('.modal-body #new-edge-from-attr').last().val(); + var to = $('.modal-body #new-edge-to').last().val(); var result = this.documentStore.createTypeEdge(collid, from, to); if (result !== false) { - $('#edgeCreateModal').modal('hide'); + //$('#edgeCreateModal').modal('hide'); + window.modalView.hide(); window.location.hash = "collection/"+result; } //Error diff --git a/js/apps/system/aardvark/frontend/js/views/foxxActiveView.js b/js/apps/system/aardvark/frontend/js/views/foxxActiveView.js index 0af9001e5c..b8fdfc4866 100644 --- a/js/apps/system/aardvark/frontend/js/views/foxxActiveView.js +++ b/js/apps/system/aardvark/frontend/js/views/foxxActiveView.js @@ -1,5 +1,5 @@ /*jslint indent: 2, nomen: true, maxlen: 100, vars: true, white: true, plusplus: true */ -/*global Backbone, $, window, EJS, arangoHelper, _, templateEngine*/ +/*global Backbone, $, window, EJS, arangoHelper, _, templateEngine, Joi*/ (function() { "use strict"; @@ -89,7 +89,13 @@ "change-mount-point", "Mount", this.model.get("mount"), "The path where the app can be reached.", "mount-path", - true + true, + [ + { + rule: Joi.string().required(), + msg: "No mount-path given." + } + ] )); /* * For the future, update apps to available newer versions diff --git a/js/apps/system/aardvark/frontend/js/views/modalView.js b/js/apps/system/aardvark/frontend/js/views/modalView.js index 0179183901..28b4d9b109 100644 --- a/js/apps/system/aardvark/frontend/js/views/modalView.js +++ b/js/apps/system/aardvark/frontend/js/views/modalView.js @@ -285,8 +285,17 @@ var ind = buttons.indexOf(this.closeButton); buttons.splice(ind, 1); + var completeTableContent = tableContent; + try { + _.each(advancedContent.content, function(x) { + completeTableContent.push(x); + }); + } + catch(ignore) { + } + //handle select2 - _.each(tableContent, function(r) { + _.each(completeTableContent, function(r) { if (r.type === self.tables.SELECT2) { $('#'+r.id).select2({ tags: r.tags || [], @@ -299,7 +308,7 @@ });//handle select2 self.testInput = (function(){ - _.each(tableContent,function(r){ + _.each(completeTableContent,function(r){ if(r.validateInput) { //catch result of validation and act @@ -331,13 +340,15 @@ if(error === true){ // if validation throws an error $('#' + r.id).addClass('invalid-input'); + $('.modal-footer .button-success').prop('disabled', true); + $('.modal-footer .button-success').addClass('disabled'); if (errorElement) { //error element available $(errorElement).text(msg); } else { - //render error element + //error element not available $('#' + r.id).after('

' + msg+ '

'); } @@ -345,6 +356,8 @@ else { //validation throws success $('#' + r.id).removeClass('invalid-input'); + $('.modal-footer .button-success').prop('disabled', false); + $('.modal-footer .button-success').removeClass('disabled'); if (errorElement) { $(errorElement).remove(); } diff --git a/js/apps/system/aardvark/frontend/js/views/queryView.js b/js/apps/system/aardvark/frontend/js/views/queryView.js index 6bcb4ff169..d4685cdfa7 100644 --- a/js/apps/system/aardvark/frontend/js/views/queryView.js +++ b/js/apps/system/aardvark/frontend/js/views/queryView.js @@ -1,6 +1,6 @@ /*jslint indent: 2, nomen: true, maxlen: 100, vars: true, white: true, plusplus: true */ /*global require, exports, Backbone, EJS, $, setTimeout, localStorage, ace, Storage, window, _ */ -/*global arangoHelper, templateEngine, jQuery*/ +/*global arangoHelper, templateEngine, jQuery, Joi*/ (function () { "use strict"; @@ -59,7 +59,12 @@ undefined, undefined, false, - /[<>&'"]/ + [ + { + rule: Joi.string().required(), + msg: "No query name given." + } + ] ) ); buttons.push( diff --git a/js/apps/system/aardvark/frontend/js/views/userManagementView.js b/js/apps/system/aardvark/frontend/js/views/userManagementView.js index 45b2042d33..de30effeb2 100644 --- a/js/apps/system/aardvark/frontend/js/views/userManagementView.js +++ b/js/apps/system/aardvark/frontend/js/views/userManagementView.js @@ -1,6 +1,6 @@ /*jslint indent: 2, nomen: true, maxlen: 100, vars: true, white: true, plusplus: true */ /*global window, document, Backbone, EJS, SwaggerUi, hljs, $, arangoHelper, templateEngine, - CryptoJS */ + CryptoJS, Joi */ (function() { "use strict"; @@ -406,7 +406,20 @@ tableContent = []; tableContent.push( - window.modalView.createTextEntry("newUsername", "Username", "", false, "Username", true) + window.modalView.createTextEntry( + "newUsername", + "Username", + "", + false, + "Username", + true, + [ + { + rule: Joi.string().required(), + msg: "No username given." + } + ] + ) ); tableContent.push( window.modalView.createTextEntry("newName", "Name", "", false, "Name", false) diff --git a/js/apps/system/aardvark/frontend/scss/_buttons.scss b/js/apps/system/aardvark/frontend/scss/_buttons.scss index 4988b2002d..2230563817 100644 --- a/js/apps/system/aardvark/frontend/scss/_buttons.scss +++ b/js/apps/system/aardvark/frontend/scss/_buttons.scss @@ -38,7 +38,10 @@ .button-success { @extend %btn; @extend %positive; +} +.button-success:disabled { + @extend %neutral; } .button-danger { diff --git a/js/apps/system/aardvark/frontend/scss/generated.css b/js/apps/system/aardvark/frontend/scss/generated.css index c221dffb04..fd21840ce1 100644 --- a/js/apps/system/aardvark/frontend/scss/generated.css +++ b/js/apps/system/aardvark/frontend/scss/generated.css @@ -1420,9 +1420,9 @@ nav.navbar, footer.footer { .button-warning:hover, .button-warning:focus { background-color: #f89406; } -.button-neutral, .button-close { +.button-neutral, .button-success:disabled, .button-close { background-color: #8f8d8c; } - .button-neutral:hover, .button-close:hover, .button-neutral:focus, .button-close:focus { + .button-neutral:hover, .button-success:hover:disabled, .button-close:hover, .button-neutral:focus, .button-success:focus:disabled, .button-close:focus { background-color: #736b68; } .dashboard-sub-bar-menu { From c62b81f5472050c4bbdd512bf30a7bf8074dcf28 Mon Sep 17 00:00:00 2001 From: Heiko Kernbach Date: Thu, 14 Aug 2014 14:53:33 +0200 Subject: [PATCH 22/34] enabled pretty printing in js shell by default --- js/apps/system/aardvark/frontend/js/views/shellView.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/apps/system/aardvark/frontend/js/views/shellView.js b/js/apps/system/aardvark/frontend/js/views/shellView.js index c9f1fa90d2..737ec5b026 100644 --- a/js/apps/system/aardvark/frontend/js/views/shellView.js +++ b/js/apps/system/aardvark/frontend/js/views/shellView.js @@ -27,6 +27,8 @@ self.resize(); }); + this.executeJs("start_pretty_print()"); + return this; }, From 4161a08371ee86f22dc66c6d816d69de5de22605 Mon Sep 17 00:00:00 2001 From: Thomas Schmidts Date: Thu, 14 Aug 2014 15:20:08 +0200 Subject: [PATCH 23/34] Fixed spelling error --- Documentation/Books/Users/Aql/Advanced.mdpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/Books/Users/Aql/Advanced.mdpp b/Documentation/Books/Users/Aql/Advanced.mdpp index 9447066861..27b479e732 100644 --- a/Documentation/Books/Users/Aql/Advanced.mdpp +++ b/Documentation/Books/Users/Aql/Advanced.mdpp @@ -32,11 +32,11 @@ Subqueries may also include other subqueries themselves. !SUBSECTION Variable expansion In order to access a named attribute from all elements in a list easily, AQL -offers the shortcut operator *[\*]* for variable expansion. +offers the shortcut operator [*] for variable expansion. -Using the *[\*]* operator with a variable will iterate over all elements in the +Using the [*] operator with a variable will iterate over all elements in the variable thus allowing to access a particular attribute of each element. It is -required that the expanded variable is a list. The result of the *[\*]* +required that the expanded variable is a list. The result of the [*] operator is again a list. FOR u IN users From 6070acb95436bccd6fe9d9b7d59c6fdcdb2f9add Mon Sep 17 00:00:00 2001 From: Heiko Kernbach Date: Thu, 14 Aug 2014 16:59:50 +0200 Subject: [PATCH 24/34] validation now on focusout instead of keyup event --- js/apps/system/aardvark/frontend/js/views/modalView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/apps/system/aardvark/frontend/js/views/modalView.js b/js/apps/system/aardvark/frontend/js/views/modalView.js index 28b4d9b109..5a833e6fe5 100644 --- a/js/apps/system/aardvark/frontend/js/views/modalView.js +++ b/js/apps/system/aardvark/frontend/js/views/modalView.js @@ -312,7 +312,7 @@ if(r.validateInput) { //catch result of validation and act - $('#' + r.id).on('keyup', function(){ + $('#' + r.id).on('focusout', function(){ var validation = r.validateInput($('#' + r.id)); var error = false, msg; From adfdec9cb81fe9825f0108954bacccd57695d6c2 Mon Sep 17 00:00:00 2001 From: Heiko Kernbach Date: Thu, 14 Aug 2014 17:19:16 +0200 Subject: [PATCH 25/34] changed style of validation in modal views --- js/apps/system/aardvark/frontend/js/templates/modalTable.ejs | 4 ++-- js/apps/system/aardvark/frontend/js/views/modalView.js | 2 ++ js/apps/system/aardvark/frontend/scss/_modals.scss | 3 +++ js/apps/system/aardvark/frontend/scss/generated.css | 2 ++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/js/apps/system/aardvark/frontend/js/templates/modalTable.ejs b/js/apps/system/aardvark/frontend/js/templates/modalTable.ejs index 9bc3a5d308..5464a2d06b 100644 --- a/js/apps/system/aardvark/frontend/js/templates/modalTable.ejs +++ b/js/apps/system/aardvark/frontend/js/templates/modalTable.ejs @@ -7,7 +7,7 @@ } %> - <%=row.label%><%=mandatory%>: +
<%=row.label%><%=mandatory%>:
<% switch(row.type) { @@ -118,4 +118,4 @@ <% } %> - \ No newline at end of file + diff --git a/js/apps/system/aardvark/frontend/js/views/modalView.js b/js/apps/system/aardvark/frontend/js/views/modalView.js index 5a833e6fe5..273ebc944c 100644 --- a/js/apps/system/aardvark/frontend/js/views/modalView.js +++ b/js/apps/system/aardvark/frontend/js/views/modalView.js @@ -342,6 +342,7 @@ $('#' + r.id).addClass('invalid-input'); $('.modal-footer .button-success').prop('disabled', true); $('.modal-footer .button-success').addClass('disabled'); + $('#' + r.id).parent().prev().find("div").addClass("collectionThDiv"); if (errorElement) { //error element available @@ -358,6 +359,7 @@ $('#' + r.id).removeClass('invalid-input'); $('.modal-footer .button-success').prop('disabled', false); $('.modal-footer .button-success').removeClass('disabled'); + $('#' + r.id).parent().prev().find("div").removeClass("collectionThDiv"); if (errorElement) { $(errorElement).remove(); } diff --git a/js/apps/system/aardvark/frontend/scss/_modals.scss b/js/apps/system/aardvark/frontend/scss/_modals.scss index 47d0a498d2..f3118a7040 100644 --- a/js/apps/system/aardvark/frontend/scss/_modals.scss +++ b/js/apps/system/aardvark/frontend/scss/_modals.scss @@ -90,6 +90,9 @@ margin-top: 10px; } + .collectionThDiv { + margin-bottom: 20px; + } } .icon-info-sign { diff --git a/js/apps/system/aardvark/frontend/scss/generated.css b/js/apps/system/aardvark/frontend/scss/generated.css index fd21840ce1..fc17196233 100644 --- a/js/apps/system/aardvark/frontend/scss/generated.css +++ b/js/apps/system/aardvark/frontend/scss/generated.css @@ -4421,6 +4421,8 @@ div.breadcrumb a.disabledBread { .modal-body th div.select2-container { margin-bottom: 10px; margin-top: 10px; } + .modal-body th .collectionThDiv { + margin-bottom: 20px; } .modal-body .icon-info-sign { margin-bottom: 10px; margin-left: 10px; From dd226830c22202a311047aaba61a944d53bcf535 Mon Sep 17 00:00:00 2001 From: Heiko Kernbach Date: Thu, 14 Aug 2014 17:57:37 +0200 Subject: [PATCH 26/34] design changes modal validation, brighter checking of values --- .../aardvark/frontend/js/templates/modalTable.ejs | 2 +- .../system/aardvark/frontend/js/views/modalView.js | 12 ++++++++---- js/apps/system/aardvark/frontend/scss/_modals.scss | 10 ++++++---- js/apps/system/aardvark/frontend/scss/generated.css | 7 ++++--- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/js/apps/system/aardvark/frontend/js/templates/modalTable.ejs b/js/apps/system/aardvark/frontend/js/templates/modalTable.ejs index 5464a2d06b..62faf8aa1c 100644 --- a/js/apps/system/aardvark/frontend/js/templates/modalTable.ejs +++ b/js/apps/system/aardvark/frontend/js/templates/modalTable.ejs @@ -7,7 +7,7 @@ } %> -
<%=row.label%><%=mandatory%>:
+ <%=row.label%><%=mandatory%>: <% switch(row.type) { diff --git a/js/apps/system/aardvark/frontend/js/views/modalView.js b/js/apps/system/aardvark/frontend/js/views/modalView.js index 273ebc944c..b9c44b341e 100644 --- a/js/apps/system/aardvark/frontend/js/views/modalView.js +++ b/js/apps/system/aardvark/frontend/js/views/modalView.js @@ -312,7 +312,7 @@ if(r.validateInput) { //catch result of validation and act - $('#' + r.id).on('focusout', function(){ + $('#' + r.id).on('keyup focusout', function(e){ var validation = r.validateInput($('#' + r.id)); var error = false, msg; @@ -323,8 +323,14 @@ toCheck: validator.rule }); + var valueToCheck = $('#' + r.id).val(); + + if (valueToCheck === '' && e.type === "keyup") { + return; + } + Joi.validate({ - toCheck: $('#' + r.id).val() + toCheck: valueToCheck }, schema, function (err, value) { @@ -342,7 +348,6 @@ $('#' + r.id).addClass('invalid-input'); $('.modal-footer .button-success').prop('disabled', true); $('.modal-footer .button-success').addClass('disabled'); - $('#' + r.id).parent().prev().find("div").addClass("collectionThDiv"); if (errorElement) { //error element available @@ -359,7 +364,6 @@ $('#' + r.id).removeClass('invalid-input'); $('.modal-footer .button-success').prop('disabled', false); $('.modal-footer .button-success').removeClass('disabled'); - $('#' + r.id).parent().prev().find("div").removeClass("collectionThDiv"); if (errorElement) { $(errorElement).remove(); } diff --git a/js/apps/system/aardvark/frontend/scss/_modals.scss b/js/apps/system/aardvark/frontend/scss/_modals.scss index f3118a7040..8e6f87f056 100644 --- a/js/apps/system/aardvark/frontend/scss/_modals.scss +++ b/js/apps/system/aardvark/frontend/scss/_modals.scss @@ -90,9 +90,6 @@ margin-top: 10px; } - .collectionThDiv { - margin-bottom: 20px; - } } .icon-info-sign { @@ -162,6 +159,10 @@ width: 398px; } + .collectionTh { + height: 55px; + } + .tab-content { min-height: 200px; } @@ -170,7 +171,8 @@ color: red; font-size: 9pt; margin-bottom: 5px; - margin-top: -5px; + margin-top: -9px; + position: absolute; } } diff --git a/js/apps/system/aardvark/frontend/scss/generated.css b/js/apps/system/aardvark/frontend/scss/generated.css index fc17196233..1e44888108 100644 --- a/js/apps/system/aardvark/frontend/scss/generated.css +++ b/js/apps/system/aardvark/frontend/scss/generated.css @@ -4421,8 +4421,6 @@ div.breadcrumb a.disabledBread { .modal-body th div.select2-container { margin-bottom: 10px; margin-top: 10px; } - .modal-body th .collectionThDiv { - margin-bottom: 20px; } .modal-body .icon-info-sign { margin-bottom: 10px; margin-left: 10px; @@ -4458,13 +4456,16 @@ div.breadcrumb a.disabledBread { width: 384px; } .modal-body select { width: 398px; } + .modal-body .collectionTh { + height: 55px; } .modal-body .tab-content { min-height: 200px; } .modal-body .errorMessage { color: red; font-size: 9pt; margin-bottom: 5px; - margin-top: -5px; } + margin-top: -9px; + position: absolute; } .modal-text { font-weight: 300; From 6f5343bc8196ffbdc68f4f033a64ab5b5ec81ad2 Mon Sep 17 00:00:00 2001 From: Frank Celler Date: Fri, 15 Aug 2014 09:41:59 +0200 Subject: [PATCH 27/34] raised version number --- build.h | 2 +- configure.ac | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.h b/build.h index 296acb248c..83d424a33e 100644 --- a/build.h +++ b/build.h @@ -1 +1 @@ -#define TRI_VERSION "2.2.0-devel" +#define TRI_VERSION "2.3.0-devel" diff --git a/configure.ac b/configure.ac index 5901ae595b..e31c2ad49d 100644 --- a/configure.ac +++ b/configure.ac @@ -6,7 +6,7 @@ dnl ============================================================================ dnl --SECTION-- triAGENS GmbH Build Environment dnl ============================================================================ -AC_INIT([triAGENS ArangoDB], [2.2.0-devel], [info@triagens.de], [arangodb], [http://www.arangodb.org]) +AC_INIT([triAGENS ArangoDB], [2.3.0-devel], [info@triagens.de], [arangodb], [http://www.arangodb.org]) dnl ---------------------------------------------------------------------------- dnl auxillary directory for install-sh and missing From 415fefdafad9c26d9b45cdb12ec507ec3a7d2c77 Mon Sep 17 00:00:00 2001 From: Thomas Schmidts Date: Fri, 15 Aug 2014 11:01:56 +0200 Subject: [PATCH 28/34] Fixed small formatation error --- arangod/V8Server/v8-vocbase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arangod/V8Server/v8-vocbase.cpp b/arangod/V8Server/v8-vocbase.cpp index 5f06e16fe9..e24d291e42 100644 --- a/arangod/V8Server/v8-vocbase.cpp +++ b/arangod/V8Server/v8-vocbase.cpp @@ -5833,7 +5833,7 @@ static v8::Handle JS_DatafileScanVocbaseCol (v8::Arguments const& arg /// ], /// "isNewlyCreated" : true /// } -/// ```js +/// ``` /// /// @endDocuBlock //////////////////////////////////////////////////////////////////////////////// From c5c1c08098c9cd3ff6fbcf71b4584f50e3db2f96 Mon Sep 17 00:00:00 2001 From: Heiko Kernbach Date: Fri, 15 Aug 2014 13:46:49 +0200 Subject: [PATCH 29/34] fixed wrong font rendering, safari browser --- js/apps/system/aardvark/frontend/scss/_navbar.scss | 3 +++ js/apps/system/aardvark/frontend/scss/generated.css | 10 ++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/js/apps/system/aardvark/frontend/scss/_navbar.scss b/js/apps/system/aardvark/frontend/scss/_navbar.scss index 54afb80471..3fed9ded1a 100644 --- a/js/apps/system/aardvark/frontend/scss/_navbar.scss +++ b/js/apps/system/aardvark/frontend/scss/_navbar.scss @@ -1,4 +1,7 @@ .navbar { + + -webkit-font-smoothing: subpixel-antialiased; + .nav { li.dropdown { .active > .dropdown-toggle, diff --git a/js/apps/system/aardvark/frontend/scss/generated.css b/js/apps/system/aardvark/frontend/scss/generated.css index 1e44888108..b8cb6a639a 100644 --- a/js/apps/system/aardvark/frontend/scss/generated.css +++ b/js/apps/system/aardvark/frontend/scss/generated.css @@ -1512,10 +1512,12 @@ ul.link-dropdown-menu, ul.user-dropdown-menu, ul.gv-dropdown-menu { right: 8px; top: -6px; } -.navbar .nav li.dropdown .active > .dropdown-toggle, -.navbar .nav li.dropdown .open > .dropdown-toggle, -.navbar .nav li.dropdown .open.active > .dropdown-toggle { - background: #788f3d; } +.navbar { + -webkit-font-smoothing: subpixel-antialiased; } + .navbar .nav li.dropdown .active > .dropdown-toggle, + .navbar .nav li.dropdown .open > .dropdown-toggle, + .navbar .nav li.dropdown .open.active > .dropdown-toggle { + background: #788f3d; } nav.navbar { height: 38px; From 98d475c103ebe03a38b294013ad88c13d4be3f52 Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Fri, 15 Aug 2014 15:47:42 +0200 Subject: [PATCH 30/34] Fixed deleting queues/jobs. --- js/server/modules/org/arangodb/foxx/queues.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/server/modules/org/arangodb/foxx/queues.js b/js/server/modules/org/arangodb/foxx/queues.js index 9f6bfc7af0..83d2d23007 100644 --- a/js/server/modules/org/arangodb/foxx/queues.js +++ b/js/server/modules/org/arangodb/foxx/queues.js @@ -91,7 +91,7 @@ queues = { }, action: function () { if (db._queues.exists(key)) { - db._queues.delete(key); + db._queues.remove(key); result = true; } } @@ -264,7 +264,7 @@ _.extend(Queue.prototype, { }, action: function () { if (db._jobs.exists(id)) { - db._jobs.delete(id); + db._jobs.remove(id); result = true; } } From 2a2c76770cb0348441de1a4000637ac5d7977369 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Fri, 15 Aug 2014 17:35:46 +0200 Subject: [PATCH 31/34] need to ALWAYS create a new environment. Otherwise results are somewhat non-deterministic --- js/common/modules/jasmine.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/common/modules/jasmine.js b/js/common/modules/jasmine.js index 592e079c2e..f1d5412826 100755 --- a/js/common/modules/jasmine.js +++ b/js/common/modules/jasmine.js @@ -42,7 +42,7 @@ jasmine.getGlobal().clearTimeout = function (timeoutId) { exports.executeTestSuite = function (specFileNames, options) { 'use strict'; - var sandbox = jasmine.getEnv(), + var sandbox = new jasmine.Env(), format = options.format || 'progress'; // Explicitly add require From 828fd9cc76b34033300486697434b29b290fc63a Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Fri, 15 Aug 2014 17:37:44 +0200 Subject: [PATCH 32/34] set waitForSync to false for system collections --- js/server/upgrade-database.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/server/upgrade-database.js b/js/server/upgrade-database.js index 6e22754bd0..62c108087e 100644 --- a/js/server/upgrade-database.js +++ b/js/server/upgrade-database.js @@ -1313,7 +1313,7 @@ database: [ DATABASE_INIT, DATABASE_UPGRADE ], task: function () { - return createSystemCollection("_queues", { waitForSync : true }); + return createSystemCollection("_queues"); } }); @@ -1332,7 +1332,7 @@ database: [ DATABASE_INIT, DATABASE_UPGRADE ], task: function () { - return createSystemCollection("_jobs", { waitForSync : true }); + return createSystemCollection("_jobs"); } }); From a97417408a2419f11e2e2948b727b3b323fd3ebb Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Fri, 15 Aug 2014 17:43:49 +0200 Subject: [PATCH 33/34] Applied performance optimization for queues. --- js/server/modules/org/arangodb/foxx/queues.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/js/server/modules/org/arangodb/foxx/queues.js b/js/server/modules/org/arangodb/foxx/queues.js index 83d2d23007..9647979b96 100644 --- a/js/server/modules/org/arangodb/foxx/queues.js +++ b/js/server/modules/org/arangodb/foxx/queues.js @@ -55,10 +55,11 @@ queues = { _jobTypes: Object.create(null), get: function (key) { 'use strict'; - if (!db._queues.exists(key)) { - throw new Error('Queue does not exist: ' + key); - } + if (!queueMap[key]) { + if (!db._queues.exists(key)) { + throw new Error('Queue does not exist: ' + key); + } queueMap[key] = new Queue(key); } return queueMap[key]; @@ -256,20 +257,20 @@ _.extend(Queue.prototype, { }, delete: function (id) { 'use strict'; - var result = false; - db._executeTransaction({ + return db._executeTransaction({ collections: { read: ['_jobs'], write: ['_jobs'] }, action: function () { - if (db._jobs.exists(id)) { + try { db._jobs.remove(id); - result = true; + return true; + } catch (err) { + return false; } } }); - return result; }, pending: function (jobType) { 'use strict'; From cd575e073f0fff93ca2a29bd9915cb1ee91f91d7 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Fri, 15 Aug 2014 18:19:52 +0200 Subject: [PATCH 34/34] fixed tests --- .../HttpInterface/api-compatibility-spec.rb | 8 ++-- .../HttpInterface/api-replication-spec.rb | 44 ++++++++++--------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/UnitTests/HttpInterface/api-compatibility-spec.rb b/UnitTests/HttpInterface/api-compatibility-spec.rb index 465c12d1f1..eae2a85961 100644 --- a/UnitTests/HttpInterface/api-compatibility-spec.rb +++ b/UnitTests/HttpInterface/api-compatibility-spec.rb @@ -16,7 +16,7 @@ describe ArangoDB do doc.code.should eq(200) compatibility = doc.parsed_response['compatibility'] compatibility.should be_kind_of(Integer) - compatibility.should eq(20200) + compatibility.should eq(20300) end it "tests the compatibility value when a broken header is set" do @@ -28,7 +28,7 @@ describe ArangoDB do doc.code.should eq(200) compatibility = doc.parsed_response['compatibility'] compatibility.should be_kind_of(Integer) - compatibility.should eq(20200) + compatibility.should eq(20300) end end @@ -124,12 +124,12 @@ describe ArangoDB do end it "tests the compatibility value when a too high version is set" do - doc = ArangoDB.get("/_admin/echo", :headers => { "x-arango-version" => "2.3" }) + doc = ArangoDB.get("/_admin/echo", :headers => { "x-arango-version" => "2.4" }) doc.code.should eq(200) compatibility = doc.parsed_response['compatibility'] compatibility.should be_kind_of(Integer) - compatibility.should eq(20300) + compatibility.should eq(20400) end end diff --git a/UnitTests/HttpInterface/api-replication-spec.rb b/UnitTests/HttpInterface/api-replication-spec.rb index dc13db1aa6..2d7218cfc0 100644 --- a/UnitTests/HttpInterface/api-replication-spec.rb +++ b/UnitTests/HttpInterface/api-replication-spec.rb @@ -230,7 +230,7 @@ describe ArangoDB do c["waitForSync"].should eq(true) end - it "fetches some collection operations the follow log" do + it "fetches some collection operations from the follow log" do ArangoDB.drop_collection("UnitTestsReplication") sleep 1 @@ -282,28 +282,30 @@ describe ArangoDB do document = JSON.parse(part) if i == 0 - # create collection - document.should have_key("tick") - document.should have_key("type") - document.should have_key("cid") - document.should have_key("collection") + if document["type"] == 2000 and document["cid"] == cid + # create collection + document.should have_key("tick") + document.should have_key("type") + document.should have_key("cid") + document.should have_key("collection") - document["tick"].should match(/^\d+$/) - document["tick"].to_i.should >= fromTick.to_i - document["type"].should eq(2000) - document["cid"].should eq(cid) + document["tick"].should match(/^\d+$/) + document["tick"].to_i.should >= fromTick.to_i + document["type"].should eq(2000) + document["cid"].should eq(cid) - c = document["collection"] - c.should have_key("version") - c["type"].should eq(2) - c["cid"].should eq(cid) - c["deleted"].should eq(false) - c["doCompact"].should eq(true) - c.should have_key("maximalSize") - c["maximalSize"].should be_kind_of(Integer) - c["name"].should eq("UnitTestsReplication") - c["isVolatile"].should eq(false) - c["waitForSync"].should eq(true) + c = document["collection"] + c.should have_key("version") + c["type"].should eq(2) + c["cid"].should eq(cid) + c["deleted"].should eq(false) + c["doCompact"].should eq(true) + c.should have_key("maximalSize") + c["maximalSize"].should be_kind_of(Integer) + c["name"].should eq("UnitTestsReplication") + c["isVolatile"].should eq(false) + c["waitForSync"].should eq(true) + end elsif i == 1 # create document