From e642ce972f42b9445f9a54ed7b6b2e17895b7e65 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Mon, 10 Aug 2015 17:08:41 +0200 Subject: [PATCH 1/7] throw error if collection not yet loaded --- arangod/VocBase/vocbase.cpp | 11 ++++++----- .../aardvark/APP/frontend/js/bootstrap/errors.js | 1 + js/common/bootstrap/errors.js | 1 + lib/Basics/errors.dat | 1 + lib/Basics/voc-errors.cpp | 1 + lib/Basics/voc-errors.h | 12 ++++++++++++ 6 files changed, 22 insertions(+), 5 deletions(-) diff --git a/arangod/VocBase/vocbase.cpp b/arangod/VocBase/vocbase.cpp index 3acf6c60b7..02d6715dfc 100644 --- a/arangod/VocBase/vocbase.cpp +++ b/arangod/VocBase/vocbase.cpp @@ -989,7 +989,7 @@ static int ScanPath (TRI_vocbase_t* vocbase, //////////////////////////////////////////////////////////////////////////////// /// @brief loads an existing (document) collection /// -/// Note that this will READ lock the collection you have to release the +/// Note that this will READ lock the collection. You have to release the /// collection lock by yourself. //////////////////////////////////////////////////////////////////////////////// @@ -1076,7 +1076,7 @@ static int LoadCollectionVocBase (TRI_vocbase_t* vocbase, // currently loading if (collection->_status == TRI_VOC_COL_STATUS_LOADING) { // loop until the status changes - while (1) { + while (true) { TRI_vocbase_col_status_e status = collection->_status; TRI_WRITE_UNLOCK_STATUS_VOCBASE_COL(collection); @@ -1084,6 +1084,9 @@ static int LoadCollectionVocBase (TRI_vocbase_t* vocbase, if (status != TRI_VOC_COL_STATUS_LOADING) { break; } + + return TRI_ERROR_ARANGO_COLLECTION_NOT_LOADED; + usleep(COLLECTION_STATUS_POLL_INTERVAL); TRI_WRITE_LOCK_STATUS_VOCBASE_COL(collection); @@ -1094,8 +1097,6 @@ static int LoadCollectionVocBase (TRI_vocbase_t* vocbase, // unloaded, load collection if (collection->_status == TRI_VOC_COL_STATUS_UNLOADED) { - TRI_document_collection_t* document; - // set the status to loading collection->_status = TRI_VOC_COL_STATUS_LOADING; @@ -1105,7 +1106,7 @@ static int LoadCollectionVocBase (TRI_vocbase_t* vocbase, // disk activity, index creation etc.) TRI_WRITE_UNLOCK_STATUS_VOCBASE_COL(collection); - document = TRI_OpenDocumentCollection(vocbase, collection, IGNORE_DATAFILE_ERRORS); + TRI_document_collection_t* document = TRI_OpenDocumentCollection(vocbase, collection, IGNORE_DATAFILE_ERRORS); // lock again the adjust the status TRI_WRITE_LOCK_STATUS_VOCBASE_COL(collection); diff --git a/js/apps/system/_admin/aardvark/APP/frontend/js/bootstrap/errors.js b/js/apps/system/_admin/aardvark/APP/frontend/js/bootstrap/errors.js index cafe37be79..62f2407ceb 100644 --- a/js/apps/system/_admin/aardvark/APP/frontend/js/bootstrap/errors.js +++ b/js/apps/system/_admin/aardvark/APP/frontend/js/bootstrap/errors.js @@ -105,6 +105,7 @@ "ERROR_ARANGO_INDEX_CREATION_FAILED" : { "code" : 1235, "message" : "index creation failed" }, "ERROR_ARANGO_WRITE_THROTTLE_TIMEOUT" : { "code" : 1236, "message" : "write-throttling timeout" }, "ERROR_ARANGO_COLLECTION_TYPE_MISMATCH" : { "code" : 1237, "message" : "collection type mismatch" }, + "ERROR_ARANGO_COLLECTION_NOT_LOADED" : { "code" : 1238, "message" : "collection not loaded" }, "ERROR_ARANGO_DATAFILE_FULL" : { "code" : 1300, "message" : "datafile full" }, "ERROR_ARANGO_EMPTY_DATADIR" : { "code" : 1301, "message" : "server database directory is empty" }, "ERROR_REPLICATION_NO_RESPONSE" : { "code" : 1400, "message" : "no response" }, diff --git a/js/common/bootstrap/errors.js b/js/common/bootstrap/errors.js index cafe37be79..62f2407ceb 100644 --- a/js/common/bootstrap/errors.js +++ b/js/common/bootstrap/errors.js @@ -105,6 +105,7 @@ "ERROR_ARANGO_INDEX_CREATION_FAILED" : { "code" : 1235, "message" : "index creation failed" }, "ERROR_ARANGO_WRITE_THROTTLE_TIMEOUT" : { "code" : 1236, "message" : "write-throttling timeout" }, "ERROR_ARANGO_COLLECTION_TYPE_MISMATCH" : { "code" : 1237, "message" : "collection type mismatch" }, + "ERROR_ARANGO_COLLECTION_NOT_LOADED" : { "code" : 1238, "message" : "collection not loaded" }, "ERROR_ARANGO_DATAFILE_FULL" : { "code" : 1300, "message" : "datafile full" }, "ERROR_ARANGO_EMPTY_DATADIR" : { "code" : 1301, "message" : "server database directory is empty" }, "ERROR_REPLICATION_NO_RESPONSE" : { "code" : 1400, "message" : "no response" }, diff --git a/lib/Basics/errors.dat b/lib/Basics/errors.dat index 4eac289dc3..f99b9eb26e 100755 --- a/lib/Basics/errors.dat +++ b/lib/Basics/errors.dat @@ -125,6 +125,7 @@ ERROR_ARANGO_INDEX_DOCUMENT_ATTRIBUTE_MISSING,1234,"index insertion warning - at ERROR_ARANGO_INDEX_CREATION_FAILED,1235,"index creation failed","Will be raised when an attempt to create an index has failed." ERROR_ARANGO_WRITE_THROTTLE_TIMEOUT,1236,"write-throttling timeout","Will be raised when the server is write-throttled and a write operation has waited too long for the server to process queued operations." ERROR_ARANGO_COLLECTION_TYPE_MISMATCH,1237,"collection type mismatch","Will be raised when a collection has a different type from what has been expected." +ERROR_ARANGO_COLLECTION_NOT_LOADED,1238,"collection not loaded","Will be raised when a collection is accessed that is not yet loaded." ################################################################################ ## ArangoDB storage errors diff --git a/lib/Basics/voc-errors.cpp b/lib/Basics/voc-errors.cpp index b068beeeb2..8ed814ff6e 100644 --- a/lib/Basics/voc-errors.cpp +++ b/lib/Basics/voc-errors.cpp @@ -101,6 +101,7 @@ void TRI_InitialiseErrorMessages () { REG_ERROR(ERROR_ARANGO_INDEX_CREATION_FAILED, "index creation failed"); REG_ERROR(ERROR_ARANGO_WRITE_THROTTLE_TIMEOUT, "write-throttling timeout"); REG_ERROR(ERROR_ARANGO_COLLECTION_TYPE_MISMATCH, "collection type mismatch"); + REG_ERROR(ERROR_ARANGO_COLLECTION_NOT_LOADED, "collection not loaded"); REG_ERROR(ERROR_ARANGO_DATAFILE_FULL, "datafile full"); REG_ERROR(ERROR_ARANGO_EMPTY_DATADIR, "server database directory is empty"); REG_ERROR(ERROR_REPLICATION_NO_RESPONSE, "no response"); diff --git a/lib/Basics/voc-errors.h b/lib/Basics/voc-errors.h index 7d3ff18b71..6d48576239 100644 --- a/lib/Basics/voc-errors.h +++ b/lib/Basics/voc-errors.h @@ -226,6 +226,8 @@ /// - 1237: @LIT{collection type mismatch} /// Will be raised when a collection has a different type from what has been /// expected. +/// - 1238: @LIT{collection not loaded} +/// Will be raised when a collection is accessed that is not yet loaded. /// - 1300: @LIT{datafile full} /// Will be raised when the datafile reaches its limit. /// - 1301: @LIT{server database directory is empty} @@ -1642,6 +1644,16 @@ void TRI_InitialiseErrorMessages (); #define TRI_ERROR_ARANGO_COLLECTION_TYPE_MISMATCH (1237) +//////////////////////////////////////////////////////////////////////////////// +/// @brief 1238: ERROR_ARANGO_COLLECTION_NOT_LOADED +/// +/// collection not loaded +/// +/// Will be raised when a collection is accessed that is not yet loaded. +//////////////////////////////////////////////////////////////////////////////// + +#define TRI_ERROR_ARANGO_COLLECTION_NOT_LOADED (1238) + //////////////////////////////////////////////////////////////////////////////// /// @brief 1300: ERROR_ARANGO_DATAFILE_FULL /// From 434d6167dba99d2cded370587b8152586123dc83 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Tue, 11 Aug 2015 12:31:42 +0200 Subject: [PATCH 2/7] added configuration option `--database.throw-collection-not-loaded-error` --- CHANGELOG | 23 ++ .../Books/Users/ConfigureArango/Arangod.mdpp | 4 + UnitTests/Makefile.unittests | 3 +- arangod/RestServer/ArangoServer.cpp | 4 + arangod/RestServer/ArangoServer.h | 33 +++ arangod/V8Server/v8-vocbase.cpp | 30 ++ arangod/VocBase/vocbase.cpp | 30 +- arangod/VocBase/vocbase.h | 13 + js/server/bootstrap/module-internal.js | 9 + ...tion-not-loaded-timecritical-noncluster.js | 256 ++++++++++++++++++ 10 files changed, 402 insertions(+), 3 deletions(-) create mode 100644 js/server/tests/shell-collection-not-loaded-timecritical-noncluster.js diff --git a/CHANGELOG b/CHANGELOG index 453fc59112..49efb67eda 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,29 @@ v2.7.0 (XXXX-XX-XX) ------------------- +* added startup option `--database.throw-collection-not-loaded-error` + + Accessing a not-yet loaded collection will automatically load a collection + on first access. This flag controls what happens in case an operation + would need to wait for another thread to finalize loading a collection. If + set to *true*, then the first operation that accesses an unloaded collection + will load it. Further threads that try to access the same collection while + it is still loading will get an error (1238, *collection not loaded*). When + the initial operation has completed loading the collection, all operations + on the collection can be carried out normally, and error 1238 will not be + thrown. + + If set to *false*, the first thread that accesses a not-yet loaded collection + will still load it. Other threads that try to access the collection while + loading will not fail with error 1238 but instead block until the collection + is fully loaded. This configuration might lead to all server threads being + blocked because they are all waiting for the same collection to complete + loading. Setting the option to *true* will prevent this from happening, but + requires clients to catch error 1238 and react on it (maybe by scheduling + a retry for later). + + The default value is *false*. + * increased default value collection-specific `indexBuckets` value from 1 to 16 Collections created from 2.7 on will use the new default if not overriden on diff --git a/Documentation/Books/Users/ConfigureArango/Arangod.mdpp b/Documentation/Books/Users/ConfigureArango/Arangod.mdpp index 35242765b9..b7562b3b1c 100644 --- a/Documentation/Books/Users/ConfigureArango/Arangod.mdpp +++ b/Documentation/Books/Users/ConfigureArango/Arangod.mdpp @@ -107,6 +107,10 @@ the option *--disable-figures*. @startDocuBlock databaseDisableQueryTracking +!SUBSECTION Throw collection not loaded error +@startDocuBlock databaseThrowCollectionNotLoadedError + + !SUBSECTION AQL Query caching mode @startDocuBlock queryCacheMode diff --git a/UnitTests/Makefile.unittests b/UnitTests/Makefile.unittests index f87bab7cb6..1467f62db5 100755 --- a/UnitTests/Makefile.unittests +++ b/UnitTests/Makefile.unittests @@ -482,7 +482,8 @@ SHELL_COMMON = \ ################################################################################ SHELL_SERVER_ONLY = \ - @top_srcdir@/js/server/tests/shell-readonly-noncluster-disabled.js\ + @top_srcdir@/js/server/tests/shell-readonly-noncluster-disabled.js \ + @top_srcdir@/js/server/tests/shell-collection-not-loaded-timecritical-noncluster.js \ @top_srcdir@/js/server/tests/shell-wal-noncluster-memoryintense.js \ @top_srcdir@/js/server/tests/shell-sharding-helpers.js \ @top_srcdir@/js/server/tests/shell-compaction-noncluster-timecritical.js \ diff --git a/arangod/RestServer/ArangoServer.cpp b/arangod/RestServer/ArangoServer.cpp index 2e2e48e95e..ca90fe908b 100644 --- a/arangod/RestServer/ArangoServer.cpp +++ b/arangod/RestServer/ArangoServer.cpp @@ -354,6 +354,7 @@ ArangoServer::ArangoServer (int argc, char** argv) _ignoreDatafileErrors(false), _disableReplicationApplier(false), _disableQueryTracking(false), + _throwCollectionNotLoadedError(false), _foxxQueues(true), _foxxQueuesPollInterval(1.0), _server(nullptr), @@ -576,6 +577,7 @@ void ArangoServer::buildApplicationServer () { ("database.query-cache-mode", &_queryCacheMode, "mode for the AQL query cache (on, off, demand)") ("database.query-cache-max-results", &_queryCacheMaxResults, "maximum number of results in query cache per database") ("database.index-threads", &_indexThreads, "threads to start for parallel background index creation") + ("database.throw-collection-not-loaded-error", &_throwCollectionNotLoadedError, "throw an error when accessing a collection that is still loading") ; // ............................................................................. @@ -715,6 +717,8 @@ void ArangoServer::buildApplicationServer () { // testing disables authentication _disableAuthentication = true; } + + TRI_SetThrowCollectionNotLoadedVocBase(nullptr, _throwCollectionNotLoadedError); // set global query tracking flag triagens::aql::Query::DisableQueryTracking(_disableQueryTracking); diff --git a/arangod/RestServer/ArangoServer.h b/arangod/RestServer/ArangoServer.h index b9bc3838fd..396e4780ac 100644 --- a/arangod/RestServer/ArangoServer.h +++ b/arangod/RestServer/ArangoServer.h @@ -367,6 +367,9 @@ namespace triagens { /// are shared among multiple collections and databases. Specifying a value of /// *0* will turn off parallel building, meaning that indexes for each collection /// are built sequentially by the thread that opened the collection. +/// If the number of index threads is greater than 1, it will also be used to +/// built the edge index of a collection in parallel (this also requires the +/// edge index in the collection to be split into multiple buckets). /// @endDocuBlock //////////////////////////////////////////////////////////////////////////////// @@ -554,6 +557,36 @@ namespace triagens { bool _disableQueryTracking; +//////////////////////////////////////////////////////////////////////////////// +/// @brief throw collection not loaded error +/// @startDocuBlock databaseThrowCollectionNotLoadedError +/// `--database.throw-collection-not-loaded-error flag` +/// +/// Accessing a not-yet loaded collection will automatically load a collection +/// on first access. This flag controls what happens in case an operation +/// would need to wait for another thread to finalize loading a collection. If +/// set to *true*, then the first operation that accesses an unloaded collection +/// will load it. Further threads that try to access the same collection while +/// it is still loading will get an error (1238, *collection not loaded*). When +/// the initial operation has completed loading the collection, all operations +/// on the collection can be carried out normally, and error 1238 will not be +/// thrown. +/// +/// If set to *false*, the first thread that accesses a not-yet loaded collection +/// will still load it. Other threads that try to access the collection while +/// loading will not fail with error 1238 but instead block until the collection +/// is fully loaded. This configuration might lead to all server threads being +/// blocked because they are all waiting for the same collection to complete +/// loading. Setting the option to *true* will prevent this from happening, but +/// requires clients to catch error 1238 and react on it (maybe by scheduling +/// a retry for later). +/// +/// The default value is *false*. +/// @endDocuBlock +//////////////////////////////////////////////////////////////////////////////// + + bool _throwCollectionNotLoadedError; + //////////////////////////////////////////////////////////////////////////////// /// @brief enable or disable the Foxx queues feature /// @startDocuBlock foxxQueues diff --git a/arangod/V8Server/v8-vocbase.cpp b/arangod/V8Server/v8-vocbase.cpp index c369291d4a..b62f2c457a 100644 --- a/arangod/V8Server/v8-vocbase.cpp +++ b/arangod/V8Server/v8-vocbase.cpp @@ -1602,6 +1602,34 @@ static void JS_QueryCacheInvalidateAql (const v8::FunctionCallbackInfo& args) { + TRI_V8_TRY_CATCH_BEGIN(isolate); + v8::HandleScope scope(isolate); + + TRI_vocbase_t* vocbase = GetContextVocBase(isolate); + + if (vocbase == nullptr) { + TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND); + } + + if (args.Length() == 0) { + bool value = TRI_GetThrowCollectionNotLoadedVocBase(vocbase); + TRI_V8_RETURN(v8::Boolean::New(isolate, value)); + } + else if (args.Length() == 1) { + TRI_SetThrowCollectionNotLoadedVocBase(vocbase, TRI_ObjectToBoolean(args[0])); + } + else { + TRI_V8_THROW_EXCEPTION_USAGE("THROW_COLLECTION_NOT_LOADED()"); + } + + TRI_V8_TRY_CATCH_END +} + //////////////////////////////////////////////////////////////////////////////// /// @brief Transforms VertexId to v8String //////////////////////////////////////////////////////////////////////////////// @@ -3814,6 +3842,8 @@ void TRI_InitV8VocBridge (v8::Isolate* isolate, TRI_AddGlobalFunctionVocbase(isolate, context, TRI_V8_ASCII_STRING("AQL_QUERY_CACHE_PROPERTIES"), JS_QueryCachePropertiesAql, true); TRI_AddGlobalFunctionVocbase(isolate, context, TRI_V8_ASCII_STRING("AQL_QUERY_CACHE_INVALIDATE"), JS_QueryCacheInvalidateAql, true); + TRI_AddGlobalFunctionVocbase(isolate, context, TRI_V8_ASCII_STRING("THROW_COLLECTION_NOT_LOADED"), JS_ThrowCollectionNotLoaded, true); + TRI_AddGlobalFunctionVocbase(isolate, context, TRI_V8_ASCII_STRING("CPP_SHORTEST_PATH"), JS_QueryShortestPath, true); TRI_AddGlobalFunctionVocbase(isolate, context, TRI_V8_ASCII_STRING("CPP_NEIGHBORS"), JS_QueryNeighbors, true); diff --git a/arangod/VocBase/vocbase.cpp b/arangod/VocBase/vocbase.cpp index 02d6715dfc..6f1d173e28 100644 --- a/arangod/VocBase/vocbase.cpp +++ b/arangod/VocBase/vocbase.cpp @@ -72,11 +72,17 @@ #define COLLECTION_STATUS_POLL_INTERVAL (1000 * 10) // ----------------------------------------------------------------------------- -// --SECTION-- private types +// --SECTION-- private variables // ----------------------------------------------------------------------------- static std::atomic QueryId(1); +static std::atomic ThrowCollectionNotLoaded(false); + +// ----------------------------------------------------------------------------- +// --SECTION-- private types +// ----------------------------------------------------------------------------- + //////////////////////////////////////////////////////////////////////////////// /// @brief auxiliary struct for index iteration //////////////////////////////////////////////////////////////////////////////// @@ -1085,7 +1091,10 @@ static int LoadCollectionVocBase (TRI_vocbase_t* vocbase, break; } - return TRI_ERROR_ARANGO_COLLECTION_NOT_LOADED; + // only throw this particular error if the server is configured to do so + if (ThrowCollectionNotLoaded.load(std::memory_order_relaxed)) { + return TRI_ERROR_ARANGO_COLLECTION_NOT_LOADED; + } usleep(COLLECTION_STATUS_POLL_INTERVAL); @@ -2332,6 +2341,23 @@ TRI_voc_tick_t TRI_NextQueryIdVocBase (TRI_vocbase_t* vocbase) { return QueryId.fetch_add(1, std::memory_order_seq_cst); } +//////////////////////////////////////////////////////////////////////////////// +/// @brief gets the "throw collection not loaded error" +//////////////////////////////////////////////////////////////////////////////// + +bool TRI_GetThrowCollectionNotLoadedVocBase (TRI_vocbase_t* vocbase) { + return ThrowCollectionNotLoaded.load(std::memory_order_seq_cst); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief sets the "throw collection not loaded error" +//////////////////////////////////////////////////////////////////////////////// + +void TRI_SetThrowCollectionNotLoadedVocBase (TRI_vocbase_t* vocbase, + bool value) { + ThrowCollectionNotLoaded.store(value, std::memory_order_seq_cst); +} + // ----------------------------------------------------------------------------- // --SECTION-- TRI_vocbase_t // ----------------------------------------------------------------------------- diff --git a/arangod/VocBase/vocbase.h b/arangod/VocBase/vocbase.h index 9b06909e14..00692f8cc9 100644 --- a/arangod/VocBase/vocbase.h +++ b/arangod/VocBase/vocbase.h @@ -611,6 +611,19 @@ bool TRI_IsAllowedNameVocBase (bool, TRI_voc_tick_t TRI_NextQueryIdVocBase (TRI_vocbase_t*); +//////////////////////////////////////////////////////////////////////////////// +/// @brief gets the "throw collection not loaded error" +//////////////////////////////////////////////////////////////////////////////// + +bool TRI_GetThrowCollectionNotLoadedVocBase (TRI_vocbase_t*); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief sets the "throw collection not loaded error" +//////////////////////////////////////////////////////////////////////////////// + +void TRI_SetThrowCollectionNotLoadedVocBase (TRI_vocbase_t*, + bool); + #endif // ----------------------------------------------------------------------------- diff --git a/js/server/bootstrap/module-internal.js b/js/server/bootstrap/module-internal.js index 7db1e84e9b..ee74a53217 100644 --- a/js/server/bootstrap/module-internal.js +++ b/js/server/bootstrap/module-internal.js @@ -134,6 +134,15 @@ if (global.SYS_DEFINE_ACTION) { delete global.SYS_DEFINE_ACTION; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief throw-collection-not-loaded +//////////////////////////////////////////////////////////////////////////////// + +if (global.THROW_COLLECTION_NOT_LOADED) { + exports.throwOnCollectionNotLoaded = global.THROW_COLLECTION_NOT_LOADED; + delete global.THROW_COLLECTION_NOT_LOADED; +} + //////////////////////////////////////////////////////////////////////////////// /// @brief autoload modules from database //////////////////////////////////////////////////////////////////////////////// diff --git a/js/server/tests/shell-collection-not-loaded-timecritical-noncluster.js b/js/server/tests/shell-collection-not-loaded-timecritical-noncluster.js new file mode 100644 index 0000000000..03f64e2ad0 --- /dev/null +++ b/js/server/tests/shell-collection-not-loaded-timecritical-noncluster.js @@ -0,0 +1,256 @@ +/*jshint globalstrict:false, strict:false */ +/*global assertTrue, assertEqual */ + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test the random document selector +/// +/// @file +/// +/// DISCLAIMER +/// +/// Copyright 2010-2012 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 Jan Steemann +/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany +//////////////////////////////////////////////////////////////////////////////// + +var jsunity = require("jsunity"); + +var arangodb = require("org/arangodb"); +var db = arangodb.db; +var internal = require("internal"); +var ArangoCollection = require("org/arangodb/arango-collection").ArangoCollection; + +// ----------------------------------------------------------------------------- +// --SECTION-- throw-collection-not-loaded +// ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test suite +//////////////////////////////////////////////////////////////////////////////// + +function ThrowCollectionNotLoadedSuite () { + 'use strict'; + var old; + var cn = "UnitTestsThrowCollectionNotLoaded"; + + return { + +//////////////////////////////////////////////////////////////////////////////// +/// @brief set up +//////////////////////////////////////////////////////////////////////////////// + + setUp : function () { + // fetch current settings + old = internal.throwOnCollectionNotLoaded(); + db._drop(cn); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief tear down +//////////////////////////////////////////////////////////////////////////////// + + tearDown : function () { + db._drop(cn); + // restore old settings + internal.throwOnCollectionNotLoaded(old); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test regular loading of collection +//////////////////////////////////////////////////////////////////////////////// + + testLoad : function () { + internal.throwOnCollectionNotLoaded(false); + + var c = db._create(cn); + c.save({ value: 1 }); + + c.unload(); + c = null; + internal.wal.flush(true, true); + while (db._collection(cn).status() !== ArangoCollection.STATUS_UNLOADED) { + internal.wait(0.5); + } + + db._collection(cn).load(); + assertEqual(ArangoCollection.STATUS_LOADED, db._collection(cn).status()); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test regular loading of collection, but with flag turned on +//////////////////////////////////////////////////////////////////////////////// + + testLoadWithFlag : function () { + internal.throwOnCollectionNotLoaded(true); + + var c = db._create(cn); + c.save({ value: 1 }); + + c.unload(); + c = null; + internal.wal.flush(true, true); + while (db._collection(cn).status() !== ArangoCollection.STATUS_UNLOADED) { + internal.wait(0.5); + } + + db._collection(cn).load(); + assertEqual(ArangoCollection.STATUS_LOADED, db._collection(cn).status()); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test parallel loading of collection +//////////////////////////////////////////////////////////////////////////////// + + testLoadParallel : function () { + internal.throwOnCollectionNotLoaded(false); + var tasks = require("org/arangodb/tasks"); + + var c = db._create(cn); + for (var i = 0; i < 10000; ++i) { + c.save({ value: 1 }); + } + + db._drop(cn + "Collect"); + var cnCollect = cn + "Collect"; + db._create(cnCollect); + + c.unload(); + c = null; + internal.wal.flush(true, true); + while (db._collection(cn).status() !== ArangoCollection.STATUS_UNLOADED) { + internal.wait(0.5); + } + + var task = { + offset: 0, + params: { cn: cn }, + command: function (params) { + var db = require('internal').db; + try { + for (var i = 0; i < 500; ++i) { + db._collection(params.cn).load(); + db._collection(params.cn).unload(); + } + } + catch (err) { + db._collection(params.cn + "Collect").save({ err: err.errorNum }); + } + } + }; + + // spawn a few tasks that load and unload + for (i = 0; i < 20; ++i) { + task.id = "loadtest" + i; + tasks.register(task); + } + + // wait for tasks to join + internal.wait(5); + + var errors = internal.errors; + + var found = db._collection(cnCollect).byExample({ + err: errors.ERROR_ARANGO_COLLECTION_NOT_LOADED.code + }).toArray(); + db._drop(cnCollect); + + // we should have seen no "collection not found" errors + assertEqual(0, found.length); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test parallel loading of collection, with flag +//////////////////////////////////////////////////////////////////////////////// + + testLoadParallelWithFlag : function () { + internal.throwOnCollectionNotLoaded(true); + var tasks = require("org/arangodb/tasks"); + + var c = db._create(cn); + for (var i = 0; i < 10000; ++i) { + c.save({ value: 1 }); + } + + db._drop(cn + "Collect"); + var cnCollect = cn + "Collect"; + db._create(cnCollect); + + c.unload(); + c = null; + internal.wal.flush(true, true); + while (db._collection(cn).status() !== ArangoCollection.STATUS_UNLOADED) { + internal.wait(0.5); + } + + var task = { + offset: 0, + params: { cn: cn }, + command: function (params) { + var db = require('internal').db; + try { + for (var i = 0; i < 500; ++i) { + db._collection(params.cn).load(); + db._collection(params.cn).unload(); + } + } + catch (err) { + db._collection(params.cn + "Collect").save({ err: err.errorNum }); + } + } + }; + + // spawn a few tasks that load and unload + for (i = 0; i < 20; ++i) { + task.id = "loadtest" + i; + tasks.register(task); + } + + // wait for tasks to join + internal.wait(5); + + var errors = internal.errors; + + var found = db._collection(cnCollect).byExample({ + err: errors.ERROR_ARANGO_COLLECTION_NOT_LOADED.code + }).toArray(); + db._drop(cnCollect); + + // we need to have seen at least one "collection not found" error + assertTrue(found.length > 0); + } + + }; +} + +// ----------------------------------------------------------------------------- +// --SECTION-- main +// ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @brief executes the test suite +//////////////////////////////////////////////////////////////////////////////// + +jsunity.run(ThrowCollectionNotLoadedSuite); + +return jsunity.done(); + +// Local Variables: +// mode: outline-minor +// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)" +// End: + From a61f5bcd128150ec9a0c9bad7aac6f2543935020 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Tue, 11 Aug 2015 12:32:16 +0200 Subject: [PATCH 3/7] display number of file descriptors at startup --- lib/Scheduler/ApplicationScheduler.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/Scheduler/ApplicationScheduler.cpp b/lib/Scheduler/ApplicationScheduler.cpp index 497910c2f9..3df1a02c61 100644 --- a/lib/Scheduler/ApplicationScheduler.cpp +++ b/lib/Scheduler/ApplicationScheduler.cpp @@ -514,7 +514,16 @@ bool ApplicationScheduler::start () { buildSchedulerReporter(); buildControlCHandler(); - bool ok = _scheduler->start(0); +#ifdef TRI_HAVE_GETRLIMIT + struct rlimit rlim; + int res = getrlimit(RLIMIT_NOFILE, &rlim); + + if (res == 0) { + LOG_INFO("file-descriptors (nofiles) hard limit is %d, soft limit is %d", (int) rlim.rlim_max, (int) rlim.rlim_cur); + } +#endif + + bool ok = _scheduler->start(nullptr); if (! ok) { LOG_FATAL_AND_EXIT("the scheduler cannot be started"); @@ -656,8 +665,8 @@ void ApplicationScheduler::adjustFileDescriptors () { if (res != 0) { LOG_FATAL_AND_EXIT("cannot get the file descriptor limit: %s", strerror(errno)); } - - LOG_DEBUG("hard limit is %d, soft limit is %d", (int) rlim.rlim_max, (int) rlim.rlim_cur); + + LOG_DEBUG("file-descriptors (nofiles) hard limit is %d, soft limit is %d", (int) rlim.rlim_max, (int) rlim.rlim_cur); bool changed = false; @@ -696,7 +705,7 @@ void ApplicationScheduler::adjustFileDescriptors () { LOG_FATAL_AND_EXIT("cannot get the file descriptor limit: %s", strerror(errno)); } - LOG_DEBUG("new hard limit is %d, new soft limit is %d", (int) rlim.rlim_max, (int) rlim.rlim_cur); + LOG_INFO("file-descriptors (nofiles) new hard limit is %d, new soft limit is %d", (int) rlim.rlim_max, (int) rlim.rlim_cur); } // the select backend has more restrictions From 525b1363b6f6563dc0d74fc1737a34c4606330db Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Tue, 11 Aug 2015 12:38:32 +0200 Subject: [PATCH 4/7] added notes about Readline --- CHANGELOG | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 057b17f673..c5c15df5bb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -32,6 +32,11 @@ v2.7.0 (XXXX-XX-XX) This affects the arangosh version build with Readline-support only. + The MacOS version of ArangoDB for Homebrew now depends on Readline, too. The + Homebrew formula has been changed accordingly. + When self-compiling ArangoDB on MacOS without Homebrew, Readline now is a + prerequisite. + * increased default value collection-specific `indexBuckets` value from 1 to 16 Collections created from 2.7 on will use the new default if not overriden on From 27b1c90812320cd4813d2fbfcc2134d906c60b44 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Tue, 11 Aug 2015 12:56:41 +0200 Subject: [PATCH 5/7] updated CHANGELOG --- CHANGELOG | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index c5c15df5bb..b63375a9e8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,19 @@ v2.7.0 (XXXX-XX-XX) ------------------- +* Linux startup scripts and systemd configuration for arangod now try to + adjust the NOFILE (number of open files) limits for the process. The limit + value is set to 131072 (128k) when ArangoDB is started via start/stop + commands + +* When ArangoDB is started/stopped manually via the start/stop commands, the + main process will wait for up to 10 seconds after it forks the supervisor + and arangod child processes. If the startup fails within that period, the + start/stop script will fail with an exit code other than zero. If the + startup of the supervisor or arangod is still ongoing after 10 seconds, + the main program will still return with exit code 0. The limit of 10 seconds + is arbitrary because the time required for a startup is not known in advance. + * added startup option `--database.throw-collection-not-loaded-error` Accessing a not-yet loaded collection will automatically load a collection From 6a26f9bc55124b349794ce03ab7f4f87f6ff8cff Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Tue, 11 Aug 2015 14:00:15 +0200 Subject: [PATCH 6/7] display indexBuckets in web interface --- .../aardvark/APP/build/documentation.js | 4 ++ .../aardvark/APP/build/documentation.js.gz | Bin 127877 -> 127893 bytes .../aardvark/APP/build/documentation.min.js | 2 +- .../js/models/arangoCollectionModel.js | 15 ++++- .../js/templates/modalCollectionInfo.ejs | 7 +++ .../frontend/js/views/collectionsItemView.js | 56 +++++++++++++----- 6 files changed, 67 insertions(+), 17 deletions(-) diff --git a/js/apps/system/_admin/aardvark/APP/build/documentation.js b/js/apps/system/_admin/aardvark/APP/build/documentation.js index 2fbba84272..2f20ba77fd 100644 --- a/js/apps/system/_admin/aardvark/APP/build/documentation.js +++ b/js/apps/system/_admin/aardvark/APP/build/documentation.js @@ -9855,6 +9855,10 @@ else { exports.printObject = printObject; +exports.isCaptureMode = function() { + return exports.output === bufferOutput; +}; + //////////////////////////////////////////////////////////////////////////////// /// @brief startCaptureMode //////////////////////////////////////////////////////////////////////////////// diff --git a/js/apps/system/_admin/aardvark/APP/build/documentation.js.gz b/js/apps/system/_admin/aardvark/APP/build/documentation.js.gz index e52e225040d08058ebe716e7738593bfddde94f8..b4a09f8231bcbf1eaba4cdcbcfd3d9ec9b722dac 100644 GIT binary patch delta 8872 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[e]=b.call(c,e);return d},x.random=function(a,b){return null==b&&(b=a,a=0),a+Math.floor(Math.random()*(b-a+1))};var G={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};G.unescape=x.invert(G.escape);var H={escape:new RegExp("["+x.keys(G.escape).join("")+"]","g"),unescape:new RegExp("("+x.keys(G.unescape).join("|")+")","g")};x.each(["escape","unescape"],function(a){x[a]=function(b){return null==b?"":(""+b).replace(H[a],function(b){return G[a][b]})}}),x.result=function(a,b){if(null==a)return void 0;var c=a[b];return x.isFunction(c)?c.call(a):c},x.mixin=function(a){y(x.functions(a),function(b){var c=x[b]=a[b];x.prototype[b]=function(){var a=[this._wrapped];return g.apply(a,arguments),M.call(this,c.apply(x,a))}})};var I=0;x.uniqueId=function(a){var b=++I+"";return a?a+b:b},x.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var J=/(.)^/,K={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},L=/\\|'|\r|\n|\t|\u2028|\u2029/g;x.template=function(a,b,c){var d;c=x.defaults({},c,x.templateSettings);var e=new RegExp([(c.escape||J).source,(c.interpolate||J).source,(c.evaluate||J).source].join("|")+"|$","g"),f=0,g="__p+='";a.replace(e,function(b,c,d,e,h){return g+=a.slice(f,h).replace(L,function(a){return"\\"+K[a]}),c&&(g+="'+\n((__t=("+c+"))==null?'':_.escape(__t))+\n'"),d&&(g+="'+\n((__t=("+d+"))==null?'':__t)+\n'"),e&&(g+="';\n"+e+"\n__p+='"),f=h+b.length,b}),g+="';\n",c.variable||(g="with(obj||{}){\n"+g+"}\n"),g="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+g+"return __p;\n";try{d=new Function(c.variable||"obj","_",g)}catch(h){throw h.source=g,h}if(b)return d(b,x);var i=function(a){return d.call(this,a,x)};return i.source="function("+(c.variable||"obj")+"){\n"+g+"}",i},x.chain=function(a){return x(a).chain()};var M=function(a){return this._chain?x(a).chain():a};x.mixin(x),y(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=d[a];x.prototype[a]=function(){var c=this._wrapped;return b.apply(c,arguments),"shift"!=a&&"splice"!=a||0!==c.length||delete c[0],M.call(this,c)}}),y(["concat","join","slice"],function(a){var b=d[a];x.prototype[a]=function(){return M.call(this,b.apply(this._wrapped,arguments))}}),x.extend(x.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}.call(this),function(){var a,b=this,c=b.Backbone,d=[],e=d.push,f=d.slice,g=d.splice;a="undefined"!=typeof exports?exports:b.Backbone={},a.VERSION="1.0.0";var h=b._;h||"undefined"==typeof require||(h=require("underscore")),a.$=b.jQuery||b.Zepto||b.ender||b.$,a.noConflict=function(){return b.Backbone=c,this},a.emulateHTTP=!1,a.emulateJSON=!1;var i=a.Events={on:function(a,b,c){if(!k(this,"on",a,[b,c])||!b)return this;this._events||(this._events={});var d=this._events[a]||(this._events[a]=[]);return d.push({callback:b,context:c,ctx:c||this}),this},once:function(a,b,c){if(!k(this,"once",a,[b,c])||!b)return this;var d=this,e=h.once(function(){d.off(a,e),b.apply(this,arguments)});return e._callback=b,this.on(a,e,c)},off:function(a,b,c){var d,e,f,g,i,j,l,m;if(!this._events||!k(this,"off",a,[b,c]))return this;if(!a&&!b&&!c)return this._events={},this;for(g=a?[a]:h.keys(this._events),i=0,j=g.length;j>i;i++)if(a=g[i],f=this._events[a]){if(this._events[a]=d=[],b||c)for(l=0,m=f.length;m>l;l++)e=f[l],(b&&b!==e.callback&&b!==e.callback._callback||c&&c!==e.context)&&d.push(e);d.length||delete this._events[a]}return this},trigger:function(a){if(!this._events)return this;var b=f.call(arguments,1);if(!k(this,"trigger",a,b))return this;var c=this._events[a],d=this._events.all;return c&&l(c,b),d&&l(d,arguments),this},stopListening:function(a,b,c){var d=this._listeners;if(!d)return this;var e=!b&&!c;"object"==typeof b&&(c=this),a&&((d={})[a._listenerId]=a);for(var f in d)d[f].off(b,c,this),e&&delete this._listeners[f];return this}},j=/\s+/,k=function(a,b,c,d){if(!c)return!0;if("object"==typeof c){for(var e in c)a[b].apply(a,[e,c[e]].concat(d));return!1}if(j.test(c)){for(var f=c.split(j),g=0,h=f.length;h>g;g++)a[b].apply(a,[f[g]].concat(d));return!1}return!0},l=function(a,b){var c,d=-1,e=a.length,f=b[0],g=b[1],h=b[2];switch(b.length){case 0:for(;++dm;m++)this.trigger("change:"+g[m],this,l[g[m]],c)}if(j)return this;if(!i)for(;this._pending;)this._pending=!1,this.trigger("change",this,c);return this._pending=!1,this._changing=!1,this},unset:function(a,b){return this.set(a,void 0,h.extend({},b,{unset:!0}))},clear:function(a){var b={};for(var c in this.attributes)b[c]=void 0;return this.set(b,h.extend({},a,{unset:!0}))},hasChanged:function(a){return null==a?!h.isEmpty(this.changed):h.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?h.clone(this.changed):!1;var b,c=!1,d=this._changing?this._previousAttributes:this.attributes;for(var e in a)h.isEqual(d[e],b=a[e])||((c||(c={}))[e]=b);return c},previous:function(a){return null!=a&&this._previousAttributes?this._previousAttributes[a]:null},previousAttributes:function(){return h.clone(this._previousAttributes)},fetch:function(a){a=a?h.clone(a):{},void 0===a.parse&&(a.parse=!0);var b=this,c=a.success;return a.success=function(d){return b.set(b.parse(d,a),a)?(c&&c(b,d,a),void b.trigger("sync",b,d,a)):!1},L(this,a),this.sync("read",this,a)},save:function(a,b,c){var d,e,f,g=this.attributes;if(null==a||"object"==typeof a?(d=a,c=b):(d={})[a]=b,d&&(!c||!c.wait)&&!this.set(d,c))return!1;if(c=h.extend({validate:!0},c),!this._validate(d,c))return!1;d&&c.wait&&(this.attributes=h.extend({},g,d)),void 0===c.parse&&(c.parse=!0);var i=this,j=c.success;return c.success=function(a){i.attributes=g;var b=i.parse(a,c);return c.wait&&(b=h.extend(d||{},b)),h.isObject(b)&&!i.set(b,c)?!1:(j&&j(i,a,c),void i.trigger("sync",i,a,c))},L(this,c),e=this.isNew()?"create":c.patch?"patch":"update","patch"===e&&(c.attrs=d),f=this.sync(e,this,c),d&&c.wait&&(this.attributes=g),f},destroy:function(a){a=a?h.clone(a):{};var b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(a.success=function(e){(a.wait||b.isNew())&&d(),c&&c(b,e,a),b.isNew()||b.trigger("sync",b,e,a)},this.isNew())return a.success(),!1;L(this,a);var e=this.sync("delete",this,a);return a.wait||d(),e},url:function(){var a=h.result(this,"urlRoot")||h.result(this.collection,"url")||K();return this.isNew()?a:a+("/"===a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a,b){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},isValid:function(a){return this._validate({},h.extend(a||{},{validate:!0}))},_validate:function(a,b){if(!b.validate||!this.validate)return!0;a=h.extend({},this.attributes,a);var c=this.validationError=this.validate(a,b)||null;return c?(this.trigger("invalid",this,c,h.extend(b||{},{validationError:c})),!1):!0}});var p=["keys","values","pairs","invert","pick","omit"];h.each(p,function(a){n.prototype[a]=function(){var b=f.call(arguments);return b.unshift(this.attributes),h[a].apply(h,b)}});var q=a.Collection=function(a,b){b||(b={}),b.url&&(this.url=b.url),b.model&&(this.model=b.model),void 0!==b.comparator&&(this.comparator=b.comparator),this._reset(),this.initialize.apply(this,arguments),a&&this.reset(a,h.extend({silent:!0},b))},r={add:!0,remove:!0,merge:!0},s={add:!0,merge:!1,remove:!1};h.extend(q.prototype,i,{model:n,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},sync:function(){return a.sync.apply(this,arguments)},add:function(a,b){return this.set(a,h.defaults(b||{},s))},remove:function(a,b){a=h.isArray(a)?a.slice():[a],b||(b={});var c,d,e,f;for(c=0,d=a.length;d>c;c++)f=this.get(a[c]),f&&(delete this._byId[f.id],delete this._byId[f.cid],e=this.indexOf(f),this.models.splice(e,1),this.length--,b.silent||(b.index=e,f.trigger("remove",f,this,b)),this._removeReference(f));return this},set:function(a,b){b=h.defaults(b||{},r),b.parse&&(a=this.parse(a,b)),h.isArray(a)||(a=a?[a]:[]);var c,d,f,i,j,k=b.at,l=this.comparator&&null==k&&b.sort!==!1,m=h.isString(this.comparator)?this.comparator:null,n=[],o=[],p={};for(c=0,d=a.length;d>c;c++)(f=this._prepareModel(a[c],b))&&((i=this.get(f))?(b.remove&&(p[i.cid]=!0),b.merge&&(i.set(f.attributes,b),l&&!j&&i.hasChanged(m)&&(j=!0))):b.add&&(n.push(f),f.on("all",this._onModelEvent,this),this._byId[f.cid]=f,null!=f.id&&(this._byId[f.id]=f)));if(b.remove){for(c=0,d=this.length;d>c;++c)p[(f=this.models[c]).cid]||o.push(f);o.length&&this.remove(o,b)}if(n.length&&(l&&(j=!0),this.length+=n.length,null!=k?g.apply(this.models,[k,0].concat(n)):e.apply(this.models,n)),j&&this.sort({silent:!0}),b.silent)return this;for(c=0,d=n.length;d>c;c++)(f=n[c]).trigger("add",f,this,b);return j&&this.trigger("sort",this,b),this},reset:function(a,b){b||(b={});for(var c=0,d=this.models.length;d>c;c++)this._removeReference(this.models[c]);return b.previousModels=this.models,this._reset(),this.add(a,h.extend({silent:!0},b)),b.silent||this.trigger("reset",this,b),this},push:function(a,b){return a=this._prepareModel(a,b),this.add(a,h.extend({at:this.length},b)),a},pop:function(a){var b=this.at(this.length-1);return this.remove(b,a),b},unshift:function(a,b){return a=this._prepareModel(a,b),this.add(a,h.extend({at:0},b)),a},shift:function(a){var b=this.at(0);return this.remove(b,a),b},slice:function(a,b){return this.models.slice(a,b)},get:function(a){return null==a?void 0:this._byId[null!=a.id?a.id:a.cid||a]},at:function(a){return this.models[a]},where:function(a,b){return h.isEmpty(a)?b?void 0:[]:this[b?"find":"filter"](function(b){for(var c in a)if(a[c]!==b.get(c))return!1;return!0})},findWhere:function(a){return this.where(a,!0)},sort:function(a){if(!this.comparator)throw new Error("Cannot sort a set without a comparator");return a||(a={}),h.isString(this.comparator)||1===this.comparator.length?this.models=this.sortBy(this.comparator,this):this.models.sort(h.bind(this.comparator,this)),a.silent||this.trigger("sort",this,a),this},sortedIndex:function(a,b,c){b||(b=this.comparator);var d=h.isFunction(b)?b:function(a){return a.get(b)};return h.sortedIndex(this.models,a,d,c)},pluck:function(a){return h.invoke(this.models,"get",a)},fetch:function(a){a=a?h.clone(a):{},void 0===a.parse&&(a.parse=!0);var b=a.success,c=this;return a.success=function(d){var e=a.reset?"reset":"set";c[e](d,a),b&&b(c,d,a),c.trigger("sync",c,d,a)},L(this,a),this.sync("read",this,a)},create:function(a,b){if(b=b?h.clone(b):{},!(a=this._prepareModel(a,b)))return!1;b.wait||this.add(a,b);var c=this,d=b.success;return b.success=function(e){b.wait&&c.add(a,b),d&&d(a,e,b)},a.save(null,b),a},parse:function(a,b){return a},clone:function(){return new this.constructor(this.models)},_reset:function(){this.length=0,this.models=[],this._byId={}},_prepareModel:function(a,b){if(a instanceof n)return a.collection||(a.collection=this),a;b||(b={}),b.collection=this;var c=new this.model(a,b);return c._validate(a,b)?c:(this.trigger("invalid",this,a,b),!1)},_removeReference:function(a){this===a.collection&&delete a.collection,a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"!==a&&"remove"!==a||c===this)&&("destroy"===a&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],null!=b.id&&(this._byId[b.id]=b)),this.trigger.apply(this,arguments))}});var t=["forEach","each","map","collect","reduce","foldl","inject","reduceRight","foldr","find","detect","filter","select","reject","every","all","some","any","include","contains","invoke","max","min","toArray","size","first","head","take","initial","rest","tail","drop","last","without","indexOf","shuffle","lastIndexOf","isEmpty","chain"];h.each(t,function(a){q.prototype[a]=function(){var b=f.call(arguments);return b.unshift(this.models),h[a].apply(h,b)}});var u=["groupBy","countBy","sortBy"];h.each(u,function(a){q.prototype[a]=function(b,c){var d=h.isFunction(b)?b:function(a){return a.get(b)};return h[a](this.models,d,c)}});var v=a.View=function(a){this.cid=h.uniqueId("view"),this._configure(a||{}),this._ensureElement(),this.initialize.apply(this,arguments),this.delegateEvents()},w=/^(\S+)\s*(.*)$/,x=["model","collection","el","id","attributes","className","tagName","events"];h.extend(v.prototype,i,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){return this.$el.remove(),this.stopListening(),this},setElement:function(b,c){return this.$el&&this.undelegateEvents(),this.$el=b instanceof a.$?b:a.$(b),this.el=this.$el[0],c!==!1&&this.delegateEvents(),this},delegateEvents:function(a){if(!a&&!(a=h.result(this,"events")))return this;this.undelegateEvents();for(var b in a){var c=a[b];if(h.isFunction(c)||(c=this[a[b]]),c){var d=b.match(w),e=d[1],f=d[2];c=h.bind(c,this),e+=".delegateEvents"+this.cid,""===f?this.$el.on(e,c):this.$el.on(e,f,c)}}return this},undelegateEvents:function(){return this.$el.off(".delegateEvents"+this.cid),this},_configure:function(a){this.options&&(a=h.extend({},h.result(this,"options"),a)),h.extend(this,h.pick(a,x)),this.options=a},_ensureElement:function(){if(this.el)this.setElement(h.result(this,"el"),!1);else{var b=h.extend({},h.result(this,"attributes"));this.id&&(b.id=h.result(this,"id")),this.className&&(b["class"]=h.result(this,"className"));var c=a.$("<"+h.result(this,"tagName")+">").attr(b);this.setElement(c,!1)}}}),a.sync=function(b,c,d){var e=y[b];h.defaults(d||(d={}),{emulateHTTP:a.emulateHTTP,emulateJSON:a.emulateJSON});var f={type:e,dataType:"json"};if(d.url||(f.url=h.result(c,"url")||K()),null!=d.data||!c||"create"!==b&&"update"!==b&&"patch"!==b||(f.contentType="application/json",f.data=JSON.stringify(d.attrs||c.toJSON(d))),d.emulateJSON&&(f.contentType="application/x-www-form-urlencoded",f.data=f.data?{model:f.data}:{}),d.emulateHTTP&&("PUT"===e||"DELETE"===e||"PATCH"===e)){f.type="POST",d.emulateJSON&&(f.data._method=e);var g=d.beforeSend;d.beforeSend=function(a){return a.setRequestHeader("X-HTTP-Method-Override",e),g?g.apply(this,arguments):void 0}}"GET"===f.type||d.emulateJSON||(f.processData=!1),"PATCH"!==f.type||!window.ActiveXObject||window.external&&window.external.msActiveXFilteringEnabled||(f.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")});var i=d.xhr=a.ajax(h.extend(f,d));return c.trigger("request",c,i,d),i};var y={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};a.ajax=function(){return a.$.ajax.apply(a.$,arguments)};var z=a.Router=function(a){a||(a={}),a.routes&&(this.routes=a.routes),this._bindRoutes(),this.initialize.apply(this,arguments)},A=/\((.*?)\)/g,B=/(\(\?)?:\w+/g,C=/\*\w+/g,D=/[\-{}\[\]+?.,\\\^$|#\s]/g;h.extend(z.prototype,i,{initialize:function(){},route:function(b,c,d){h.isRegExp(b)||(b=this._routeToRegExp(b)),h.isFunction(c)&&(d=c,c=""),d||(d=this[c]);var e=this;return a.history.route(b,function(f){var g=e._extractParameters(b,f);d&&d.apply(e,g),e.trigger.apply(e,["route:"+c].concat(g)),e.trigger("route",c,g),a.history.trigger("route",e,c,g)}),this},navigate:function(b,c){return a.history.navigate(b,c),this},_bindRoutes:function(){if(this.routes){this.routes=h.result(this,"routes");for(var a,b=h.keys(this.routes);null!=(a=b.pop());)this.route(a,this.routes[a])}},_routeToRegExp:function(a){return a=a.replace(D,"\\$&").replace(A,"(?:$1)?").replace(B,function(a,b){return b?a:"([^/]+)"}).replace(C,"(.*?)"),new RegExp("^"+a+"$")},_extractParameters:function(a,b){var c=a.exec(b).slice(1);return h.map(c,function(a){return a?decodeURIComponent(a):null})}});var E=a.History=function(){this.handlers=[],h.bindAll(this,"checkUrl"),"undefined"!=typeof window&&(this.location=window.location,this.history=window.history)},F=/^[#\/]|\s+$/g,G=/^\/+|\/+$/g,H=/msie [\w.]+/,I=/\/$/;E.started=!1,h.extend(E.prototype,i,{interval:50,getHash:function(a){var b=(a||this).location.href.match(/#(.*)$/);return b?b[1]:""},getFragment:function(a,b){if(null==a)if(this._hasPushState||!this._wantsHashChange||b){a=this.location.pathname;var c=this.root.replace(I,"");a.indexOf(c)||(a=a.substr(c.length))}else a=this.getHash();return a.replace(F,"")},start:function(b){if(E.started)throw new Error("Backbone.history has already been started");E.started=!0,this.options=h.extend({},{root:"/"},this.options,b),this.root=this.options.root,this._wantsHashChange=this.options.hashChange!==!1,this._wantsPushState=!!this.options.pushState,this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var c=this.getFragment(),d=document.documentMode,e=H.exec(navigator.userAgent.toLowerCase())&&(!d||7>=d);this.root=("/"+this.root+"/").replace(G,"/"),e&&this._wantsHashChange&&(this.iframe=a.$('