This is an automated email from the ASF dual-hosted git repository. ronny pushed a commit to branch add-sm-v140 in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit 677566d1069c6f53566013cab14e72bd6070a702 Author: Jiahui Li <lijiahui...@gmail.com> AuthorDate: Wed Sep 24 08:06:12 2025 -0500 Fix config options (#5642) * Config: `secret` was moved to `[chttpd_auth]` session * Docs: `changes_timeout` is applicable for `_changes?feed=eventsource` * [couchdb]: Add `delete_after_rename = true` option - Update comments for `enable_database_recovery = false`. - Add `delete_after_rename = true` option: - No ops when `enable_database_recovery = true`. - If `true`, delete the database shard files from the `data` directory. - If `false`, rename the database shard files and move them to the `.delete` directory. * [fabric]: `attachments_timeout` default should be `600000` * [view_updater]: Add `queue_memory_cap` and `queue_item_cap` * [fabric]: Make `all_docs_view_permsg_timeout = 5000` configurable * Fix typos --- rel/overlay/etc/default.ini | 47 ++-- src/couch/priv/couch_js/140/help.h | 80 +++++++ src/couch/priv/couch_js/140/main.cpp | 337 ++++++++++++++++++++++++++ src/couch/priv/couch_js/140/util.cpp | 380 ++++++++++++++++++++++++++++++ src/couch/priv/couch_js/140/util.h | 43 ++++ src/couch/rebar.config.script | 15 +- src/docs/src/api/database/changes.rst | 5 +- src/dreyfus/src/dreyfus_fabric_group1.erl | 4 +- src/dreyfus/src/dreyfus_fabric_group2.erl | 4 +- src/fabric/src/fabric_view_all_docs.erl | 2 +- src/fabric/src/fabric_view_changes.erl | 2 +- src/setup/test/t-frontend-setup.sh | 4 +- 12 files changed, 893 insertions(+), 30 deletions(-) diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini index 63db3f854..db4d37f3e 100644 --- a/rel/overlay/etc/default.ini +++ b/rel/overlay/etc/default.ini @@ -71,11 +71,18 @@ view_index_dir = {{view_index_dir}} ;default_engine = couch ; Enable this to only "soft-delete" databases when DELETE /{db} requests are -; made. This will place a .recovery directory in your data directory and -; move deleted databases/shards there instead. You can then manually delete -; these files later, as desired. +; made. This will add `.deleted.couch` extension to the database shard files +; in the data directory. You can then manually delete these files later, as +; desired. ;enable_database_recovery = false +; Applies only when `enable_database_recovery = false`. +; When DELETE /{db} requests are made: +; - If `true`, delete the database shard files from the `data` directory. +; - If `false`, rename the database shard files and move them to the `.delete` +; directory. +;delete_after_rename = true + ; Set the maximum size allowed for a partition. This helps users avoid ; inadvertently abusing partitions resulting in hot shards. The default ; is 10GiB. A value of 0 or less will disable partition size checks. @@ -116,7 +123,7 @@ view_index_dir = {{view_index_dir}} ; When enabled, use cfile parallel reads for all the requests. By default the ; setting is "false", so only requests which are configured to bypass the IOQ ; would use the cfile parallel reads. If there is enough RAM available for a -; large file cache and the disks have enough IO bandwith, consider enabling +; large file cache and the disks have enough IO bandwidth, consider enabling ; this setting. ;cfile_skip_ioq = false @@ -257,7 +264,7 @@ bind_address = 127.0.0.1 ; Set to false to revert to a previous _bulk_get implementation using single ; doc fetches internally. Using batches should be faster, however there may be -; bugs in the new new implemention, so expose this option to allow reverting to +; bugs in the new implementation, so expose this option to allow reverting to ; the old behavior. ;bulk_get_use_batches = true @@ -441,7 +448,8 @@ hash_algorithms = sha256, sha ;uuid_prefix_len = 7 ;request_timeout = 60000 ;all_docs_timeout = 10000 -;attachments_timeout = 60000 +;all_docs_view_permsg_timeout = 5000 +;attachments_timeout = 600000 ;view_timeout = infinity ;view_permsg_timeout = 3600000 ;partition_view_timeout = infinity @@ -468,6 +476,16 @@ hash_algorithms = sha256, sha ;update_db = true ;[view_updater] +; Configure the queue capacity used during indexing. These settings apply to +; both the queue between the changes feed and the JS mapper, and between the +; JS mapper and the disk writer. +; Whichever limit happens to be hit first is the one that takes effect. + +; The maximum queue memory size +;queue_memory_cap = 100000 +; The maximum queue length +;queue_item_cap = 500 + ;min_writer_items = 100 ;min_writer_size = 16777216 @@ -711,7 +729,7 @@ partitioned||* = true ; How much time to wait before retrying after a missing doc exception. This ; exception happens if the document was seen in the changes feed, but internal ; replication hasn't caught up yet, and fetching document's revisions -; fails. This a common scenario when source is updated while continuous +; fails. This is a common scenario when source is updated while continuous ; replication is running. The retry period would depend on how quickly internal ; replication is expected to catch up. In general this is an optimisation to ; avoid crashing the whole replication job, which would consume more resources @@ -730,7 +748,7 @@ partitioned||* = true ; couch_replicator_auth_session - use _session cookie authentication ; couch_replicator_auth_noop - use basic authentication (previous default) ; Currently, the new _session cookie authentication is tried first, before -; falling back to the old basic authenticaion default: +; falling back to the old basic authentication default: ;auth_plugins = couch_replicator_auth_session,couch_replicator_auth_noop ; To restore the old behaviour, use the following value: @@ -758,7 +776,7 @@ partitioned||* = true ; priority 0, and would render this algorithm useless. The default value of ; 0.98 is picked such that if a job ran for one scheduler cycle, then didn't ; get to run for 7 hours, it would still have priority > 0. 7 hours was picked -; as it was close enought to 8 hours which is the default maximum error backoff +; as it was close enough to 8 hours which is the default maximum error backoff ; interval. ;priority_coeff = 0.98 @@ -971,12 +989,11 @@ port = {{prometheus_port}} [custodian] ; When set to `true`, force using `[cluster] n` values as the expected n value -; of of shard copies. In cases where the application prevents creating -; non-default n databases, this could help detect case where the shard map was -; altered by hand, or via an external tools, such that it doesn't have the -; necessary number of copies for some ranges. By default, when the setting is -; `false`, the expected n value is based on the number of available copies in -; the shard map. +; of shard copies. In cases where the application prevents creating non-default +; n databases, this could help detect case where the shard map was altered by +; hand, or via an external tools, such that it doesn't have the necessary number +; of copies for some ranges. By default, when the setting is `false`, the +; expected n value is based on the number of available copies in the shard map. ;use_cluster_n_as_expected_n = false [nouveau] diff --git a/src/couch/priv/couch_js/140/help.h b/src/couch/priv/couch_js/140/help.h new file mode 100644 index 000000000..28a2f6455 --- /dev/null +++ b/src/couch/priv/couch_js/140/help.h @@ -0,0 +1,80 @@ +// 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. + +#ifndef COUCHJS_HELP_H +#define COUCHJS_HELP_H + +#include "config.h" + +static const char VERSION_TEMPLATE[] = + "%s - %s (SpiderMonkey %s)\n" + "\n" + "Licensed under the Apache License, Version 2.0 (the \"License\"); you may " + "not use\n" + "this file except in compliance with the License. You may obtain a copy of" + "the\n" + "License at\n" + "\n" + " http://www.apache.org/licenses/LICENSE-2.0\n" + "\n" + "Unless required by applicable law or agreed to in writing, software " + "distributed\n" + "under the License is distributed on an \"AS IS\" BASIS, WITHOUT " + "WARRANTIES OR\n" + "CONDITIONS OF ANY KIND, either express or implied. See the License " + "for the\n" + "specific language governing permissions and limitations under the " + "License.\n"; + +static const char USAGE_TEMPLATE[] = + "Usage: %s [FILE]\n" + "\n" + "The %s command runs the %s JavaScript interpreter.\n" + "\n" + "The exit status is 0 for success or 1 for failure.\n" + "\n" + "Options:\n" + "\n" + " -h display a short help message and exit\n" + " -V display version information and exit\n" + " -S SIZE specify that the runtime should allow at\n" + " most SIZE bytes of memory to be allocated\n" + " default is 64 MiB\n" + " --eval Enable runtime code evaluation (dangerous!)\n" + "\n" + "Report bugs at <%s>.\n"; + +#define BASENAME COUCHJS_NAME + +#define couch_version(basename) \ + fprintf( \ + stdout, \ + VERSION_TEMPLATE, \ + basename, \ + PACKAGE_STRING, \ + get_spidermonkey_version()) + +#define DISPLAY_VERSION couch_version(BASENAME) + + +#define couch_usage(basename) \ + fprintf( \ + stdout, \ + USAGE_TEMPLATE, \ + basename, \ + basename, \ + PACKAGE_NAME, \ + PACKAGE_BUGREPORT) + +#define DISPLAY_USAGE couch_usage(BASENAME) + +#endif // Included help.h diff --git a/src/couch/priv/couch_js/140/main.cpp b/src/couch/priv/couch_js/140/main.cpp new file mode 100644 index 000000000..e238654c9 --- /dev/null +++ b/src/couch/priv/couch_js/140/main.cpp @@ -0,0 +1,337 @@ +// 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. + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#ifdef XP_WIN +#define NOMINMAX +#include <windows.h> +#else +#include <unistd.h> +#endif + +#include <jsapi.h> +#include <js/CompilationAndEvaluation.h> +#include <js/Conversions.h> +#include <js/Initialization.h> +#include <js/SourceText.h> +#include <js/StableStringChars.h> +#include <js/Warnings.h> +#include <js/Wrapper.h> + +#include "config.h" +#include "util.h" + +static bool enableSharedMemory = true; +static bool enableToSource = true; + +/* The class of the global object. */ +static JSClass global_class = { + "global", + JSCLASS_GLOBAL_FLAGS, + &JS::DefaultGlobalClassOps +}; + +static JSObject* +NewSandbox(JSContext* cx, bool lazy) +{ + JS::RealmOptions options; + options.creationOptions().setSharedMemoryAndAtomicsEnabled(enableSharedMemory); + options.creationOptions().setNewCompartmentAndZone(); + // we need this in the query server error handling + options.creationOptions().setToSourceEnabled(enableToSource); + JS::RootedObject obj(cx, JS_NewGlobalObject(cx, &global_class, nullptr, + JS::DontFireOnNewGlobalHook, options)); + if (!obj) + return nullptr; + + { + JSAutoRealm ac(cx, obj); + if (!lazy && !JS::InitRealmStandardClasses(cx)) + return nullptr; + + JS::RootedValue value(cx, JS::BooleanValue(lazy)); + if (!JS_DefineProperty(cx, obj, "lazy", value, JSPROP_PERMANENT | JSPROP_READONLY)) + return nullptr; + + JS_FireOnNewGlobalObject(cx, obj); + } + + if (!JS_WrapObject(cx, &obj)) + return nullptr; + return obj; +} + +static bool +evalcx(JSContext *cx, unsigned int argc, JS::Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + bool ret = false; + + JS::RootedString str(cx, args[0].toString()); + if (!str) + return false; + + JS::RootedObject sandbox(cx); + if (args.hasDefined(1)) { + sandbox = JS::ToObject(cx, args[1]); + if (!sandbox) + return false; + } + + if (!sandbox) { + sandbox = NewSandbox(cx, false); + if (!sandbox) + return false; + } + + JS::AutoStableStringChars strChars(cx); + if (!strChars.initTwoByte(cx, str)) + return false; + + mozilla::Range<const char16_t> chars = strChars.twoByteRange(); + JS::SourceText<char16_t> srcBuf; + if (!srcBuf.init(cx, chars.begin().get(), chars.length(), + JS::SourceOwnership::Borrowed)) { + return false; + } + + if(srcBuf.length() == 0) { + args.rval().setObject(*sandbox); + } else { + mozilla::Maybe<JSAutoRealm> ar; + unsigned flags; + JSObject* unwrapped = UncheckedUnwrap(sandbox, true, &flags); + if (flags & js::Wrapper::CROSS_COMPARTMENT) { + sandbox = unwrapped; + ar.emplace(cx, sandbox); + } + + JS::CompileOptions opts(cx); + JS::RootedValue rval(cx); + opts.setFileAndLine("<unknown>", 1); + + if (!JS::Evaluate(cx, opts, srcBuf, args.rval())) { + return false; + } + } + ret = true; + if (!JS_WrapValue(cx, args.rval())) + return false; + + return ret; +} + + +static bool +gc(JSContext* cx, unsigned int argc, JS::Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS_GC(cx); + args.rval().setUndefined(); + return true; +} + + +static bool +print(JSContext* cx, unsigned int argc, JS::Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + bool use_stderr = false; + if(argc > 1 && args[1].isTrue()) { + use_stderr = true; + } + + if(!args[0].isString()) { + JS_ReportErrorUTF8(cx, "Unable to print non-string value."); + return false; + } + + couch_print(cx, args[0], use_stderr); + + args.rval().setUndefined(); + return true; +} + + +static bool +quit(JSContext* cx, unsigned int argc, JS::Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + int exit_code = args[0].toInt32();; + JS_DestroyContext(cx); + JS_ShutDown(); + exit(exit_code); +} + + +static bool +readline(JSContext* cx, unsigned int argc, JS::Value* vp) +{ + JSString* line; + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + /* GC Occasionally */ + JS_MaybeGC(cx); + + line = couch_readline(cx, stdin); + if(line == NULL) return false; + + // return with JSString* instead of JSValue in the past + args.rval().setString(line); + return true; +} + + +static bool +seal(JSContext* cx, unsigned int argc, JS::Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject target(cx); + target = JS::ToObject(cx, args[0]); + if (!target) { + args.rval().setUndefined(); + return true; + } + bool deep = false; + deep = args[1].toBoolean(); + bool ret = deep ? JS_DeepFreezeObject(cx, target) : JS_FreezeObject(cx, target); + args.rval().setUndefined(); + return ret; +} + + +static JSFunctionSpec global_functions[] = { + JS_FN("evalcx", evalcx, 0, 0), + JS_FN("gc", gc, 0, 0), + JS_FN("print", print, 0, 0), + JS_FN("quit", quit, 0, 0), + JS_FN("readline", readline, 0, 0), + JS_FN("seal", seal, 0, 0), + JS_FS_END +}; + + +static bool +csp_allows(JSContext* cx, + JS::RuntimeCode kind, JS::Handle<JSString*> codeString, + JS::CompilationType compilationType, + JS::Handle<JS::StackGCVector<JSString*>> parameterStrings, + JS::Handle<JSString*> bodyString, + JS::Handle<JS::StackGCVector<JS::Value>> parameterArgs, + JS::Handle<JS::Value> bodyArg, bool* outCanCompileStrings) +{ + couch_args* args = static_cast<couch_args*>(JS_GetContextPrivate(cx)); + if(args->eval) { + return true; + } else { + return false; + } +} + + +static JSSecurityCallbacks security_callbacks = { + csp_allows, //JSCSPEvalChecker contentSecurityPolicyAllows; + nullptr, //JSCodeForEvalOp codeForEvalGets; + nullptr //JSSubsumesOp subsumes; +}; + +int runWithContext(JSContext* cx, couch_args* args) { + JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_ENABLE, 0); + JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_ENABLE, 0); + + if (!JS::InitSelfHostedCode(cx)) + return 1; + + JS::SetWarningReporter(cx, couch_error); + JS::SetOutOfMemoryCallback(cx, couch_oom, NULL); + JS_SetContextPrivate(cx, args); + JS_SetSecurityCallbacks(cx, &security_callbacks); + + JS::RealmOptions options; + // we need this in the query server error handling + options.creationOptions().setToSourceEnabled(enableToSource); + JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr, + JS::FireOnNewGlobalHook, options)); + if (!global) + return 1; + + JSAutoRealm ar(cx, global); + + if(!JS::InitRealmStandardClasses(cx)) + return 1; + + if(couch_load_funcs(cx, global, global_functions) != true) + return 1; + + for(int i = 0 ; args->scripts[i] ; i++) { + const char* filename = args->scripts[i]; + + // Compile and run + JS::CompileOptions options(cx); + JS::RootedScript script(cx); + + script = JS::CompileUtf8Path(cx, options, filename); + if (!script) { + JS::RootedValue exc(cx); + if(!JS_GetPendingException(cx, &exc)) { + fprintf(stderr, "Failed to compile file: %s\n", filename); + } else { + JS::RootedObject exc_obj(cx, &exc.toObject()); + JSErrorReport* report = JS_ErrorFromException(cx, exc_obj); + couch_error(cx, report); + } + return 1; + } + + JS::RootedValue result(cx); + if(JS_ExecuteScript(cx, script, &result) != true) { + JS::RootedValue exc(cx); + if(!JS_GetPendingException(cx, &exc)) { + fprintf(stderr, "Failed to execute script.\n"); + } else { + JS::RootedObject exc_obj(cx, &exc.toObject()); + JSErrorReport* report = JS_ErrorFromException(cx, exc_obj); + couch_error(cx, report); + } + } + + // Give the GC a chance to run. + JS_MaybeGC(cx); + } + return 0; +} + +int +main(int argc, const char* argv[]) +{ + JSContext* cx = NULL; + int ret; + + couch_args* args = couch_parse_args(argc, argv); + + JS_Init(); + cx = JS_NewContext(args->stack_size); + if(cx == NULL) { + JS_ShutDown(); + return 1; + } + ret = runWithContext(cx, args); + JS_DestroyContext(cx); + JS_ShutDown(); + + return ret; +} diff --git a/src/couch/priv/couch_js/140/util.cpp b/src/couch/priv/couch_js/140/util.cpp new file mode 100644 index 000000000..d2381cedb --- /dev/null +++ b/src/couch/priv/couch_js/140/util.cpp @@ -0,0 +1,380 @@ +// 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. + +#include <stdlib.h> +#include <string.h> + +#include <sstream> + +#include <jsapi.h> +#include <jsfriendapi.h> +#include <js/CharacterEncoding.h> +#include <js/Conversions.h> +#include <js/Initialization.h> +#include <js/MemoryFunctions.h> +#include <js/RegExp.h> + +#include "help.h" +#include "util.h" + +const char* +get_spidermonkey_version() { + const char *JAVASCRIPT = "JavaScript-C"; + int js_len = strlen(JAVASCRIPT); + + // JS_GetImplementationVersion() + // returns "JavaScript-CMAJOR.MINOR.PATCH" + const char *FULLVERSION = JS_GetImplementationVersion(); + int fv_len = strlen(FULLVERSION); + + const char* foundJSString = strstr(FULLVERSION,JAVASCRIPT); + if (foundJSString != NULL) { + //trim off "JavaScript-C", + char *buf = (char*) malloc((fv_len - js_len + 1) * sizeof(char)); + strncpy(buf, &FULLVERSION[js_len], fv_len - js_len); + buf[fv_len - js_len] = '\0'; + return buf; + } else { + //something changed in JS_GetImplementationVersion(), return original + return FULLVERSION; + } +} + +std::string +js_to_string(JSContext* cx, JS::HandleValue val) +{ + JS::AutoSaveExceptionState exc_state(cx); + JS::RootedString sval(cx); + sval = val.toString(); + + JS::UniqueChars chars(JS_EncodeStringToUTF8(cx, sval)); + if(!chars) { + JS_ClearPendingException(cx); + return std::string(); + } + + return chars.get(); +} + +bool +js_to_string(JSContext* cx, JS::HandleValue val, std::string& str) +{ + if(!val.isString()) { + return false; + } + + if(JS_GetStringLength(val.toString()) == 0) { + str = ""; + return true; + } + + std::string conv = js_to_string(cx, val); + if(!conv.size()) { + return false; + } + + str = conv; + return true; +} + +JSString* +string_to_js(JSContext* cx, const std::string& raw) +{ + JS::UTF8Chars utf8(raw.c_str(), raw.size()); + JS::UniqueTwoByteChars utf16; + size_t len; + + utf16.reset(JS::UTF8CharsToNewTwoByteCharsZ(cx, utf8, &len, js::MallocArena).get()); + if(!utf16) { + return nullptr; + } + + return JS_NewUCString(cx, std::move(utf16), len); +} + +size_t +couch_readfile(const char* file, char** outbuf_p) +{ + FILE* fp; + char fbuf[16384]; + char *buf = NULL; + char* tmp; + size_t nread = 0; + size_t buflen = 0; + + if(strcmp(file, "-") == 0) { + fp = stdin; + } else { + fp = fopen(file, "r"); + if(fp == NULL) { + fprintf(stderr, "Failed to read file: %s\n", file); + exit(3); + } + } + + while((nread = fread(fbuf, 1, 16384, fp)) > 0) { + if(buf == NULL) { + buf = new char[nread + 1]; + if(buf == NULL) { + fprintf(stderr, "Out of memory.\n"); + exit(3); + } + memcpy(buf, fbuf, nread); + } else { + tmp = new char[buflen + nread + 1]; + if(tmp == NULL) { + fprintf(stderr, "Out of memory.\n"); + exit(3); + } + memcpy(tmp, buf, buflen); + memcpy(tmp+buflen, fbuf, nread); + delete buf; + buf = tmp; + } + buflen += nread; + buf[buflen] = '\0'; + } + *outbuf_p = buf; + return buflen ; +} + +couch_args* +couch_parse_args(int argc, const char* argv[]) +{ + couch_args* args; + int i = 1; + + args = new couch_args(); + if(args == NULL) + return NULL; + + args->eval = 0; + args->stack_size = 64L * 1024L * 1024L; + args->scripts = nullptr; + + while(i < argc) { + if(strcmp("-h", argv[i]) == 0) { + DISPLAY_USAGE; + exit(0); + } else if(strcmp("-V", argv[i]) == 0) { + DISPLAY_VERSION; + exit(0); + } else if(strcmp("-S", argv[i]) == 0) { + args->stack_size = atoi(argv[++i]); + if(args->stack_size <= 0) { + fprintf(stderr, "Invalid stack size.\n"); + exit(2); + } + } else if(strcmp("--eval", argv[i]) == 0) { + args->eval = 1; + } else if(strcmp("--", argv[i]) == 0) { + i++; + break; + } else { + break; + } + i++; + } + + if(i >= argc) { + DISPLAY_USAGE; + exit(3); + } + args->scripts = argv + i; + + return args; +} + + +int +couch_fgets(char* buf, int size, FILE* fp) +{ + int n, i, c; + + if(size <= 0) return -1; + n = size - 1; + + for(i = 0; i < n && (c = getc(fp)) != EOF; i++) { + buf[i] = c; + if(c == '\n') { + i++; + break; + } + } + + buf[i] = '\0'; + return i; +} + + +JSString* +couch_readline(JSContext* cx, FILE* fp) +{ + JSString* str; + char* bytes = NULL; + char* tmp = NULL; + size_t used = 0; + size_t byteslen = 256; + size_t oldbyteslen = 256; + size_t readlen = 0; + + bytes = static_cast<char*>(JS_malloc(cx, byteslen)); + if(bytes == NULL) return NULL; + + while((readlen = couch_fgets(bytes+used, byteslen-used, fp)) > 0) { + used += readlen; + + if(bytes[used-1] == '\n') { + bytes[used-1] = '\0'; + break; + } + + // Double our buffer and read more. + oldbyteslen = byteslen; + byteslen *= 2; + tmp = static_cast<char*>(JS_realloc(cx, bytes, oldbyteslen, byteslen)); + if(!tmp) { + JS_free(cx, bytes); + return NULL; + } + + bytes = tmp; + } + + // Treat empty strings specially + if(used == 0) { + JS_free(cx, bytes); + return JS_NewStringCopyZ(cx, nullptr); + } + + // Shrink the buffer to the actual data size + tmp = static_cast<char*>(JS_realloc(cx, bytes, byteslen, used)); + if(!tmp) { + JS_free(cx, bytes); + return NULL; + } + bytes = tmp; + byteslen = used; + + str = string_to_js(cx, std::string(tmp)); + JS_free(cx, bytes); + return str; +} + + +void +couch_print(JSContext* cx, JS::HandleValue obj, bool use_stderr) +{ + FILE *stream = stdout; + + if (use_stderr) { + stream = stderr; + } + std::string val = js_to_string(cx, obj); + fprintf(stream, "%s\n", val.c_str()); + fflush(stream); +} + + +void +couch_error(JSContext* cx, JSErrorReport* report) +{ + if(!report) { + return; + } + + if(report->isWarning()) { + return; + } + + std::ostringstream msg; + msg << "error: " << report->message().c_str(); + + mozilla::Maybe<JSAutoRealm> ar; + JS::RootedValue exc(cx); + JS::RootedObject exc_obj(cx); + JS::RootedObject stack_obj(cx); + JS::RootedString stack_str(cx); + JS::RootedValue stack_val(cx); + JSPrincipals* principals = GetRealmPrincipals(js::GetContextRealm(cx)); + + if(!JS_GetPendingException(cx, &exc)) { + goto done; + } + + // Clear the exception before an JS method calls or the result is + // infinite, recursive error report generation. + JS_ClearPendingException(cx); + + exc_obj.set(exc.toObjectOrNull()); + stack_obj.set(JS::ExceptionStackOrNull(exc_obj)); + + if(!stack_obj) { + // Compilation errors don't have a stack + + msg << " at "; + + if(report->filename) { +#if MOZJS_MAJOR_VERSION < 128 + msg << report->filename; +#else // MOZJS_MAJOR_VERSION >= 128 + msg << report->filename.c_str(); +#endif + } else { + msg << "<unknown>"; + } + + if(report->lineno) { +#if MOZJS_MAJOR_VERSION < 128 + msg << ':' << report->lineno << ':' << report->column; +#else // MOZJS_MAJOR_VERSION >= 128 + msg << ':' << report->lineno << ':' << + report->column.oneOriginValue(); +#endif + } + + goto done; + } + + if(!JS::BuildStackString(cx, principals, stack_obj, &stack_str, 2)) { + goto done; + } + + stack_val.set(JS::StringValue(stack_str)); + msg << std::endl << std::endl << js_to_string(cx, stack_val).c_str(); + +done: + msg << std::endl; + fprintf(stderr, "%s", msg.str().c_str()); +} + + +void +couch_oom(JSContext* cx, void* data) +{ + fprintf(stderr, "out of memory\n"); + _Exit(1); +} + + +bool +couch_load_funcs(JSContext* cx, JS::HandleObject obj, JSFunctionSpec* funcs) +{ + JSFunctionSpec* f; + for(f = funcs; f->name; f++) { + if(!JS_DefineFunction(cx, obj, f->name.string(), f->call.op, f->nargs, f->flags)) { + fprintf(stderr, "Failed to create function: %s\n", f->name.string()); + return false; + } + } + return true; +} diff --git a/src/couch/priv/couch_js/140/util.h b/src/couch/priv/couch_js/140/util.h new file mode 100644 index 000000000..28d44f4ba --- /dev/null +++ b/src/couch/priv/couch_js/140/util.h @@ -0,0 +1,43 @@ +// 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. + +#ifndef COUCHJS_UTIL_H +#define COUCHJS_UTIL_H + +#include <jsapi.h> + +typedef struct { + int eval; + int use_http; + int use_test_funs; + int stack_size; + const char** scripts; + const char* uri_file; + JSString* uri; +} couch_args; + +const char* get_spidermonkey_version(); + +std::string js_to_string(JSContext* cx, JS::HandleValue val); +bool js_to_string(JSContext* cx, JS::HandleValue val, std::string& str); +JSString* string_to_js(JSContext* cx, const std::string& s); + +couch_args* couch_parse_args(int argc, const char* argv[]); +int couch_fgets(char* buf, int size, FILE* fp); +JSString* couch_readline(JSContext* cx, FILE* fp); +size_t couch_readfile(const char* file, char** outbuf_p); +void couch_print(JSContext* cx, JS::HandleValue str, bool use_stderr); +void couch_error(JSContext* cx, JSErrorReport* report); +void couch_oom(JSContext* cx, void* data); +bool couch_load_funcs(JSContext* cx, JS::HandleObject obj, JSFunctionSpec* funcs); + +#endif // Included util.h diff --git a/src/couch/rebar.config.script b/src/couch/rebar.config.script index 1c9809902..abfab31bd 100644 --- a/src/couch/rebar.config.script +++ b/src/couch/rebar.config.script @@ -97,13 +97,15 @@ SMVsn = case lists:keyfind(spidermonkey_version, 1, CouchConfig) of "115"; {_, "128"} -> "128"; + {_, "140"} -> + "140"; undefined -> - "91"; + "140"; {_, Unsupported} -> io:format(standard_error, "Unsupported SpiderMonkey version: ~s~n", [Unsupported]), erlang:halt(1); false -> - "91" + "140" end. ConfigH = [ @@ -129,6 +131,8 @@ CouchJSConfig = case SMVsn of "priv/couch_js/102/config.h"; "128" -> "priv/couch_js/102/config.h"; + "140" -> + "priv/couch_js/140/config.h"; _ -> "priv/couch_js/" ++ SMVsn ++ "/config.h" end. @@ -214,7 +218,7 @@ end. "-DXP_UNIX -I/usr/include/mozjs-86 -I/usr/local/include/mozjs-86 -I/opt/homebrew/include/mozjs-86/ -std=c++17 -Wno-invalid-offsetof", "-L/usr/local/lib -L /opt/homebrew/lib/ -std=c++17 -lmozjs-86 -lm" }; - {unix, _} when SMVsn == "91"; SMVsn == "102"; SMVsn == "115"; SMVsn == "128" -> + {unix, _} when SMVsn == "91"; SMVsn == "102"; SMVsn == "115"; SMVsn == "128"; SMVsn == "140" -> { "$CFLAGS -DXP_UNIX " ++ MozJSIncludePath ++ " -std=c++17 -Wno-invalid-offsetof", "$LDFLAGS " ++ MozJSLibPath ++ " -std=c++17 -lm -lmozjs-" ++ SMVsn @@ -224,7 +228,7 @@ end. "/std:c++17 /DXP_WIN", "$LDFLAGS mozjs-91.lib" }; - {win32, _} when SMVsn == "102"; SMVsn == "115"; SMVsn == "128" -> + {win32, _} when SMVsn == "102"; SMVsn == "115"; SMVsn == "128"; SMVsn == "140" -> { "/std:c++17 /DXP_WIN /Zc:preprocessor /utf-8", "$LDFLAGS mozjs-" ++ SMVsn ++ ".lib" @@ -240,7 +244,8 @@ CouchJSSrc = case SMVsn of "91" -> ["priv/couch_js/86/*.cpp"]; "102" -> ["priv/couch_js/102/*.cpp"]; "115" -> ["priv/couch_js/102/*.cpp"]; - "128" -> ["priv/couch_js/102/*.cpp"] + "128" -> ["priv/couch_js/102/*.cpp"]; + "140" -> ["priv/couch_js/140/*.cpp"] end. CouchJSEnv = case SMVsn of diff --git a/src/docs/src/api/database/changes.rst b/src/docs/src/api/database/changes.rst index cbf47b6fb..812e3b5db 100644 --- a/src/docs/src/api/database/changes.rst +++ b/src/docs/src/api/database/changes.rst @@ -115,8 +115,9 @@ the filtering criteria. :query number timeout: Maximum period in *milliseconds* to wait for a change before the response is sent, even if there are no results. - Only applicable for :ref:`longpoll <changes/longpoll>` or - :ref:`continuous <changes/continuous>` feeds. + Only applicable for :ref:`longpoll <changes/longpoll>`, + :ref:`continuous <changes/continuous>` or + :ref:`eventsource <changes/eventsource>` feeds. Default value is specified by :config:option:`chttpd/changes_timeout` configuration option. Note that ``60000`` value is also the default maximum timeout to prevent undetected dead connections. diff --git a/src/dreyfus/src/dreyfus_fabric_group1.erl b/src/dreyfus/src/dreyfus_fabric_group1.erl index 9b08a94eb..990d6d24e 100644 --- a/src/dreyfus/src/dreyfus_fabric_group1.erl +++ b/src/dreyfus/src/dreyfus_fabric_group1.erl @@ -63,8 +63,8 @@ go(DbName, DDoc, IndexName, #index_query_args{} = QueryArgs) -> #shard.ref, fun handle_message/3, State, - infinity, - 1000 * 60 * 60 + fabric_util:timeout("search", "infinity"), + fabric_util:timeout("search_permsg", "3600000") ) after rexi_monitor:stop(RexiMon), diff --git a/src/dreyfus/src/dreyfus_fabric_group2.erl b/src/dreyfus/src/dreyfus_fabric_group2.erl index 3059aa30e..613ac6555 100644 --- a/src/dreyfus/src/dreyfus_fabric_group2.erl +++ b/src/dreyfus/src/dreyfus_fabric_group2.erl @@ -68,8 +68,8 @@ go(DbName, DDoc, IndexName, #index_query_args{} = QueryArgs) -> #shard.ref, fun handle_message/3, State, - infinity, - 1000 * 60 * 60 + fabric_util:timeout("search", "infinity"), + fabric_util:timeout("search_permsg", "3600000") ) after rexi_monitor:stop(RexiMon), diff --git a/src/fabric/src/fabric_view_all_docs.erl b/src/fabric/src/fabric_view_all_docs.erl index 2d0133acb..17f522a2b 100644 --- a/src/fabric/src/fabric_view_all_docs.erl +++ b/src/fabric/src/fabric_view_all_docs.erl @@ -144,7 +144,7 @@ go(DbName, _Options, Workers, QueryArgs, Callback, Acc0) -> fun handle_message/3, State, fabric_util:view_timeout(QueryArgs), - 5000 + fabric_util:timeout("all_docs_view_permsg", "5000") ) of {ok, NewState} -> diff --git a/src/fabric/src/fabric_view_changes.erl b/src/fabric/src/fabric_view_changes.erl index df958b6b4..dd2386f5e 100644 --- a/src/fabric/src/fabric_view_changes.erl +++ b/src/fabric/src/fabric_view_changes.erl @@ -543,7 +543,7 @@ get_old_seq(#shard{range = R} = Shard, SinceSeqs) -> get_db_uuid_shards(DbName) -> % Need to use an isolated process as we are performing a fabric call from - % another fabric call and there is a good chance we'd polute the mailbox + % another fabric call and there is a good chance we'd pollute the mailbox % with returned messages Timeout = fabric_util:request_timeout(), IsolatedFun = fun() -> fabric:db_uuids(DbName) end, diff --git a/src/setup/test/t-frontend-setup.sh b/src/setup/test/t-frontend-setup.sh index e025cfba2..106312dec 100755 --- a/src/setup/test/t-frontend-setup.sh +++ b/src/setup/test/t-frontend-setup.sh @@ -64,8 +64,8 @@ curl a:b@127.0.0.1:25984/_node/node2@127.0.0.1/_config/cluster/n curl a:b@127.0.0.1:15984/_node/node1@127.0.0.1/_config/couchdb/uuid curl a:b@127.0.0.1:15984/_node/node2@127.0.0.1/_config/couchdb/uuid -curl a:b@127.0.0.1:15984/_node/node1@127.0.0.1/_config/couch_httpd_auth/secret -curl a:b@127.0.0.1:15984/_node/node2@127.0.0.1/_config/couch_httpd_auth/secret +curl a:b@127.0.0.1:15984/_node/node1@127.0.0.1/_config/chttpd_auth/secret +curl a:b@127.0.0.1:15984/_node/node2@127.0.0.1/_config/chttpd_auth/secret echo "YAY ALL GOOD"