merge
This commit is contained in:
commit
d46e551f11
|
@ -22,7 +22,7 @@ Contributing to FoundationDB can be in contributions to the code base, sharing y
|
|||
|
||||
### Binary downloads
|
||||
|
||||
Developers interested in using FoundationDB can get started by downloading and installing a binary package. Please see the [downloads page](https://www.foundationdb.org/download/) for a list of available packages.
|
||||
Developers interested in using FoundationDB can get started by downloading and installing a binary package. Please see the [downloads page](https://github.com/apple/foundationdb/releases) for a list of available packages.
|
||||
|
||||
|
||||
### Compiling from source
|
||||
|
@ -181,4 +181,4 @@ Under Windows, only Visual Studio with ClangCl is supported
|
|||
1. `mkdir build && cd build`
|
||||
1. `cmake -G "Visual Studio 16 2019" -A x64 -T ClangCl <PATH_TO_FOUNDATIONDB_SOURCE>`
|
||||
1. `msbuild /p:Configuration=Release foundationdb.sln`
|
||||
1. To increase build performance, use `/p:UseMultiToolTask=true` and `/p:CL_MPCount=<NUMBER_OF_PARALLEL_JOBS>`
|
||||
1. To increase build performance, use `/p:UseMultiToolTask=true` and `/p:CL_MPCount=<NUMBER_OF_PARALLEL_JOBS>`
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
set(FDB_C_SRCS
|
||||
fdb_c.cpp
|
||||
foundationdb/fdb_c.h
|
||||
ThreadCleanup.cpp)
|
||||
foundationdb/fdb_c.h)
|
||||
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/foundationdb)
|
||||
|
||||
|
@ -18,6 +17,8 @@ endif()
|
|||
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64" OR CMAKE_SYSTEM_PROCESSOR MATCHES "arm64")
|
||||
set(cpu "aarch64")
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(ppc64le|powerpc64le)")
|
||||
set(cpu "ppc64le")
|
||||
endif()
|
||||
|
||||
set(IS_ARM_MAC NO)
|
||||
|
@ -49,7 +50,7 @@ endif()
|
|||
add_dependencies(fdb_c fdb_c_generated fdb_c_options)
|
||||
add_dependencies(fdbclient fdb_c_options)
|
||||
add_dependencies(fdbclient_sampling fdb_c_options)
|
||||
target_link_libraries(fdb_c PUBLIC $<BUILD_INTERFACE:fdbclient>)
|
||||
target_link_libraries(fdb_c PRIVATE $<BUILD_INTERFACE:fdbclient>)
|
||||
if(APPLE)
|
||||
set(symbols ${CMAKE_CURRENT_BINARY_DIR}/fdb_c.symbols)
|
||||
add_custom_command(OUTPUT ${symbols}
|
||||
|
@ -74,10 +75,8 @@ if(WIN32)
|
|||
set_property(SOURCE ${asm_file} PROPERTY LANGUAGE ASM_MASM)
|
||||
endif()
|
||||
|
||||
# The tests don't build on windows and ARM macs
|
||||
# doctest doesn't seem to compile on ARM macs, we should
|
||||
# check later whether this works
|
||||
if(NOT WIN32 AND NOT IS_ARM_MAC)
|
||||
# The tests don't build on windows
|
||||
if(NOT WIN32)
|
||||
set(MAKO_SRCS
|
||||
test/mako/mako.c
|
||||
test/mako/mako.h
|
||||
|
@ -121,9 +120,9 @@ if(NOT WIN32 AND NOT IS_ARM_MAC)
|
|||
strip_debug_symbols(fdb_c_ryw_benchmark)
|
||||
strip_debug_symbols(fdb_c_txn_size_test)
|
||||
endif()
|
||||
target_link_libraries(fdb_c_performance_test PRIVATE fdb_c)
|
||||
target_link_libraries(fdb_c_ryw_benchmark PRIVATE fdb_c)
|
||||
target_link_libraries(fdb_c_txn_size_test PRIVATE fdb_c)
|
||||
target_link_libraries(fdb_c_performance_test PRIVATE fdb_c Threads::Threads)
|
||||
target_link_libraries(fdb_c_ryw_benchmark PRIVATE fdb_c Threads::Threads)
|
||||
target_link_libraries(fdb_c_txn_size_test PRIVATE fdb_c Threads::Threads)
|
||||
|
||||
add_dependencies(fdb_c_setup_tests doctest)
|
||||
add_dependencies(fdb_c_unit_tests doctest)
|
||||
|
@ -134,14 +133,14 @@ if(NOT WIN32 AND NOT IS_ARM_MAC)
|
|||
target_include_directories(fdb_c_unit_tests_version_510 PUBLIC ${DOCTEST_INCLUDE_DIR})
|
||||
target_include_directories(disconnected_timeout_unit_tests PUBLIC ${DOCTEST_INCLUDE_DIR})
|
||||
target_link_libraries(fdb_c_setup_tests PRIVATE fdb_c Threads::Threads)
|
||||
target_link_libraries(fdb_c_unit_tests PRIVATE fdb_c Threads::Threads)
|
||||
target_link_libraries(fdb_c_unit_tests PRIVATE fdb_c Threads::Threads fdbclient)
|
||||
target_link_libraries(fdb_c_unit_tests_version_510 PRIVATE fdb_c Threads::Threads)
|
||||
target_link_libraries(trace_partial_file_suffix_test PRIVATE fdb_c Threads::Threads)
|
||||
target_link_libraries(trace_partial_file_suffix_test PRIVATE fdb_c Threads::Threads flow)
|
||||
target_link_libraries(disconnected_timeout_unit_tests PRIVATE fdb_c Threads::Threads)
|
||||
|
||||
# do not set RPATH for mako
|
||||
set_property(TARGET mako PROPERTY SKIP_BUILD_RPATH TRUE)
|
||||
target_link_libraries(mako PRIVATE fdb_c)
|
||||
target_link_libraries(mako PRIVATE fdb_c fdbclient)
|
||||
|
||||
if(NOT OPEN_FOR_IDE)
|
||||
# Make sure that fdb_c.h is compatible with c90
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
/*
|
||||
* ThreadCleanup.cpp
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
|
||||
*
|
||||
* 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 "flow/Platform.h"
|
||||
#include "flow/FastAlloc.h"
|
||||
|
||||
#if defined(WIN32)
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, LPVOID reserved) {
|
||||
|
||||
if (reason == DLL_THREAD_DETACH)
|
||||
releaseAllThreadMagazines();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#elif defined(__unixish__)
|
||||
|
||||
#ifdef __INTEL_COMPILER
|
||||
#pragma warning(disable : 2415)
|
||||
#endif
|
||||
|
||||
static pthread_key_t threadDestructorKey;
|
||||
|
||||
static void threadDestructor(void*) {
|
||||
releaseAllThreadMagazines();
|
||||
}
|
||||
|
||||
void registerThread() {
|
||||
pthread_setspecific(threadDestructorKey, (const void*)1);
|
||||
}
|
||||
|
||||
static int initThreadDestructorKey() {
|
||||
if (!pthread_key_create(&threadDestructorKey, &threadDestructor)) {
|
||||
registerThread();
|
||||
setFastAllocatorThreadInitFunction(®isterThread);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int threadDestructorKeyInit = initThreadDestructorKey();
|
||||
|
||||
#else
|
||||
#error Port me!
|
||||
#endif
|
|
@ -76,7 +76,8 @@ extern "C" DLLEXPORT fdb_bool_t fdb_error_predicate(int predicate_test, fdb_erro
|
|||
return code == error_code_not_committed || code == error_code_transaction_too_old ||
|
||||
code == error_code_future_version || code == error_code_database_locked ||
|
||||
code == error_code_proxy_memory_limit_exceeded || code == error_code_batch_transaction_throttled ||
|
||||
code == error_code_process_behind || code == error_code_tag_throttled;
|
||||
code == error_code_process_behind || code == error_code_tag_throttled ||
|
||||
code == error_code_unknown_tenant;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -59,9 +59,10 @@ def write_windows_asm(asmfile, functions):
|
|||
|
||||
|
||||
def write_unix_asm(asmfile, functions, prefix):
|
||||
if cpu != "aarch64":
|
||||
if cpu != "aarch64" and cpu!= "ppc64le":
|
||||
asmfile.write(".intel_syntax noprefix\n")
|
||||
|
||||
i = 0
|
||||
if os == 'linux' or os == 'freebsd':
|
||||
asmfile.write("\n.data\n")
|
||||
for f in functions:
|
||||
|
@ -70,8 +71,13 @@ def write_unix_asm(asmfile, functions, prefix):
|
|||
if os == 'linux' or os == 'freebsd':
|
||||
asmfile.write("\n.text\n")
|
||||
for f in functions:
|
||||
if cpu == "ppc64le":
|
||||
asmfile.write("\n.LC%d:\n" % (i))
|
||||
asmfile.write("\t.quad \tfdb_api_ptr_%s\n" % (f))
|
||||
asmfile.write("\t.align 2\n")
|
||||
i = i + 1
|
||||
asmfile.write("\t.global %s\n\t.type %s, @function\n" % (f, f))
|
||||
|
||||
i = 0
|
||||
for f in functions:
|
||||
asmfile.write("\n.globl %s%s\n" % (prefix, f))
|
||||
if cpu == 'aarch64' and os == 'osx':
|
||||
|
@ -118,6 +124,46 @@ def write_unix_asm(asmfile, functions, prefix):
|
|||
assert False, '{} not supported for Arm yet'.format(os)
|
||||
asmfile.write("\tldr x8, [x8]\n")
|
||||
asmfile.write("\tbr x8\n")
|
||||
elif cpu == "ppc64le":
|
||||
asmfile.write("\n.LCF%d:\n" % (i))
|
||||
asmfile.write("\taddis 2,12,.TOC.-.LCF%d@ha\n" % (i))
|
||||
asmfile.write("\taddi 2,2,.TOC.-.LCF%d@l\n" % (i))
|
||||
asmfile.write("\tmflr 0\n")
|
||||
asmfile.write("\tstd 31, -8(1)\n")
|
||||
asmfile.write("\tstd 0,16(1)\n")
|
||||
asmfile.write("\tstdu 1,-192(1)\n")
|
||||
#asmfile.write("\tstd 2,24(1)\n")
|
||||
asmfile.write("\taddis 11,2,.LC%d@toc@ha\n" % (i))
|
||||
asmfile.write("\tld 11,.LC%d@toc@l(11)\n" % (i))
|
||||
asmfile.write("\tld 12,0(11)\n")
|
||||
asmfile.write("\tstd 2,24(1)\n")
|
||||
asmfile.write("\tlwa 11,344(1)\n")
|
||||
asmfile.write("\tmtctr 12\n")
|
||||
asmfile.write("\tstd 11,152(1)\n")
|
||||
asmfile.write("\tlwa 11,352(1)\n")
|
||||
asmfile.write("\tstd 11,160(1)\n")
|
||||
asmfile.write("\tlwa 11,336(1)\n")
|
||||
asmfile.write("\tstd 11,144(1)\n")
|
||||
asmfile.write("\tlwa 11,328(1)\n")
|
||||
asmfile.write("\tstd 11,136(1)\n")
|
||||
asmfile.write("\tlwa 11,320(1)\n")
|
||||
asmfile.write("\tstd 11,128(1)\n")
|
||||
asmfile.write("\tlwa 11,312(1)\n")
|
||||
asmfile.write("\tstd 11,120(1)\n")
|
||||
asmfile.write("\tlwa 11,304(1)\n")
|
||||
asmfile.write("\tstd 11,112(1)\n")
|
||||
asmfile.write("\tld 11,296(1)\n")
|
||||
asmfile.write("\tstd 11,104(1)\n")
|
||||
asmfile.write("\tlwa 11,288(1)\n")
|
||||
asmfile.write("\tstd 11,96(1)\n")
|
||||
asmfile.write("\tbctrl\n")
|
||||
asmfile.write("\tld 2,24(1)\n")
|
||||
asmfile.write("\taddi 1,1,192\n")
|
||||
asmfile.write("\tld 0,16(1)\n")
|
||||
asmfile.write("\tld 31, -8(1)\n")
|
||||
asmfile.write("\tmtlr 0\n")
|
||||
asmfile.write("\tblr\n")
|
||||
i = i + 1
|
||||
else:
|
||||
asmfile.write(
|
||||
"\tmov r11, qword ptr [%sfdb_api_ptr_%s@GOTPCREL+rip]\n" % (prefix, f))
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <linux/limits.h>
|
||||
|
@ -615,7 +616,7 @@ int64_t granule_start_load(const char* filename,
|
|||
// don't seek if offset == 0
|
||||
if (offset && fseek(fp, offset, SEEK_SET)) {
|
||||
// if fseek was non-zero, it failed
|
||||
fprintf(stderr, "ERROR: BG could not seek to %ld in file %s\n", offset, full_fname);
|
||||
fprintf(stderr, "ERROR: BG could not seek to %" PRId64 " in file %s\n", offset, full_fname);
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
|
@ -625,7 +626,7 @@ int64_t granule_start_load(const char* filename,
|
|||
fclose(fp);
|
||||
|
||||
if (readSize != length) {
|
||||
fprintf(stderr, "ERROR: BG could not read %ld bytes from file: %s\n", length, full_fname);
|
||||
fprintf(stderr, "ERROR: BG could not read %" PRId64 " bytes from file: %s\n", length, full_fname);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -636,7 +637,7 @@ int64_t granule_start_load(const char* filename,
|
|||
uint8_t* granule_get_load(int64_t loadId, void* userContext) {
|
||||
BGLocalFileContext* context = (BGLocalFileContext*)userContext;
|
||||
if (context->data_by_id[loadId] == 0) {
|
||||
fprintf(stderr, "ERROR: BG loadId invalid for get_load: %ld\n", loadId);
|
||||
fprintf(stderr, "ERROR: BG loadId invalid for get_load: %" PRId64 "\n", loadId);
|
||||
return 0;
|
||||
}
|
||||
return context->data_by_id[loadId];
|
||||
|
@ -645,7 +646,7 @@ uint8_t* granule_get_load(int64_t loadId, void* userContext) {
|
|||
void granule_free_load(int64_t loadId, void* userContext) {
|
||||
BGLocalFileContext* context = (BGLocalFileContext*)userContext;
|
||||
if (context->data_by_id[loadId] == 0) {
|
||||
fprintf(stderr, "ERROR: BG loadId invalid for free_load: %ld\n", loadId);
|
||||
fprintf(stderr, "ERROR: BG loadId invalid for free_load: %" PRId64 "\n", loadId);
|
||||
}
|
||||
free(context->data_by_id[loadId]);
|
||||
context->data_by_id[loadId] = 0;
|
||||
|
@ -1119,7 +1120,7 @@ int run_workload(FDBTransaction* transaction,
|
|||
if (tracetimer == dotrace) {
|
||||
fdb_error_t err;
|
||||
tracetimer = 0;
|
||||
snprintf(traceid, 32, "makotrace%019ld", total_xacts);
|
||||
snprintf(traceid, 32, "makotrace%019" PRId64, total_xacts);
|
||||
fprintf(debugme, "DEBUG: txn tracing %s\n", traceid);
|
||||
err = fdb_transaction_set_option(transaction,
|
||||
FDB_TR_OPTION_DEBUG_TRANSACTION_IDENTIFIER,
|
||||
|
@ -1283,7 +1284,7 @@ void* worker_thread(void* thread_args) {
|
|||
}
|
||||
|
||||
fprintf(debugme,
|
||||
"DEBUG: worker_id:%d (%d) thread_id:%d (%d) database_index:%lu (tid:%lu)\n",
|
||||
"DEBUG: worker_id:%d (%d) thread_id:%d (%d) database_index:%lu (tid:%" PRIu64 ")\n",
|
||||
worker_id,
|
||||
args->num_processes,
|
||||
thread_id,
|
||||
|
@ -1350,6 +1351,11 @@ void* worker_thread(void* thread_args) {
|
|||
char str2[1000];
|
||||
sprintf(str2, "%s%d", TEMP_DATA_STORE, *parent_id);
|
||||
rc = mkdir(str2, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
||||
if (rc < 0 && errno != EEXIST) {
|
||||
int ec = errno;
|
||||
fprintf(stderr, "Failed to make directory: %s because %s\n", str2, strerror(ec));
|
||||
goto failExit;
|
||||
}
|
||||
for (op = 0; op < MAX_OP; op++) {
|
||||
if (args->txnspec.ops[op][OP_COUNT] > 0 || op == OP_COMMIT || op == OP_TRANSACTION) {
|
||||
FILE* fp;
|
||||
|
@ -1357,6 +1363,11 @@ void* worker_thread(void* thread_args) {
|
|||
strcat(file_name, str2);
|
||||
get_stats_file_name(file_name, worker_id, thread_id, op);
|
||||
fp = fopen(file_name, "w");
|
||||
if (!fp) {
|
||||
int ec = errno;
|
||||
fprintf(stderr, "Failed to open file: %s because %s\n", file_name, strerror(ec));
|
||||
goto failExit;
|
||||
}
|
||||
lat_block_t* temp_block = ((thread_args_t*)thread_args)->block[op];
|
||||
if (is_memory_allocated[op]) {
|
||||
size = stats->latency_samples[op] / LAT_BLOCK_SIZE;
|
||||
|
@ -1376,11 +1387,11 @@ void* worker_thread(void* thread_args) {
|
|||
fclose(fp);
|
||||
}
|
||||
}
|
||||
__sync_fetch_and_add(stopcount, 1);
|
||||
}
|
||||
|
||||
/* fall through */
|
||||
failExit:
|
||||
__sync_fetch_and_add(stopcount, 1);
|
||||
for (op = 0; op < MAX_OP; op++) {
|
||||
lat_block_t* curr = ((thread_args_t*)thread_args)->block[op];
|
||||
lat_block_t* prev = NULL;
|
||||
|
@ -2240,9 +2251,9 @@ void print_stats(mako_args_t* args, mako_stats_t* stats, struct timespec* now, s
|
|||
for (op = 0; op < MAX_OP; op++) {
|
||||
if (args->txnspec.ops[op][OP_COUNT] > 0) {
|
||||
uint64_t ops_total_diff = ops_total[op] - ops_total_prev[op];
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", ops_total_diff);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) PRIu64 " ", ops_total_diff);
|
||||
if (fp) {
|
||||
fprintf(fp, "\"%s\": %lu,", get_ops_name(op), ops_total_diff);
|
||||
fprintf(fp, "\"%s\": %" PRIu64 ",", get_ops_name(op), ops_total_diff);
|
||||
}
|
||||
errors_diff[op] = errors_total[op] - errors_total_prev[op];
|
||||
print_err = (errors_diff[op] > 0);
|
||||
|
@ -2270,7 +2281,7 @@ void print_stats(mako_args_t* args, mako_stats_t* stats, struct timespec* now, s
|
|||
printf("%" STR(STATS_TITLE_WIDTH) "s ", "Errors");
|
||||
for (op = 0; op < MAX_OP; op++) {
|
||||
if (args->txnspec.ops[op][OP_COUNT] > 0) {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", errors_diff[op]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) PRIu64 " ", errors_diff[op]);
|
||||
if (fp) {
|
||||
fprintf(fp, ",\"errors\": %.2f", conflicts_diff);
|
||||
}
|
||||
|
@ -2419,10 +2430,10 @@ void print_report(mako_args_t* args,
|
|||
break;
|
||||
}
|
||||
}
|
||||
printf("Total Xacts: %8lu\n", totalxacts);
|
||||
printf("Total Conflicts: %8lu\n", conflicts);
|
||||
printf("Total Errors: %8lu\n", totalerrors);
|
||||
printf("Overall TPS: %8lu\n\n", totalxacts * 1000000000 / duration_nsec);
|
||||
printf("Total Xacts: %8" PRIu64 "\n", totalxacts);
|
||||
printf("Total Conflicts: %8" PRIu64 "\n", conflicts);
|
||||
printf("Total Errors: %8" PRIu64 "\n", totalerrors);
|
||||
printf("Overall TPS: %8" PRIu64 "\n\n", totalxacts * 1000000000 / duration_nsec);
|
||||
|
||||
if (fp) {
|
||||
fprintf(fp, "\"results\": {");
|
||||
|
@ -2430,10 +2441,10 @@ void print_report(mako_args_t* args,
|
|||
fprintf(fp, "\"totalProcesses\": %d,", args->num_processes);
|
||||
fprintf(fp, "\"totalThreads\": %d,", args->num_threads);
|
||||
fprintf(fp, "\"targetTPS\": %d,", args->tpsmax);
|
||||
fprintf(fp, "\"totalXacts\": %lu,", totalxacts);
|
||||
fprintf(fp, "\"totalConflicts\": %lu,", conflicts);
|
||||
fprintf(fp, "\"totalErrors\": %lu,", totalerrors);
|
||||
fprintf(fp, "\"overallTPS\": %lu,", totalxacts * 1000000000 / duration_nsec);
|
||||
fprintf(fp, "\"totalXacts\": %" PRIu64 ",", totalxacts);
|
||||
fprintf(fp, "\"totalConflicts\": %" PRIu64 ",", conflicts);
|
||||
fprintf(fp, "\"totalErrors\": %" PRIu64 ",", totalerrors);
|
||||
fprintf(fp, "\"overallTPS\": %" PRIu64 ",", totalxacts * 1000000000 / duration_nsec);
|
||||
}
|
||||
|
||||
/* per-op stats */
|
||||
|
@ -2446,14 +2457,14 @@ void print_report(mako_args_t* args,
|
|||
}
|
||||
for (op = 0; op < MAX_OP; op++) {
|
||||
if ((args->txnspec.ops[op][OP_COUNT] > 0 && op != OP_TRANSACTION) || op == OP_COMMIT) {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", ops_total[op]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) PRIu64 " ", ops_total[op]);
|
||||
if (fp) {
|
||||
if (first_op) {
|
||||
first_op = 0;
|
||||
} else {
|
||||
fprintf(fp, ",");
|
||||
}
|
||||
fprintf(fp, "\"%s\": %lu", get_ops_name(op), ops_total[op]);
|
||||
fprintf(fp, "\"%s\": %" PRIu64, get_ops_name(op), ops_total[op]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2475,14 +2486,14 @@ void print_report(mako_args_t* args,
|
|||
first_op = 1;
|
||||
for (op = 0; op < MAX_OP; op++) {
|
||||
if (args->txnspec.ops[op][OP_COUNT] > 0 && op != OP_TRANSACTION) {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", errors_total[op]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) PRIu64 " ", errors_total[op]);
|
||||
if (fp) {
|
||||
if (first_op) {
|
||||
first_op = 0;
|
||||
} else {
|
||||
fprintf(fp, ",");
|
||||
}
|
||||
fprintf(fp, "\"%s\": %lu", get_ops_name(op), errors_total[op]);
|
||||
fprintf(fp, "\"%s\": %" PRIu64, get_ops_name(op), errors_total[op]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2500,7 +2511,7 @@ void print_report(mako_args_t* args,
|
|||
for (op = 0; op < MAX_OP; op++) {
|
||||
if (args->txnspec.ops[op][OP_COUNT] > 0 || op == OP_TRANSACTION || op == OP_COMMIT) {
|
||||
if (lat_total[op]) {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", lat_samples[op]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) PRIu64 " ", lat_samples[op]);
|
||||
} else {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "s ", "N/A");
|
||||
}
|
||||
|
@ -2510,7 +2521,7 @@ void print_report(mako_args_t* args,
|
|||
} else {
|
||||
fprintf(fp, ",");
|
||||
}
|
||||
fprintf(fp, "\"%s\": %lu", get_ops_name(op), lat_samples[op]);
|
||||
fprintf(fp, "\"%s\": %" PRIu64, get_ops_name(op), lat_samples[op]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2527,14 +2538,14 @@ void print_report(mako_args_t* args,
|
|||
if (lat_min[op] == -1) {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "s ", "N/A");
|
||||
} else {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", lat_min[op]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) PRIu64 " ", lat_min[op]);
|
||||
if (fp) {
|
||||
if (first_op) {
|
||||
first_op = 0;
|
||||
} else {
|
||||
fprintf(fp, ",");
|
||||
}
|
||||
fprintf(fp, "\"%s\": %lu", get_ops_name(op), lat_min[op]);
|
||||
fprintf(fp, "\"%s\": %" PRIu64, get_ops_name(op), lat_min[op]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2550,14 +2561,14 @@ void print_report(mako_args_t* args,
|
|||
for (op = 0; op < MAX_OP; op++) {
|
||||
if (args->txnspec.ops[op][OP_COUNT] > 0 || op == OP_TRANSACTION || op == OP_COMMIT) {
|
||||
if (lat_total[op]) {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", lat_total[op] / lat_samples[op]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) PRIu64 " ", lat_total[op] / lat_samples[op]);
|
||||
if (fp) {
|
||||
if (first_op) {
|
||||
first_op = 0;
|
||||
} else {
|
||||
fprintf(fp, ",");
|
||||
}
|
||||
fprintf(fp, "\"%s\": %lu", get_ops_name(op), lat_total[op] / lat_samples[op]);
|
||||
fprintf(fp, "\"%s\": %" PRIu64, get_ops_name(op), lat_total[op] / lat_samples[op]);
|
||||
}
|
||||
} else {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "s ", "N/A");
|
||||
|
@ -2577,14 +2588,14 @@ void print_report(mako_args_t* args,
|
|||
if (lat_max[op] == 0) {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "s ", "N/A");
|
||||
} else {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", lat_max[op]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) PRIu64 " ", lat_max[op]);
|
||||
if (fp) {
|
||||
if (first_op) {
|
||||
first_op = 0;
|
||||
} else {
|
||||
fprintf(fp, ",");
|
||||
}
|
||||
fprintf(fp, "\"%s\": %lu", get_ops_name(op), lat_max[op]);
|
||||
fprintf(fp, "\"%s\": %" PRIu64, get_ops_name(op), lat_max[op]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2635,14 +2646,14 @@ void print_report(mako_args_t* args,
|
|||
} else {
|
||||
median = (dataPoints[op][num_points[op] / 2] + dataPoints[op][num_points[op] / 2 - 1]) >> 1;
|
||||
}
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", median);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) PRIu64 " ", median);
|
||||
if (fp) {
|
||||
if (first_op) {
|
||||
first_op = 0;
|
||||
} else {
|
||||
fprintf(fp, ",");
|
||||
}
|
||||
fprintf(fp, "\"%s\": %lu", get_ops_name(op), median);
|
||||
fprintf(fp, "\"%s\": %" PRIu64, get_ops_name(op), median);
|
||||
}
|
||||
} else {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "s ", "N/A");
|
||||
|
@ -2665,14 +2676,14 @@ void print_report(mako_args_t* args,
|
|||
}
|
||||
if (lat_total[op]) {
|
||||
point_95pct = ((float)(num_points[op]) * 0.95) - 1;
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", dataPoints[op][point_95pct]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) PRIu64 " ", dataPoints[op][point_95pct]);
|
||||
if (fp) {
|
||||
if (first_op) {
|
||||
first_op = 0;
|
||||
} else {
|
||||
fprintf(fp, ",");
|
||||
}
|
||||
fprintf(fp, "\"%s\": %lu", get_ops_name(op), dataPoints[op][point_95pct]);
|
||||
fprintf(fp, "\"%s\": %" PRIu64, get_ops_name(op), dataPoints[op][point_95pct]);
|
||||
}
|
||||
} else {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "s ", "N/A");
|
||||
|
@ -2695,14 +2706,14 @@ void print_report(mako_args_t* args,
|
|||
}
|
||||
if (lat_total[op]) {
|
||||
point_99pct = ((float)(num_points[op]) * 0.99) - 1;
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", dataPoints[op][point_99pct]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) PRIu64 " ", dataPoints[op][point_99pct]);
|
||||
if (fp) {
|
||||
if (first_op) {
|
||||
first_op = 0;
|
||||
} else {
|
||||
fprintf(fp, ",");
|
||||
}
|
||||
fprintf(fp, "\"%s\": %lu", get_ops_name(op), dataPoints[op][point_99pct]);
|
||||
fprintf(fp, "\"%s\": %" PRIu64, get_ops_name(op), dataPoints[op][point_99pct]);
|
||||
}
|
||||
} else {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "s ", "N/A");
|
||||
|
@ -2725,14 +2736,14 @@ void print_report(mako_args_t* args,
|
|||
}
|
||||
if (lat_total[op]) {
|
||||
point_99_9pct = ((float)(num_points[op]) * 0.999) - 1;
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", dataPoints[op][point_99_9pct]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) PRIu64 " ", dataPoints[op][point_99_9pct]);
|
||||
if (fp) {
|
||||
if (first_op) {
|
||||
first_op = 0;
|
||||
} else {
|
||||
fprintf(fp, ",");
|
||||
}
|
||||
fprintf(fp, "\"%s\": %lu", get_ops_name(op), dataPoints[op][point_99_9pct]);
|
||||
fprintf(fp, "\"%s\": %" PRIu64, get_ops_name(op), dataPoints[op][point_99_9pct]);
|
||||
}
|
||||
} else {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "s ", "N/A");
|
||||
|
|
|
@ -67,25 +67,25 @@ void runTests(struct ResultSet* rs) {
|
|||
fdb_transaction_set(tr, keys[i], KEY_SIZE, valueStr, VALUE_SIZE);
|
||||
e = getSize(rs, tr, sizes + i);
|
||||
checkError(e, "transaction get size", rs);
|
||||
printf("size %d: %ld\n", i, sizes[i]);
|
||||
printf("size %d: %" PRId64 "\n", i, sizes[i]);
|
||||
i++;
|
||||
|
||||
fdb_transaction_set(tr, keys[i], KEY_SIZE, valueStr, VALUE_SIZE);
|
||||
e = getSize(rs, tr, sizes + i);
|
||||
checkError(e, "transaction get size", rs);
|
||||
printf("size %d: %ld\n", i, sizes[i]);
|
||||
printf("size %d: %" PRId64 "\n", i, sizes[i]);
|
||||
i++;
|
||||
|
||||
fdb_transaction_clear(tr, keys[i], KEY_SIZE);
|
||||
e = getSize(rs, tr, sizes + i);
|
||||
checkError(e, "transaction get size", rs);
|
||||
printf("size %d: %ld\n", i, sizes[i]);
|
||||
printf("size %d: %" PRId64 "\n", i, sizes[i]);
|
||||
i++;
|
||||
|
||||
fdb_transaction_clear_range(tr, keys[i], KEY_SIZE, keys[i + 1], KEY_SIZE);
|
||||
e = getSize(rs, tr, sizes + i);
|
||||
checkError(e, "transaction get size", rs);
|
||||
printf("size %d: %ld\n", i, sizes[i]);
|
||||
printf("size %d: %" PRId64 "\n", i, sizes[i]);
|
||||
i++;
|
||||
|
||||
for (j = 0; j + 1 < i; j++) {
|
||||
|
|
|
@ -6,7 +6,7 @@ ExternalProject_Add(
|
|||
doctest
|
||||
PREFIX ${CMAKE_BINARY_DIR}/doctest
|
||||
GIT_REPOSITORY https://github.com/onqtam/doctest.git
|
||||
GIT_TAG 1c8da00c978c19e00a434b2b1f854fcffc9fba35 # v2.4.0
|
||||
GIT_TAG 8424be522357e68d8c6178375546bb0cf9d5f6b3 # v2.4.1
|
||||
TIMEOUT 10
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
|
|
|
@ -2358,6 +2358,19 @@ TEST_CASE("commit_does_not_reset") {
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Fast alloc thread cleanup") {
|
||||
// Try to cause an OOM if thread cleanup doesn't work
|
||||
for (int i = 0; i < 50000; ++i) {
|
||||
auto thread = std::thread([]() {
|
||||
fdb::Transaction tr(db);
|
||||
for (int s = 0; s < 11; ++s) {
|
||||
tr.set(key("foo"), std::string(8 << s, '\x00'));
|
||||
}
|
||||
});
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 3) {
|
||||
std::cout << "Unit tests for the FoundationDB C API.\n"
|
||||
|
|
|
@ -18,6 +18,7 @@ set(SRCS
|
|||
|
||||
add_flow_target(STATIC_LIBRARY NAME fdb_flow SRCS ${SRCS})
|
||||
target_link_libraries(fdb_flow PUBLIC fdb_c)
|
||||
target_link_libraries(fdb_flow PUBLIC fdbclient)
|
||||
target_include_directories(fdb_flow PUBLIC
|
||||
"${CMAKE_CURRENT_BINARY_DIR}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
#include <stdio.h>
|
||||
#include <cinttypes>
|
||||
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "contrib/fmt-8.1.1/include/fmt/format.h"
|
||||
#include "flow/DeterministicRandom.h"
|
||||
#include "flow/SystemMonitor.h"
|
||||
#include "flow/TLSConfig.actor.h"
|
||||
|
|
|
@ -598,6 +598,11 @@ func (o TransactionOptions) SetBypassUnreadable() error {
|
|||
return o.setOpt(1100, nil)
|
||||
}
|
||||
|
||||
// Allows this transaction to use cached GRV from the database context. Defaults to off. Upon first usage, starts a background updater to periodically update the cache to avoid stale read versions.
|
||||
func (o TransactionOptions) SetUseGrvCache() error {
|
||||
return o.setOpt(1101, nil)
|
||||
}
|
||||
|
||||
type StreamingMode int
|
||||
|
||||
const (
|
||||
|
|
|
@ -154,6 +154,7 @@ endif()
|
|||
set_target_properties(java_workloads PROPERTIES
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/share/foundationdb")
|
||||
target_link_libraries(java_workloads PUBLIC fdb_c ${JNI_LIBRARIES})
|
||||
target_link_libraries(java_workloads PRIVATE flow) # mostly for boost
|
||||
target_include_directories(java_workloads PUBLIC ${JNI_INCLUDE_DIRS})
|
||||
|
||||
set(CMAKE_JAVA_COMPILE_FLAGS "-source" "1.8" "-target" "1.8" "-XDignore.symbol.file")
|
||||
|
@ -228,6 +229,8 @@ if(NOT OPEN_FOR_IDE)
|
|||
else()
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
|
||||
set(lib_destination "linux/aarch64")
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64le")
|
||||
set(lib_destination "linux/ppc64le")
|
||||
else()
|
||||
set(lib_destination "linux/amd64")
|
||||
endif()
|
||||
|
|
|
@ -182,7 +182,7 @@ public class JNIUtil {
|
|||
private static OS getRunningOS() {
|
||||
String osname = System.getProperty("os.name").toLowerCase();
|
||||
String arch = System.getProperty("os.arch");
|
||||
if (!arch.equals("amd64") && !arch.equals("x86_64") && !arch.equals("aarch64")) {
|
||||
if (!arch.equals("amd64") && !arch.equals("x86_64") && !arch.equals("aarch64") && !arch.equals("ppc64le")) {
|
||||
throw new IllegalStateException("Unknown or unsupported arch: " + arch);
|
||||
}
|
||||
if (osname.startsWith("windows")) {
|
||||
|
|
|
@ -219,7 +219,7 @@ else()
|
|||
endif()
|
||||
if(STATIC_LINK_LIBCXX)
|
||||
if (NOT USE_LIBCXX AND NOT APPLE)
|
||||
add_link_options(-static-libstdc++ -static-libgcc)
|
||||
add_link_options(-static-libstdc++ -static-libgcc)
|
||||
endif()
|
||||
endif()
|
||||
# # Instruction sets we require to be supported by the CPU
|
||||
|
@ -309,7 +309,7 @@ else()
|
|||
if (PROFILE_INSTR_GENERATE)
|
||||
message(FATAL_ERROR "Can't set both PROFILE_INSTR_GENERATE and PROFILE_INSTR_USE")
|
||||
endif()
|
||||
add_compile_options(-Wno-error=profile-instr-out-of-date)
|
||||
add_compile_options(-Wno-error=profile-instr-out-of-date -Wno-error=profile-instr-unprofiled)
|
||||
add_compile_options(-fprofile-instr-use=${PROFILE_INSTR_USE})
|
||||
add_link_options(-fprofile-instr-use=${PROFILE_INSTR_USE})
|
||||
endif()
|
||||
|
@ -349,6 +349,9 @@ else()
|
|||
add_compile_options(-march=armv8.2-a+crc+simd)
|
||||
endif()
|
||||
|
||||
if (CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64le")
|
||||
add_compile_options(-m64 -mcpu=power9 -mtune=power9 -DNO_WARN_X86_INTRINSICS)
|
||||
endif()
|
||||
# Check whether we can use dtrace probes
|
||||
include(CheckSymbolExists)
|
||||
check_symbol_exists(DTRACE_PROBE sys/sdt.h SUPPORT_DTRACE)
|
||||
|
|
|
@ -303,7 +303,9 @@ set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION
|
|||
"/etc/rc.d/init.d"
|
||||
"/usr/lib/pkgconfig"
|
||||
"/usr/lib/foundationdb"
|
||||
"/usr/lib/cmake")
|
||||
"/usr/lib/cmake"
|
||||
"/usr/lib/foundationdb-${FDB_VERSION}/etc/foundationdb"
|
||||
)
|
||||
set(CPACK_RPM_DEBUGINFO_PACKAGE ${GENERATE_DEBUG_PACKAGES})
|
||||
#set(CPACK_RPM_BUILD_SOURCE_FDB_INSTALL_DIRS_PREFIX /usr/src)
|
||||
set(CPACK_RPM_COMPONENT_INSTALL ON)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
add_subdirectory(fmt-8.0.1)
|
||||
add_subdirectory(fmt-8.1.1)
|
||||
if(NOT WIN32)
|
||||
add_subdirectory(debug_determinism)
|
||||
add_subdirectory(monitoring)
|
||||
add_subdirectory(TraceLogHelper)
|
||||
add_subdirectory(TestHarness)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
add_library(debug_determinism STATIC debug_determinism.cpp)
|
||||
|
||||
# So that we can link to libfdb_c.so. Not strictly necessary but convenient for use with our
|
||||
# TRACE_PC_GUARD_INSTRUMENTATION_LIB cmake option
|
||||
target_compile_options(debug_determinism PRIVATE -fPIC)
|
|
@ -0,0 +1,45 @@
|
|||
Utilities for debugging unseed mismatches for foundationdb simulation tests.
|
||||
|
||||
99/100 times the source of the nondeterminism is use of uninitialized memory and
|
||||
what you want to do is build with `-DUSE_VALGRIND=ON` and run simulations under
|
||||
valgrind.
|
||||
|
||||
Common sources of nondeterminism and specialized tools to find them.
|
||||
1. Use of uninitialized memory (use valgrind!)
|
||||
1. Memory errors (use valgrind and/or asan)
|
||||
1. Undefined behavior (use ubsan. You can also try _GLIBCXX_DEBUG)
|
||||
|
||||
If it's not any of these then now it's time to try this technique. Look for
|
||||
|
||||
1. Call to some kind of "get current time" function that's not in `INetwork`
|
||||
1. Depending on the relative ordering of allocated memory. E.g. Using heap-allocated pointers as keys in a `std::map`.
|
||||
1. Inspecting something about the current state of the system (e.g. free disk space)
|
||||
1. Depending on iteration order of an unordered map
|
||||
|
||||
# Quickstart
|
||||
|
||||
Set these cmake flags
|
||||
|
||||
```
|
||||
-DTRACE_PC_GUARD_INSTRUMENTATION_LIB=$BUILDDIR/lib/libdebug_determinism.a
|
||||
```
|
||||
|
||||
and change `#define DEBUG_DETERMINISM 0` to `#define DEBUG_DETERMINISM 1` in
|
||||
flow/Platform.h. This disables several known sources of nondeterminism that
|
||||
don't affect unseeds.
|
||||
|
||||
For reasons I don't fully understand, it appears that sqlite exhibits some
|
||||
nondeterminism if you don't add `#define SQLITE_OMIT_LOOKASIDE` to the top of
|
||||
fdbserver/sqlite/sqlite3.amalgamation.c, so you probably want to do that too.
|
||||
|
||||
Now when you run an fdbserver simulation, it will write a file `out.bin` in the
|
||||
current directory which contains the sequence of edges in the control flow graph
|
||||
that were encountered during the simulation. If you rename `out.bin` to `in.bin`
|
||||
and then re-run, the simulation will validate that the sequence of edges is the
|
||||
same as the last run. If it's not, then the simulation will enter an infinite
|
||||
loop at the first difference and print a message. Then you probably want to
|
||||
attach gdb to the process and investigate from there.
|
||||
|
||||
You'll need to make sure you delete the `simfdb` folder before each run, because
|
||||
otherwise you'll take a different codepath for deleting the `simfdb` folder at
|
||||
the beginning of simulation.
|
|
@ -0,0 +1,52 @@
|
|||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace {
|
||||
FILE* out = nullptr;
|
||||
FILE* in = nullptr;
|
||||
void loop_forever() {
|
||||
// Try to convince the optimizer not to optimize away this loop
|
||||
static volatile uint64_t x = 0;
|
||||
for (;;) {
|
||||
++x;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// This callback is inserted by the compiler as a module constructor
|
||||
// into every DSO. 'start' and 'stop' correspond to the
|
||||
// beginning and end of the section with the guards for the entire
|
||||
// binary (executable or DSO). The callback will be called at least
|
||||
// once per DSO and may be called multiple times with the same parameters.
|
||||
extern "C" void __sanitizer_cov_trace_pc_guard_init(uint32_t* start, uint32_t* stop) {
|
||||
in = fopen("in.bin", "r");
|
||||
out = fopen("out.bin", "w");
|
||||
static uint64_t N; // Counter for the guards.
|
||||
if (start == stop || *start)
|
||||
return; // Initialize only once.
|
||||
for (uint32_t* x = start; x < stop; x++) {
|
||||
*x = ++N; // Guards should start from 1.
|
||||
}
|
||||
}
|
||||
|
||||
// This callback is inserted by the compiler on every edge in the
|
||||
// control flow (some optimizations apply).
|
||||
// Typically, the compiler will emit the code like this:
|
||||
// if(*guard)
|
||||
// __sanitizer_cov_trace_pc_guard(guard);
|
||||
// But for large functions it will emit a simple call:
|
||||
// __sanitizer_cov_trace_pc_guard(guard);
|
||||
extern "C" void __sanitizer_cov_trace_pc_guard(uint32_t* guard) {
|
||||
if (!guard) {
|
||||
return;
|
||||
}
|
||||
fwrite(guard, sizeof(*guard), 1, out);
|
||||
if (in) {
|
||||
uint32_t theirs;
|
||||
auto read = fread(&theirs, sizeof(theirs), 1, in);
|
||||
if (read != 1 || *guard != theirs) {
|
||||
printf("Non-determinism detected\n");
|
||||
loop_forever();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,181 +0,0 @@
|
|||
// Formatting library for C++ - std::ostream support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_OSTREAM_H_
|
||||
#define FMT_OSTREAM_H_
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
template <typename Char> class basic_printf_parse_context;
|
||||
template <typename OutputIt, typename Char> class basic_printf_context;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class Char> class formatbuf : public std::basic_streambuf<Char> {
|
||||
private:
|
||||
using int_type = typename std::basic_streambuf<Char>::int_type;
|
||||
using traits_type = typename std::basic_streambuf<Char>::traits_type;
|
||||
|
||||
buffer<Char>& buffer_;
|
||||
|
||||
public:
|
||||
formatbuf(buffer<Char>& buf) : buffer_(buf) {}
|
||||
|
||||
protected:
|
||||
// The put-area is actually always empty. This makes the implementation
|
||||
// simpler and has the advantage that the streambuf and the buffer are always
|
||||
// in sync and sputc never writes into uninitialized memory. The obvious
|
||||
// disadvantage is that each call to sputc always results in a (virtual) call
|
||||
// to overflow. There is no disadvantage here for sputn since this always
|
||||
// results in a call to xsputn.
|
||||
|
||||
int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE {
|
||||
if (!traits_type::eq_int_type(ch, traits_type::eof()))
|
||||
buffer_.push_back(static_cast<Char>(ch));
|
||||
return ch;
|
||||
}
|
||||
|
||||
std::streamsize xsputn(const Char* s, std::streamsize count) FMT_OVERRIDE {
|
||||
buffer_.append(s, s + count);
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
struct converter {
|
||||
template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> converter(T);
|
||||
};
|
||||
|
||||
template <typename Char> struct test_stream : std::basic_ostream<Char> {
|
||||
private:
|
||||
void_t<> operator<<(converter);
|
||||
};
|
||||
|
||||
// Hide insertion operators for built-in types.
|
||||
template <typename Char, typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<Char, Traits>&, Char);
|
||||
template <typename Char, typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<Char, Traits>&, char);
|
||||
template <typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<char, Traits>&, char);
|
||||
template <typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<char, Traits>&, signed char);
|
||||
template <typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<char, Traits>&, unsigned char);
|
||||
|
||||
// Checks if T has a user-defined operator<< (e.g. not a member of
|
||||
// std::ostream).
|
||||
template <typename T, typename Char> class is_streamable {
|
||||
private:
|
||||
template <typename U>
|
||||
static bool_constant<!std::is_same<decltype(std::declval<test_stream<Char>&>()
|
||||
<< std::declval<U>()),
|
||||
void_t<>>::value>
|
||||
test(int);
|
||||
|
||||
template <typename> static std::false_type test(...);
|
||||
|
||||
using result = decltype(test<T>(0));
|
||||
|
||||
public:
|
||||
is_streamable() = default;
|
||||
|
||||
static const bool value = result::value;
|
||||
};
|
||||
|
||||
// Write the content of buf to os.
|
||||
template <typename Char>
|
||||
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||
const Char* buf_data = buf.data();
|
||||
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
|
||||
unsigned_streamsize size = buf.size();
|
||||
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
|
||||
do {
|
||||
unsigned_streamsize n = size <= max_size ? size : max_size;
|
||||
os.write(buf_data, static_cast<std::streamsize>(n));
|
||||
buf_data += n;
|
||||
size -= n;
|
||||
} while (size != 0);
|
||||
}
|
||||
|
||||
template <typename Char, typename T>
|
||||
void format_value(buffer<Char>& buf, const T& value,
|
||||
locale_ref loc = locale_ref()) {
|
||||
formatbuf<Char> format_buf(buf);
|
||||
std::basic_ostream<Char> output(&format_buf);
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
if (loc) output.imbue(loc.get<std::locale>());
|
||||
#endif
|
||||
output << value;
|
||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
buf.try_resize(buf.size());
|
||||
}
|
||||
|
||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||
template <typename T, typename Char>
|
||||
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
|
||||
: private formatter<basic_string_view<Char>, Char> {
|
||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||
-> decltype(ctx.begin()) {
|
||||
return formatter<basic_string_view<Char>, Char>::parse(ctx);
|
||||
}
|
||||
template <typename ParseCtx,
|
||||
FMT_ENABLE_IF(std::is_same<
|
||||
ParseCtx, basic_printf_parse_context<Char>>::value)>
|
||||
auto parse(ParseCtx& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename OutputIt>
|
||||
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx)
|
||||
-> OutputIt {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
format_value(buffer, value, ctx.locale());
|
||||
basic_string_view<Char> str(buffer.data(), buffer.size());
|
||||
return formatter<basic_string_view<Char>, Char>::format(str, ctx);
|
||||
}
|
||||
template <typename OutputIt>
|
||||
auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx)
|
||||
-> OutputIt {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
format_value(buffer, value, ctx.locale());
|
||||
return std::copy(buffer.begin(), buffer.end(), ctx.out());
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
FMT_MODULE_EXPORT
|
||||
template <typename Char>
|
||||
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
detail::vformat_to(buffer, format_str, args);
|
||||
detail::write_buffer(os, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the stream *os*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print(cerr, "Don't {}!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
FMT_MODULE_EXPORT
|
||||
template <typename S, typename... Args,
|
||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
|
||||
vprint(os, to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OSTREAM_H_
|
|
@ -1,468 +0,0 @@
|
|||
// Formatting library for C++ - experimental range support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
//
|
||||
// Copyright (c) 2018 - present, Remotion (Igor Schulz)
|
||||
// All Rights Reserved
|
||||
// {fmt} support for ranges, containers and types tuple interface.
|
||||
|
||||
#ifndef FMT_RANGES_H_
|
||||
#define FMT_RANGES_H_
|
||||
|
||||
#include <initializer_list>
|
||||
#include <type_traits>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
template <typename Char, typename Enable = void> struct formatting_range {
|
||||
#ifdef FMT_DEPRECATED_BRACED_RANGES
|
||||
Char prefix = '{';
|
||||
Char postfix = '}';
|
||||
#else
|
||||
Char prefix = '[';
|
||||
Char postfix = ']';
|
||||
#endif
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename Enable = void> struct formatting_tuple {
|
||||
Char prefix = '(';
|
||||
Char postfix = ')';
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename RangeT, typename OutputIterator>
|
||||
OutputIterator copy(const RangeT& range, OutputIterator out) {
|
||||
for (auto it = range.begin(), end = range.end(); it != end; ++it)
|
||||
*out++ = *it;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
OutputIterator copy(const char* str, OutputIterator out) {
|
||||
while (*str) *out++ = *str++;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
OutputIterator copy(char ch, OutputIterator out) {
|
||||
*out++ = ch;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
OutputIterator copy(wchar_t ch, OutputIterator out) {
|
||||
*out++ = ch;
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Return true value if T has std::string interface, like std::string_view.
|
||||
template <typename T> class is_std_string_like {
|
||||
template <typename U>
|
||||
static auto check(U* p)
|
||||
-> decltype((void)p->find('a'), p->length(), (void)p->data(), int());
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
is_string<T>::value || !std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {};
|
||||
|
||||
template <typename... Ts> struct conditional_helper {};
|
||||
|
||||
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
|
||||
|
||||
#if !FMT_MSC_VER || FMT_MSC_VER > 1800
|
||||
|
||||
# define FMT_DECLTYPE_RETURN(val) \
|
||||
->decltype(val) { return val; } \
|
||||
static_assert( \
|
||||
true, "") // This makes it so that a semicolon is required after the
|
||||
// macro, which helps clang-format handle the formatting.
|
||||
|
||||
// C array overload
|
||||
template <typename T, std::size_t N>
|
||||
auto range_begin(const T (&arr)[N]) -> const T* {
|
||||
return arr;
|
||||
}
|
||||
template <typename T, std::size_t N>
|
||||
auto range_end(const T (&arr)[N]) -> const T* {
|
||||
return arr + N;
|
||||
}
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_member_fn_begin_end_t : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_member_fn_begin_end_t<T, void_t<decltype(std::declval<T>().begin()),
|
||||
decltype(std::declval<T>().end())>>
|
||||
: std::true_type {};
|
||||
|
||||
// Member function overload
|
||||
template <typename T>
|
||||
auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
|
||||
template <typename T>
|
||||
auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end());
|
||||
|
||||
// ADL overload. Only participates in overload resolution if member functions
|
||||
// are not found.
|
||||
template <typename T>
|
||||
auto range_begin(T&& rng)
|
||||
-> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
|
||||
decltype(begin(static_cast<T&&>(rng)))> {
|
||||
return begin(static_cast<T&&>(rng));
|
||||
}
|
||||
template <typename T>
|
||||
auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
|
||||
decltype(end(static_cast<T&&>(rng)))> {
|
||||
return end(static_cast<T&&>(rng));
|
||||
}
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_const_begin_end : std::false_type {};
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_mutable_begin_end : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_const_begin_end<
|
||||
T, void_t<decltype(detail::range_begin(
|
||||
std::declval<const remove_cvref_t<T>&>())),
|
||||
decltype(detail::range_begin(
|
||||
std::declval<const remove_cvref_t<T>&>()))>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_mutable_begin_end<
|
||||
T, void_t<decltype(detail::range_begin(std::declval<T>())),
|
||||
decltype(detail::range_begin(std::declval<T>())),
|
||||
enable_if_t<std::is_copy_constructible<T>::value>>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
struct is_range_<T, void>
|
||||
: std::integral_constant<bool, (has_const_begin_end<T>::value ||
|
||||
has_mutable_begin_end<T>::value)> {};
|
||||
|
||||
template <typename T, typename Enable = void> struct range_to_view;
|
||||
template <typename T>
|
||||
struct range_to_view<T, enable_if_t<has_const_begin_end<T>::value>> {
|
||||
struct view_t {
|
||||
const T* m_range_ptr;
|
||||
|
||||
auto begin() const FMT_DECLTYPE_RETURN(detail::range_begin(*m_range_ptr));
|
||||
auto end() const FMT_DECLTYPE_RETURN(detail::range_end(*m_range_ptr));
|
||||
};
|
||||
static auto view(const T& range) -> view_t { return {&range}; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct range_to_view<T, enable_if_t<!has_const_begin_end<T>::value &&
|
||||
has_mutable_begin_end<T>::value>> {
|
||||
struct view_t {
|
||||
T m_range_copy;
|
||||
|
||||
auto begin() FMT_DECLTYPE_RETURN(detail::range_begin(m_range_copy));
|
||||
auto end() FMT_DECLTYPE_RETURN(detail::range_end(m_range_copy));
|
||||
};
|
||||
static auto view(const T& range) -> view_t { return {range}; }
|
||||
};
|
||||
# undef FMT_DECLTYPE_RETURN
|
||||
#endif
|
||||
|
||||
/// tuple_size and tuple_element check.
|
||||
template <typename T> class is_tuple_like_ {
|
||||
template <typename U>
|
||||
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
};
|
||||
|
||||
// Check for integer_sequence
|
||||
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900
|
||||
template <typename T, T... N>
|
||||
using integer_sequence = std::integer_sequence<T, N...>;
|
||||
template <size_t... N> using index_sequence = std::index_sequence<N...>;
|
||||
template <size_t N> using make_index_sequence = std::make_index_sequence<N>;
|
||||
#else
|
||||
template <typename T, T... N> struct integer_sequence {
|
||||
using value_type = T;
|
||||
|
||||
static FMT_CONSTEXPR size_t size() { return sizeof...(N); }
|
||||
};
|
||||
|
||||
template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
|
||||
|
||||
template <typename T, size_t N, T... Ns>
|
||||
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
|
||||
template <typename T, T... Ns>
|
||||
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
|
||||
|
||||
template <size_t N>
|
||||
using make_index_sequence = make_integer_sequence<size_t, N>;
|
||||
#endif
|
||||
|
||||
template <class Tuple, class F, size_t... Is>
|
||||
void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) FMT_NOEXCEPT {
|
||||
using std::get;
|
||||
// using free function get<I>(T) now.
|
||||
const int _[] = {0, ((void)f(get<Is>(tup)), 0)...};
|
||||
(void)_; // blocks warnings
|
||||
}
|
||||
|
||||
template <class T>
|
||||
FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes(
|
||||
T const&) {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
|
||||
const auto indexes = get_indexes(tup);
|
||||
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <typename Range>
|
||||
using value_type =
|
||||
remove_cvref_t<decltype(*detail::range_begin(std::declval<Range>()))>;
|
||||
|
||||
template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
|
||||
*out++ = ',';
|
||||
*out++ = ' ';
|
||||
return out;
|
||||
}
|
||||
|
||||
template <
|
||||
typename Char, typename OutputIt, typename Arg,
|
||||
FMT_ENABLE_IF(is_std_string_like<typename std::decay<Arg>::type>::value)>
|
||||
OutputIt write_range_entry(OutputIt out, const Arg& v) {
|
||||
*out++ = '"';
|
||||
out = write<Char>(out, v);
|
||||
*out++ = '"';
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt, typename Arg,
|
||||
FMT_ENABLE_IF(std::is_same<Arg, Char>::value)>
|
||||
OutputIt write_range_entry(OutputIt out, const Arg v) {
|
||||
*out++ = '\'';
|
||||
*out++ = v;
|
||||
*out++ = '\'';
|
||||
return out;
|
||||
}
|
||||
|
||||
template <
|
||||
typename Char, typename OutputIt, typename Arg,
|
||||
FMT_ENABLE_IF(!is_std_string_like<typename std::decay<Arg>::type>::value &&
|
||||
!std::is_same<Arg, Char>::value)>
|
||||
OutputIt write_range_entry(OutputIt out, const Arg& v) {
|
||||
return write<Char>(out, v);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T> struct is_tuple_like {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
|
||||
};
|
||||
|
||||
template <typename TupleT, typename Char>
|
||||
struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
|
||||
private:
|
||||
// C++11 generic lambda for format()
|
||||
template <typename FormatContext> struct format_each {
|
||||
template <typename T> void operator()(const T& v) {
|
||||
if (i > 0) out = detail::write_delimiter(out);
|
||||
out = detail::write_range_entry<Char>(out, v);
|
||||
++i;
|
||||
}
|
||||
formatting_tuple<Char>& formatting;
|
||||
size_t& i;
|
||||
typename std::add_lvalue_reference<
|
||||
decltype(std::declval<FormatContext>().out())>::type out;
|
||||
};
|
||||
|
||||
public:
|
||||
formatting_tuple<Char> formatting;
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return formatting.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext = format_context>
|
||||
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
size_t i = 0;
|
||||
|
||||
detail::copy(formatting.prefix, out);
|
||||
detail::for_each(values, format_each<FormatContext>{formatting, i, out});
|
||||
detail::copy(formatting.postfix, out);
|
||||
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Char> struct is_range {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
detail::is_range_<T>::value && !detail::is_std_string_like<T>::value &&
|
||||
!std::is_convertible<T, std::basic_string<Char>>::value &&
|
||||
!std::is_constructible<detail::std_string_view<Char>, T>::value;
|
||||
};
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct formatter<
|
||||
T, Char,
|
||||
enable_if_t<
|
||||
fmt::is_range<T, Char>::value
|
||||
// Workaround a bug in MSVC 2017 and earlier.
|
||||
#if !FMT_MSC_VER || FMT_MSC_VER >= 1927
|
||||
&& (has_formatter<detail::value_type<T>, format_context>::value ||
|
||||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
|
||||
#endif
|
||||
>> {
|
||||
formatting_range<Char> formatting;
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return formatting.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator format(const T& values, FormatContext& ctx) {
|
||||
auto out = detail::copy(formatting.prefix, ctx.out());
|
||||
size_t i = 0;
|
||||
auto view = detail::range_to_view<T>::view(values);
|
||||
auto it = view.begin();
|
||||
auto end = view.end();
|
||||
for (; it != end; ++it) {
|
||||
if (i > 0) out = detail::write_delimiter(out);
|
||||
out = detail::write_range_entry<Char>(out, *it);
|
||||
++i;
|
||||
}
|
||||
return detail::copy(formatting.postfix, out);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename... T> struct tuple_join_view : detail::view {
|
||||
const std::tuple<T...>& tuple;
|
||||
basic_string_view<Char> sep;
|
||||
|
||||
tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s)
|
||||
: tuple(t), sep{s} {}
|
||||
};
|
||||
|
||||
template <typename Char, typename... T>
|
||||
using tuple_arg_join = tuple_join_view<Char, T...>;
|
||||
|
||||
template <typename Char, typename... T>
|
||||
struct formatter<tuple_join_view<Char, T...>, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx) ->
|
||||
typename FormatContext::iterator {
|
||||
return format(value, ctx, detail::make_index_sequence<sizeof...(T)>{});
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename FormatContext, size_t... N>
|
||||
auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
|
||||
detail::index_sequence<N...>) ->
|
||||
typename FormatContext::iterator {
|
||||
using std::get;
|
||||
return format_args(value, ctx, get<N>(value.tuple)...);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format_args(const tuple_join_view<Char, T...>&, FormatContext& ctx) ->
|
||||
typename FormatContext::iterator {
|
||||
// NOTE: for compilers that support C++17, this empty function instantiation
|
||||
// can be replaced with a constexpr branch in the variadic overload.
|
||||
return ctx.out();
|
||||
}
|
||||
|
||||
template <typename FormatContext, typename Arg, typename... Args>
|
||||
auto format_args(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
|
||||
const Arg& arg, const Args&... args) ->
|
||||
typename FormatContext::iterator {
|
||||
using base = formatter<typename std::decay<Arg>::type, Char>;
|
||||
auto out = base().format(arg, ctx);
|
||||
if (sizeof...(Args) > 0) {
|
||||
out = std::copy(value.sep.begin(), value.sep.end(), out);
|
||||
ctx.advance_to(out);
|
||||
return format_args(value, ctx, args...);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
|
||||
/**
|
||||
\rst
|
||||
Returns an object that formats `tuple` with elements separated by `sep`.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::tuple<int, char> t = {1, 'a'};
|
||||
fmt::print("{}", fmt::join(t, ", "));
|
||||
// Output: "1, a"
|
||||
\endrst
|
||||
*/
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
|
||||
-> tuple_join_view<char, T...> {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple,
|
||||
basic_string_view<wchar_t> sep)
|
||||
-> tuple_join_view<wchar_t, T...> {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Returns an object that formats `initializer_list` with elements separated by
|
||||
`sep`.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print("{}", fmt::join({1, 2, 3}, ", "));
|
||||
// Output: "1, 2, 3"
|
||||
\endrst
|
||||
*/
|
||||
template <typename T>
|
||||
auto join(std::initializer_list<T> list, string_view sep)
|
||||
-> join_view<const T*, const T*> {
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_RANGES_H_
|
|
@ -81,12 +81,13 @@ option(FMT_FUZZ "Generate the fuzz target." OFF)
|
|||
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
|
||||
option(FMT_OS "Include core requiring OS (Windows/Posix) " ON)
|
||||
option(FMT_MODULE "Build a module instead of a traditional library." OFF)
|
||||
option(FMT_SYSTEM_HEADERS "Expose headers with marking them as system." OFF)
|
||||
|
||||
set(FMT_CAN_MODULE OFF)
|
||||
if (CMAKE_CXX_STANDARD GREATER 17 AND
|
||||
# msvc 16.10-pre4
|
||||
MSVC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 19.29.30035)
|
||||
set(FMT_CAN_MODULE ON)
|
||||
set(FMT_CAN_MODULE OFF)
|
||||
endif ()
|
||||
if (NOT FMT_CAN_MODULE)
|
||||
set(FMT_MODULE OFF)
|
||||
|
@ -96,6 +97,10 @@ if (FMT_TEST AND FMT_MODULE)
|
|||
# The tests require {fmt} to be compiled as traditional library
|
||||
message(STATUS "Testing is incompatible with build mode 'module'.")
|
||||
endif ()
|
||||
set(FMT_SYSTEM_HEADERS_ATTRIBUTE "")
|
||||
if (FMT_SYSTEM_HEADERS)
|
||||
set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM)
|
||||
endif ()
|
||||
|
||||
# Get version from core.h
|
||||
file(READ include/fmt/core.h core_h)
|
||||
|
@ -151,7 +156,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
|||
-Wcast-align
|
||||
-Wctor-dtor-privacy -Wdisabled-optimization
|
||||
-Winvalid-pch -Woverloaded-virtual
|
||||
-Wconversion -Wswitch-enum -Wundef
|
||||
-Wconversion -Wundef
|
||||
-Wno-ctor-dtor-privacy -Wno-format-nonliteral)
|
||||
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
|
||||
|
@ -244,7 +249,7 @@ if (HAVE_STRTOD_L)
|
|||
endif ()
|
||||
|
||||
if (MINGW)
|
||||
check_cxx_compiler_flag("Wa,-mbig-obj" FMT_HAS_MBIG_OBJ)
|
||||
check_cxx_compiler_flag("-Wa,-mbig-obj" FMT_HAS_MBIG_OBJ)
|
||||
if (${FMT_HAS_MBIG_OBJ})
|
||||
target_compile_options(fmt PUBLIC "-Wa,-mbig-obj")
|
||||
endif()
|
||||
|
@ -262,7 +267,7 @@ endif ()
|
|||
|
||||
target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES})
|
||||
|
||||
target_include_directories(fmt PUBLIC
|
||||
target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
|
||||
|
||||
|
@ -298,7 +303,7 @@ add_library(fmt::fmt-header-only ALIAS fmt-header-only)
|
|||
target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
|
||||
target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES})
|
||||
|
||||
target_include_directories(fmt-header-only INTERFACE
|
||||
target_include_directories(fmt-header-only ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
|
||||
|
|
@ -143,6 +143,8 @@ class dynamic_format_arg_store
|
|||
}
|
||||
|
||||
public:
|
||||
constexpr dynamic_format_arg_store() = default;
|
||||
|
||||
/**
|
||||
\rst
|
||||
Adds an argument into the dynamic store for later passing to a formatting
|
File diff suppressed because it is too large
Load Diff
|
@ -185,9 +185,13 @@ enum class terminal_color : uint8_t {
|
|||
|
||||
enum class emphasis : uint8_t {
|
||||
bold = 1,
|
||||
italic = 1 << 1,
|
||||
underline = 1 << 2,
|
||||
strikethrough = 1 << 3
|
||||
faint = 1 << 1,
|
||||
italic = 1 << 2,
|
||||
underline = 1 << 3,
|
||||
blink = 1 << 4,
|
||||
reverse = 1 << 5,
|
||||
conceal = 1 << 6,
|
||||
strikethrough = 1 << 7,
|
||||
};
|
||||
|
||||
// rgb is a struct for red, green and blue colors.
|
||||
|
@ -409,16 +413,18 @@ template <typename Char> struct ansi_color_escape {
|
|||
buffer[19] = static_cast<Char>(0);
|
||||
}
|
||||
FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT {
|
||||
uint8_t em_codes[4] = {};
|
||||
uint8_t em_bits = static_cast<uint8_t>(em);
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::bold)) em_codes[0] = 1;
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::italic)) em_codes[1] = 3;
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::underline)) em_codes[2] = 4;
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::strikethrough))
|
||||
em_codes[3] = 9;
|
||||
uint8_t em_codes[num_emphases] = {};
|
||||
if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1;
|
||||
if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2;
|
||||
if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3;
|
||||
if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4;
|
||||
if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5;
|
||||
if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7;
|
||||
if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
|
||||
if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
|
||||
|
||||
size_t index = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
for (size_t i = 0; i < num_emphases; ++i) {
|
||||
if (!em_codes[i]) continue;
|
||||
buffer[index++] = static_cast<Char>('\x1b');
|
||||
buffer[index++] = static_cast<Char>('[');
|
||||
|
@ -435,7 +441,8 @@ template <typename Char> struct ansi_color_escape {
|
|||
}
|
||||
|
||||
private:
|
||||
Char buffer[7u + 3u * 4u + 1u];
|
||||
static constexpr size_t num_emphases = 8;
|
||||
Char buffer[7u + 3u * num_emphases + 1u];
|
||||
|
||||
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
|
||||
char delimiter) FMT_NOEXCEPT {
|
||||
|
@ -444,6 +451,10 @@ template <typename Char> struct ansi_color_escape {
|
|||
out[2] = static_cast<Char>('0' + c % 10);
|
||||
out[3] = static_cast<Char>(delimiter);
|
||||
}
|
||||
static FMT_CONSTEXPR bool has_emphasis(emphasis em,
|
||||
emphasis mask) FMT_NOEXCEPT {
|
||||
return static_cast<uint8_t>(em) & static_cast<uint8_t>(mask);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
|
@ -156,7 +156,7 @@ struct is_compiled_string : std::is_base_of<compiled_string, S> {};
|
|||
std::string s = fmt::format(FMT_COMPILE("{}"), 42);
|
||||
\endrst
|
||||
*/
|
||||
#ifdef __cpp_if_constexpr
|
||||
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||
# define FMT_COMPILE(s) \
|
||||
FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit)
|
||||
#else
|
||||
|
@ -179,7 +179,7 @@ const T& first(const T& value, const Tail&...) {
|
|||
return value;
|
||||
}
|
||||
|
||||
#ifdef __cpp_if_constexpr
|
||||
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||
template <typename... Args> struct type_list {};
|
||||
|
||||
// Returns a reference to the argument at index N from [first, rest...].
|
||||
|
@ -190,7 +190,7 @@ constexpr const auto& get([[maybe_unused]] const T& first,
|
|||
if constexpr (N == 0)
|
||||
return first;
|
||||
else
|
||||
return get<N - 1>(rest...);
|
||||
return detail::get<N - 1>(rest...);
|
||||
}
|
||||
|
||||
template <typename Char, typename... Args>
|
||||
|
@ -202,7 +202,8 @@ constexpr int get_arg_index_by_name(basic_string_view<Char> name,
|
|||
template <int N, typename> struct get_type_impl;
|
||||
|
||||
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
|
||||
using type = remove_cvref_t<decltype(get<N>(std::declval<Args>()...))>;
|
||||
using type =
|
||||
remove_cvref_t<decltype(detail::get<N>(std::declval<Args>()...))>;
|
||||
};
|
||||
|
||||
template <int N, typename T>
|
||||
|
@ -242,7 +243,7 @@ template <typename Char> struct code_unit {
|
|||
// This ensures that the argument type is convertible to `const T&`.
|
||||
template <typename T, int N, typename... Args>
|
||||
constexpr const T& get_arg_checked(const Args&... args) {
|
||||
const auto& arg = get<N>(args...);
|
||||
const auto& arg = detail::get<N>(args...);
|
||||
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
|
||||
return arg.value;
|
||||
} else {
|
||||
|
@ -289,7 +290,7 @@ template <typename Char> struct runtime_named_field {
|
|||
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
||||
bool found = (try_format_argument(out, name, args) || ...);
|
||||
if (!found) {
|
||||
throw format_error("argument with specified name is not found");
|
||||
FMT_THROW(format_error("argument with specified name is not found"));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
@ -399,7 +400,9 @@ template <typename Char> struct arg_id_handler {
|
|||
return 0;
|
||||
}
|
||||
|
||||
constexpr void on_error(const char* message) { throw format_error(message); }
|
||||
constexpr void on_error(const char* message) {
|
||||
FMT_THROW(format_error(message));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char> struct parse_arg_id_result {
|
||||
|
@ -451,7 +454,7 @@ constexpr auto compile_format_string(S format_str) {
|
|||
constexpr auto str = basic_string_view<char_type>(format_str);
|
||||
if constexpr (str[POS] == '{') {
|
||||
if constexpr (POS + 1 == str.size())
|
||||
throw format_error("unmatched '{' in format string");
|
||||
FMT_THROW(format_error("unmatched '{' in format string"));
|
||||
if constexpr (str[POS + 1] == '{') {
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||
} else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
|
||||
|
@ -500,7 +503,7 @@ constexpr auto compile_format_string(S format_str) {
|
|||
}
|
||||
} else if constexpr (str[POS] == '}') {
|
||||
if constexpr (POS + 1 == str.size())
|
||||
throw format_error("unmatched '}' in format string");
|
||||
FMT_THROW(format_error("unmatched '}' in format string"));
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||
} else {
|
||||
constexpr auto end = parse_text(str, POS + 1);
|
||||
|
@ -527,12 +530,12 @@ constexpr auto compile(S format_str) {
|
|||
return result;
|
||||
}
|
||||
}
|
||||
#endif // __cpp_if_constexpr
|
||||
#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||
} // namespace detail
|
||||
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
|
||||
#ifdef __cpp_if_constexpr
|
||||
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||
|
||||
template <typename CompiledFormat, typename... Args,
|
||||
typename Char = typename CompiledFormat::char_type,
|
File diff suppressed because it is too large
Load Diff
|
@ -40,6 +40,10 @@ FMT_FUNC void assert_fail(const char* file, int line, const char* message) {
|
|||
std::terminate();
|
||||
}
|
||||
|
||||
FMT_FUNC void throw_format_error(const char* message) {
|
||||
FMT_THROW(format_error(message));
|
||||
}
|
||||
|
||||
#ifndef _MSC_VER
|
||||
# define FMT_SNPRINTF snprintf
|
||||
#else // _MSC_VER
|
||||
|
@ -145,141 +149,13 @@ template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) {
|
|||
return i >= 0 ? i * char_digits + count_digits<4, unsigned>(n.value[i]) : 1;
|
||||
}
|
||||
|
||||
#if __cplusplus < 201703L
|
||||
template <typename T> constexpr const char basic_data<T>::digits[][2];
|
||||
template <typename T> constexpr const char basic_data<T>::hex_digits[];
|
||||
template <typename T> constexpr const char basic_data<T>::signs[];
|
||||
template <typename T> constexpr const unsigned basic_data<T>::prefixes[];
|
||||
template <typename T> constexpr const char basic_data<T>::left_padding_shifts[];
|
||||
template <typename T>
|
||||
constexpr const char basic_data<T>::right_padding_shifts[];
|
||||
#endif
|
||||
// log10(2) = 0x0.4d104d427de7fbcc...
|
||||
static constexpr uint64_t log10_2_significand = 0x4d104d427de7fbcc;
|
||||
|
||||
template <typename T> struct bits {
|
||||
static FMT_CONSTEXPR_DECL const int value =
|
||||
static_cast<int>(sizeof(T) * std::numeric_limits<unsigned char>::digits);
|
||||
};
|
||||
|
||||
class fp;
|
||||
template <int SHIFT = 0> fp normalize(fp value);
|
||||
|
||||
// Lower (upper) boundary is a value half way between a floating-point value
|
||||
// and its predecessor (successor). Boundaries have the same exponent as the
|
||||
// value so only significands are stored.
|
||||
struct boundaries {
|
||||
uint64_t lower;
|
||||
uint64_t upper;
|
||||
};
|
||||
|
||||
// A handmade floating-point number f * pow(2, e).
|
||||
class fp {
|
||||
private:
|
||||
using significand_type = uint64_t;
|
||||
|
||||
template <typename Float>
|
||||
using is_supported_float = bool_constant<sizeof(Float) == sizeof(uint64_t) ||
|
||||
sizeof(Float) == sizeof(uint32_t)>;
|
||||
|
||||
public:
|
||||
significand_type f;
|
||||
int e;
|
||||
|
||||
// All sizes are in bits.
|
||||
// Subtract 1 to account for an implicit most significant bit in the
|
||||
// normalized form.
|
||||
static FMT_CONSTEXPR_DECL const int double_significand_size =
|
||||
std::numeric_limits<double>::digits - 1;
|
||||
static FMT_CONSTEXPR_DECL const uint64_t implicit_bit =
|
||||
1ULL << double_significand_size;
|
||||
static FMT_CONSTEXPR_DECL const int significand_size =
|
||||
bits<significand_type>::value;
|
||||
|
||||
fp() : f(0), e(0) {}
|
||||
fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {}
|
||||
|
||||
// Constructs fp from an IEEE754 double. It is a template to prevent compile
|
||||
// errors on platforms where double is not IEEE754.
|
||||
template <typename Double> explicit fp(Double d) { assign(d); }
|
||||
|
||||
// Assigns d to this and return true iff predecessor is closer than successor.
|
||||
template <typename Float, FMT_ENABLE_IF(is_supported_float<Float>::value)>
|
||||
bool assign(Float d) {
|
||||
// Assume float is in the format [sign][exponent][significand].
|
||||
using limits = std::numeric_limits<Float>;
|
||||
const int float_significand_size = limits::digits - 1;
|
||||
const int exponent_size =
|
||||
bits<Float>::value - float_significand_size - 1; // -1 for sign
|
||||
const uint64_t float_implicit_bit = 1ULL << float_significand_size;
|
||||
const uint64_t significand_mask = float_implicit_bit - 1;
|
||||
const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask;
|
||||
const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1;
|
||||
constexpr bool is_double = sizeof(Float) == sizeof(uint64_t);
|
||||
auto u = bit_cast<conditional_t<is_double, uint64_t, uint32_t>>(d);
|
||||
f = u & significand_mask;
|
||||
int biased_e =
|
||||
static_cast<int>((u & exponent_mask) >> float_significand_size);
|
||||
// Predecessor is closer if d is a normalized power of 2 (f == 0) other than
|
||||
// the smallest normalized number (biased_e > 1).
|
||||
bool is_predecessor_closer = f == 0 && biased_e > 1;
|
||||
if (biased_e != 0)
|
||||
f += float_implicit_bit;
|
||||
else
|
||||
biased_e = 1; // Subnormals use biased exponent 1 (min exponent).
|
||||
e = biased_e - exponent_bias - float_significand_size;
|
||||
return is_predecessor_closer;
|
||||
}
|
||||
|
||||
template <typename Float, FMT_ENABLE_IF(!is_supported_float<Float>::value)>
|
||||
bool assign(Float) {
|
||||
*this = fp();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Normalizes the value converted from double and multiplied by (1 << SHIFT).
|
||||
template <int SHIFT> fp normalize(fp value) {
|
||||
// Handle subnormals.
|
||||
const auto shifted_implicit_bit = fp::implicit_bit << SHIFT;
|
||||
while ((value.f & shifted_implicit_bit) == 0) {
|
||||
value.f <<= 1;
|
||||
--value.e;
|
||||
}
|
||||
// Subtract 1 to account for hidden bit.
|
||||
const auto offset =
|
||||
fp::significand_size - fp::double_significand_size - SHIFT - 1;
|
||||
value.f <<= offset;
|
||||
value.e -= offset;
|
||||
return value;
|
||||
}
|
||||
|
||||
inline bool operator==(fp x, fp y) { return x.f == y.f && x.e == y.e; }
|
||||
|
||||
// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking.
|
||||
inline uint64_t multiply(uint64_t lhs, uint64_t rhs) {
|
||||
#if FMT_USE_INT128
|
||||
auto product = static_cast<__uint128_t>(lhs) * rhs;
|
||||
auto f = static_cast<uint64_t>(product >> 64);
|
||||
return (static_cast<uint64_t>(product) & (1ULL << 63)) != 0 ? f + 1 : f;
|
||||
#else
|
||||
// Multiply 32-bit parts of significands.
|
||||
uint64_t mask = (1ULL << 32) - 1;
|
||||
uint64_t a = lhs >> 32, b = lhs & mask;
|
||||
uint64_t c = rhs >> 32, d = rhs & mask;
|
||||
uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d;
|
||||
// Compute mid 64-bit of result and round.
|
||||
uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31);
|
||||
return ac + (ad >> 32) + (bc >> 32) + (mid >> 32);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline fp operator*(fp x, fp y) { return {multiply(x.f, y.f), x.e + y.e + 64}; }
|
||||
|
||||
// Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its
|
||||
// (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`.
|
||||
inline fp get_cached_power(int min_exponent, int& pow10_exponent) {
|
||||
template <typename T = void> struct basic_impl_data {
|
||||
// Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340.
|
||||
// These are generated by support/compute-powers.py.
|
||||
static constexpr const uint64_t pow10_significands[] = {
|
||||
static constexpr uint64_t pow10_significands[87] = {
|
||||
0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76,
|
||||
0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df,
|
||||
0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c,
|
||||
|
@ -311,9 +187,13 @@ inline fp get_cached_power(int min_exponent, int& pow10_exponent) {
|
|||
0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b,
|
||||
};
|
||||
|
||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
||||
# pragma GCC diagnostic push
|
||||
# pragma GCC diagnostic ignored "-Wnarrowing"
|
||||
#endif
|
||||
// Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding
|
||||
// to significands above.
|
||||
static constexpr const int16_t pow10_exponents[] = {
|
||||
static constexpr int16_t pow10_exponents[87] = {
|
||||
-1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954,
|
||||
-927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661,
|
||||
-635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369,
|
||||
|
@ -322,11 +202,137 @@ inline fp get_cached_power(int min_exponent, int& pow10_exponent) {
|
|||
242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508,
|
||||
534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800,
|
||||
827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066};
|
||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
||||
# pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
static constexpr uint64_t power_of_10_64[20] = {
|
||||
1, FMT_POWERS_OF_10(1ULL), FMT_POWERS_OF_10(1000000000ULL),
|
||||
10000000000000000000ULL};
|
||||
};
|
||||
|
||||
// This is a struct rather than an alias to avoid shadowing warnings in gcc.
|
||||
struct impl_data : basic_impl_data<> {};
|
||||
|
||||
#if __cplusplus < 201703L
|
||||
template <typename T>
|
||||
constexpr uint64_t basic_impl_data<T>::pow10_significands[];
|
||||
template <typename T> constexpr int16_t basic_impl_data<T>::pow10_exponents[];
|
||||
template <typename T> constexpr uint64_t basic_impl_data<T>::power_of_10_64[];
|
||||
#endif
|
||||
|
||||
template <typename T> struct bits {
|
||||
static FMT_CONSTEXPR_DECL const int value =
|
||||
static_cast<int>(sizeof(T) * std::numeric_limits<unsigned char>::digits);
|
||||
};
|
||||
|
||||
// Returns the number of significand bits in Float excluding the implicit bit.
|
||||
template <typename Float> constexpr int num_significand_bits() {
|
||||
// Subtract 1 to account for an implicit most significant bit in the
|
||||
// normalized form.
|
||||
return std::numeric_limits<Float>::digits - 1;
|
||||
}
|
||||
|
||||
// A floating-point number f * pow(2, e).
|
||||
struct fp {
|
||||
uint64_t f;
|
||||
int e;
|
||||
|
||||
static constexpr const int num_significand_bits = bits<decltype(f)>::value;
|
||||
|
||||
constexpr fp() : f(0), e(0) {}
|
||||
constexpr fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {}
|
||||
|
||||
// Constructs fp from an IEEE754 floating-point number. It is a template to
|
||||
// prevent compile errors on systems where n is not IEEE754.
|
||||
template <typename Float> explicit FMT_CONSTEXPR fp(Float n) { assign(n); }
|
||||
|
||||
template <typename Float>
|
||||
using is_supported = bool_constant<sizeof(Float) == sizeof(uint64_t) ||
|
||||
sizeof(Float) == sizeof(uint32_t)>;
|
||||
|
||||
// Assigns d to this and return true iff predecessor is closer than successor.
|
||||
template <typename Float, FMT_ENABLE_IF(is_supported<Float>::value)>
|
||||
FMT_CONSTEXPR bool assign(Float n) {
|
||||
// Assume float is in the format [sign][exponent][significand].
|
||||
const int num_float_significand_bits =
|
||||
detail::num_significand_bits<Float>();
|
||||
const uint64_t implicit_bit = 1ULL << num_float_significand_bits;
|
||||
const uint64_t significand_mask = implicit_bit - 1;
|
||||
constexpr bool is_double = sizeof(Float) == sizeof(uint64_t);
|
||||
auto u = bit_cast<conditional_t<is_double, uint64_t, uint32_t>>(n);
|
||||
f = u & significand_mask;
|
||||
const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask;
|
||||
int biased_e =
|
||||
static_cast<int>((u & exponent_mask) >> num_float_significand_bits);
|
||||
// The predecessor is closer if n is a normalized power of 2 (f == 0) other
|
||||
// than the smallest normalized number (biased_e > 1).
|
||||
bool is_predecessor_closer = f == 0 && biased_e > 1;
|
||||
if (biased_e != 0)
|
||||
f += implicit_bit;
|
||||
else
|
||||
biased_e = 1; // Subnormals use biased exponent 1 (min exponent).
|
||||
const int exponent_bias = std::numeric_limits<Float>::max_exponent - 1;
|
||||
e = biased_e - exponent_bias - num_float_significand_bits;
|
||||
return is_predecessor_closer;
|
||||
}
|
||||
|
||||
template <typename Float, FMT_ENABLE_IF(!is_supported<Float>::value)>
|
||||
bool assign(Float) {
|
||||
FMT_ASSERT(false, "");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Normalizes the value converted from double and multiplied by (1 << SHIFT).
|
||||
template <int SHIFT = 0> FMT_CONSTEXPR fp normalize(fp value) {
|
||||
// Handle subnormals.
|
||||
const uint64_t implicit_bit = 1ULL << num_significand_bits<double>();
|
||||
const auto shifted_implicit_bit = implicit_bit << SHIFT;
|
||||
while ((value.f & shifted_implicit_bit) == 0) {
|
||||
value.f <<= 1;
|
||||
--value.e;
|
||||
}
|
||||
// Subtract 1 to account for hidden bit.
|
||||
const auto offset =
|
||||
fp::num_significand_bits - num_significand_bits<double>() - SHIFT - 1;
|
||||
value.f <<= offset;
|
||||
value.e -= offset;
|
||||
return value;
|
||||
}
|
||||
|
||||
inline bool operator==(fp x, fp y) { return x.f == y.f && x.e == y.e; }
|
||||
|
||||
// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking.
|
||||
FMT_CONSTEXPR inline uint64_t multiply(uint64_t lhs, uint64_t rhs) {
|
||||
#if FMT_USE_INT128
|
||||
auto product = static_cast<__uint128_t>(lhs) * rhs;
|
||||
auto f = static_cast<uint64_t>(product >> 64);
|
||||
return (static_cast<uint64_t>(product) & (1ULL << 63)) != 0 ? f + 1 : f;
|
||||
#else
|
||||
// Multiply 32-bit parts of significands.
|
||||
uint64_t mask = (1ULL << 32) - 1;
|
||||
uint64_t a = lhs >> 32, b = lhs & mask;
|
||||
uint64_t c = rhs >> 32, d = rhs & mask;
|
||||
uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d;
|
||||
// Compute mid 64-bit of result and round.
|
||||
uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31);
|
||||
return ac + (ad >> 32) + (bc >> 32) + (mid >> 32);
|
||||
#endif
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR inline fp operator*(fp x, fp y) {
|
||||
return {multiply(x.f, y.f), x.e + y.e + 64};
|
||||
}
|
||||
|
||||
// Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its
|
||||
// (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`.
|
||||
FMT_CONSTEXPR inline fp get_cached_power(int min_exponent,
|
||||
int& pow10_exponent) {
|
||||
const int shift = 32;
|
||||
const auto significand = static_cast<int64_t>(data::log10_2_significand);
|
||||
const auto significand = static_cast<int64_t>(log10_2_significand);
|
||||
int index = static_cast<int>(
|
||||
((min_exponent + fp::significand_size - 1) * (significand >> shift) +
|
||||
((min_exponent + fp::num_significand_bits - 1) * (significand >> shift) +
|
||||
((int64_t(1) << shift) - 1)) // ceil
|
||||
>> 32 // arithmetic shift
|
||||
);
|
||||
|
@ -336,7 +342,8 @@ inline fp get_cached_power(int min_exponent, int& pow10_exponent) {
|
|||
const int dec_exp_step = 8;
|
||||
index = (index - first_dec_exp - 1) / dec_exp_step + 1;
|
||||
pow10_exponent = first_dec_exp + index * dec_exp_step;
|
||||
return {pow10_significands[index], pow10_exponents[index]};
|
||||
return {impl_data::pow10_significands[index],
|
||||
impl_data::pow10_exponents[index]};
|
||||
}
|
||||
|
||||
// A simple accumulator to hold the sums of terms in bigint::square if uint128_t
|
||||
|
@ -345,14 +352,16 @@ struct accumulator {
|
|||
uint64_t lower;
|
||||
uint64_t upper;
|
||||
|
||||
accumulator() : lower(0), upper(0) {}
|
||||
explicit operator uint32_t() const { return static_cast<uint32_t>(lower); }
|
||||
constexpr accumulator() : lower(0), upper(0) {}
|
||||
constexpr explicit operator uint32_t() const {
|
||||
return static_cast<uint32_t>(lower);
|
||||
}
|
||||
|
||||
void operator+=(uint64_t n) {
|
||||
FMT_CONSTEXPR void operator+=(uint64_t n) {
|
||||
lower += n;
|
||||
if (lower < n) ++upper;
|
||||
}
|
||||
void operator>>=(int shift) {
|
||||
FMT_CONSTEXPR void operator>>=(int shift) {
|
||||
FMT_ASSERT(shift == 32, "");
|
||||
(void)shift;
|
||||
lower = (upper << 32) | (lower >> 32);
|
||||
|
@ -370,27 +379,31 @@ class bigint {
|
|||
basic_memory_buffer<bigit, bigits_capacity> bigits_;
|
||||
int exp_;
|
||||
|
||||
bigit operator[](int index) const { return bigits_[to_unsigned(index)]; }
|
||||
bigit& operator[](int index) { return bigits_[to_unsigned(index)]; }
|
||||
FMT_CONSTEXPR20 bigit operator[](int index) const {
|
||||
return bigits_[to_unsigned(index)];
|
||||
}
|
||||
FMT_CONSTEXPR20 bigit& operator[](int index) {
|
||||
return bigits_[to_unsigned(index)];
|
||||
}
|
||||
|
||||
static FMT_CONSTEXPR_DECL const int bigit_bits = bits<bigit>::value;
|
||||
|
||||
friend struct formatter<bigint>;
|
||||
|
||||
void subtract_bigits(int index, bigit other, bigit& borrow) {
|
||||
FMT_CONSTEXPR20 void subtract_bigits(int index, bigit other, bigit& borrow) {
|
||||
auto result = static_cast<double_bigit>((*this)[index]) - other - borrow;
|
||||
(*this)[index] = static_cast<bigit>(result);
|
||||
borrow = static_cast<bigit>(result >> (bigit_bits * 2 - 1));
|
||||
}
|
||||
|
||||
void remove_leading_zeros() {
|
||||
FMT_CONSTEXPR20 void remove_leading_zeros() {
|
||||
int num_bigits = static_cast<int>(bigits_.size()) - 1;
|
||||
while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits;
|
||||
bigits_.resize(to_unsigned(num_bigits + 1));
|
||||
}
|
||||
|
||||
// Computes *this -= other assuming aligned bigints and *this >= other.
|
||||
void subtract_aligned(const bigint& other) {
|
||||
FMT_CONSTEXPR20 void subtract_aligned(const bigint& other) {
|
||||
FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints");
|
||||
FMT_ASSERT(compare(*this, other) >= 0, "");
|
||||
bigit borrow = 0;
|
||||
|
@ -401,7 +414,7 @@ class bigint {
|
|||
remove_leading_zeros();
|
||||
}
|
||||
|
||||
void multiply(uint32_t value) {
|
||||
FMT_CONSTEXPR20 void multiply(uint32_t value) {
|
||||
const double_bigit wide_value = value;
|
||||
bigit carry = 0;
|
||||
for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
|
||||
|
@ -412,7 +425,7 @@ class bigint {
|
|||
if (carry != 0) bigits_.push_back(carry);
|
||||
}
|
||||
|
||||
void multiply(uint64_t value) {
|
||||
FMT_CONSTEXPR20 void multiply(uint64_t value) {
|
||||
const bigit mask = ~bigit(0);
|
||||
const double_bigit lower = value & mask;
|
||||
const double_bigit upper = value >> bigit_bits;
|
||||
|
@ -430,14 +443,16 @@ class bigint {
|
|||
}
|
||||
|
||||
public:
|
||||
bigint() : exp_(0) {}
|
||||
FMT_CONSTEXPR20 bigint() : exp_(0) {}
|
||||
explicit bigint(uint64_t n) { assign(n); }
|
||||
~bigint() { FMT_ASSERT(bigits_.capacity() <= bigits_capacity, ""); }
|
||||
FMT_CONSTEXPR20 ~bigint() {
|
||||
FMT_ASSERT(bigits_.capacity() <= bigits_capacity, "");
|
||||
}
|
||||
|
||||
bigint(const bigint&) = delete;
|
||||
void operator=(const bigint&) = delete;
|
||||
|
||||
void assign(const bigint& other) {
|
||||
FMT_CONSTEXPR20 void assign(const bigint& other) {
|
||||
auto size = other.bigits_.size();
|
||||
bigits_.resize(size);
|
||||
auto data = other.bigits_.data();
|
||||
|
@ -445,7 +460,7 @@ class bigint {
|
|||
exp_ = other.exp_;
|
||||
}
|
||||
|
||||
void assign(uint64_t n) {
|
||||
FMT_CONSTEXPR20 void assign(uint64_t n) {
|
||||
size_t num_bigits = 0;
|
||||
do {
|
||||
bigits_[num_bigits++] = n & ~bigit(0);
|
||||
|
@ -455,9 +470,11 @@ class bigint {
|
|||
exp_ = 0;
|
||||
}
|
||||
|
||||
int num_bigits() const { return static_cast<int>(bigits_.size()) + exp_; }
|
||||
FMT_CONSTEXPR20 int num_bigits() const {
|
||||
return static_cast<int>(bigits_.size()) + exp_;
|
||||
}
|
||||
|
||||
FMT_NOINLINE bigint& operator<<=(int shift) {
|
||||
FMT_NOINLINE FMT_CONSTEXPR20 bigint& operator<<=(int shift) {
|
||||
FMT_ASSERT(shift >= 0, "");
|
||||
exp_ += shift / bigit_bits;
|
||||
shift %= bigit_bits;
|
||||
|
@ -472,13 +489,13 @@ class bigint {
|
|||
return *this;
|
||||
}
|
||||
|
||||
template <typename Int> bigint& operator*=(Int value) {
|
||||
template <typename Int> FMT_CONSTEXPR20 bigint& operator*=(Int value) {
|
||||
FMT_ASSERT(value > 0, "");
|
||||
multiply(uint32_or_64_or_128_t<Int>(value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend int compare(const bigint& lhs, const bigint& rhs) {
|
||||
friend FMT_CONSTEXPR20 int compare(const bigint& lhs, const bigint& rhs) {
|
||||
int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits();
|
||||
if (num_lhs_bigits != num_rhs_bigits)
|
||||
return num_lhs_bigits > num_rhs_bigits ? 1 : -1;
|
||||
|
@ -495,8 +512,8 @@ class bigint {
|
|||
}
|
||||
|
||||
// Returns compare(lhs1 + lhs2, rhs).
|
||||
friend int add_compare(const bigint& lhs1, const bigint& lhs2,
|
||||
const bigint& rhs) {
|
||||
friend FMT_CONSTEXPR20 int add_compare(const bigint& lhs1, const bigint& lhs2,
|
||||
const bigint& rhs) {
|
||||
int max_lhs_bigits = (std::max)(lhs1.num_bigits(), lhs2.num_bigits());
|
||||
int num_rhs_bigits = rhs.num_bigits();
|
||||
if (max_lhs_bigits + 1 < num_rhs_bigits) return -1;
|
||||
|
@ -519,7 +536,7 @@ class bigint {
|
|||
}
|
||||
|
||||
// Assigns pow(10, exp) to this bigint.
|
||||
void assign_pow10(int exp) {
|
||||
FMT_CONSTEXPR20 void assign_pow10(int exp) {
|
||||
FMT_ASSERT(exp >= 0, "");
|
||||
if (exp == 0) return assign(1);
|
||||
// Find the top bit.
|
||||
|
@ -538,7 +555,7 @@ class bigint {
|
|||
*this <<= exp; // Multiply by pow(2, exp) by shifting.
|
||||
}
|
||||
|
||||
void square() {
|
||||
FMT_CONSTEXPR20 void square() {
|
||||
int num_bigits = static_cast<int>(bigits_.size());
|
||||
int num_result_bigits = 2 * num_bigits;
|
||||
basic_memory_buffer<bigit, bigits_capacity> n(std::move(bigits_));
|
||||
|
@ -569,7 +586,7 @@ class bigint {
|
|||
|
||||
// If this bigint has a bigger exponent than other, adds trailing zero to make
|
||||
// exponents equal. This simplifies some operations such as subtraction.
|
||||
void align(const bigint& other) {
|
||||
FMT_CONSTEXPR20 void align(const bigint& other) {
|
||||
int exp_difference = exp_ - other.exp_;
|
||||
if (exp_difference <= 0) return;
|
||||
int num_bigits = static_cast<int>(bigits_.size());
|
||||
|
@ -582,7 +599,7 @@ class bigint {
|
|||
|
||||
// Divides this bignum by divisor, assigning the remainder to this and
|
||||
// returning the quotient.
|
||||
int divmod_assign(const bigint& divisor) {
|
||||
FMT_CONSTEXPR20 int divmod_assign(const bigint& divisor) {
|
||||
FMT_ASSERT(this != &divisor, "");
|
||||
if (compare(*this, divisor) < 0) return 0;
|
||||
FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, "");
|
||||
|
@ -602,8 +619,9 @@ enum class round_direction { unknown, up, down };
|
|||
// some number v and the error, returns whether v should be rounded up, down, or
|
||||
// whether the rounding direction can't be determined due to error.
|
||||
// error should be less than divisor / 2.
|
||||
inline round_direction get_round_direction(uint64_t divisor, uint64_t remainder,
|
||||
uint64_t error) {
|
||||
FMT_CONSTEXPR inline round_direction get_round_direction(uint64_t divisor,
|
||||
uint64_t remainder,
|
||||
uint64_t error) {
|
||||
FMT_ASSERT(remainder < divisor, ""); // divisor - remainder won't overflow.
|
||||
FMT_ASSERT(error < divisor, ""); // divisor - error won't overflow.
|
||||
FMT_ASSERT(error < divisor - error, ""); // error * 2 won't overflow.
|
||||
|
@ -626,19 +644,52 @@ enum result {
|
|||
};
|
||||
}
|
||||
|
||||
inline uint64_t power_of_10_64(int exp) {
|
||||
static constexpr const uint64_t data[] = {1, FMT_POWERS_OF_10(1),
|
||||
FMT_POWERS_OF_10(1000000000ULL),
|
||||
10000000000000000000ULL};
|
||||
return data[exp];
|
||||
}
|
||||
struct gen_digits_handler {
|
||||
char* buf;
|
||||
int size;
|
||||
int precision;
|
||||
int exp10;
|
||||
bool fixed;
|
||||
|
||||
FMT_CONSTEXPR digits::result on_digit(char digit, uint64_t divisor,
|
||||
uint64_t remainder, uint64_t error,
|
||||
bool integral) {
|
||||
FMT_ASSERT(remainder < divisor, "");
|
||||
buf[size++] = digit;
|
||||
if (!integral && error >= remainder) return digits::error;
|
||||
if (size < precision) return digits::more;
|
||||
if (!integral) {
|
||||
// Check if error * 2 < divisor with overflow prevention.
|
||||
// The check is not needed for the integral part because error = 1
|
||||
// and divisor > (1 << 32) there.
|
||||
if (error >= divisor || error >= divisor - error) return digits::error;
|
||||
} else {
|
||||
FMT_ASSERT(error == 1 && divisor > 2, "");
|
||||
}
|
||||
auto dir = get_round_direction(divisor, remainder, error);
|
||||
if (dir != round_direction::up)
|
||||
return dir == round_direction::down ? digits::done : digits::error;
|
||||
++buf[size - 1];
|
||||
for (int i = size - 1; i > 0 && buf[i] > '9'; --i) {
|
||||
buf[i] = '0';
|
||||
++buf[i - 1];
|
||||
}
|
||||
if (buf[0] > '9') {
|
||||
buf[0] = '1';
|
||||
if (fixed)
|
||||
buf[size++] = '0';
|
||||
else
|
||||
++exp10;
|
||||
}
|
||||
return digits::done;
|
||||
}
|
||||
};
|
||||
|
||||
// Generates output using the Grisu digit-gen algorithm.
|
||||
// error: the size of the region (lower, upper) outside of which numbers
|
||||
// definitely do not round to value (Delta in Grisu3).
|
||||
template <typename Handler>
|
||||
FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp,
|
||||
Handler& handler) {
|
||||
FMT_INLINE FMT_CONSTEXPR20 digits::result grisu_gen_digits(
|
||||
fp value, uint64_t error, int& exp, gen_digits_handler& handler) {
|
||||
const fp one(1ULL << -value.e, value.e);
|
||||
// The integral part of scaled value (p1 in Grisu) = value / one. It cannot be
|
||||
// zero because it contains a product of two 64-bit numbers with MSB set (due
|
||||
|
@ -649,10 +700,28 @@ FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp,
|
|||
// The fractional part of scaled value (p2 in Grisu) c = value % one.
|
||||
uint64_t fractional = value.f & (one.f - 1);
|
||||
exp = count_digits(integral); // kappa in Grisu.
|
||||
// Divide by 10 to prevent overflow.
|
||||
auto result = handler.on_start(power_of_10_64(exp - 1) << -one.e,
|
||||
value.f / 10, error * 10, exp);
|
||||
if (result != digits::more) return result;
|
||||
// Non-fixed formats require at least one digit and no precision adjustment.
|
||||
if (handler.fixed) {
|
||||
// Adjust fixed precision by exponent because it is relative to decimal
|
||||
// point.
|
||||
int precision_offset = exp + handler.exp10;
|
||||
if (precision_offset > 0 &&
|
||||
handler.precision > max_value<int>() - precision_offset) {
|
||||
FMT_THROW(format_error("number is too big"));
|
||||
}
|
||||
handler.precision += precision_offset;
|
||||
// Check if precision is satisfied just by leading zeros, e.g.
|
||||
// format("{:.2f}", 0.001) gives "0.00" without generating any digits.
|
||||
if (handler.precision <= 0) {
|
||||
if (handler.precision < 0) return digits::done;
|
||||
// Divide by 10 to prevent overflow.
|
||||
uint64_t divisor = impl_data::power_of_10_64[exp - 1] << -one.e;
|
||||
auto dir = get_round_direction(divisor, value.f / 10, error * 10);
|
||||
if (dir == round_direction::unknown) return digits::error;
|
||||
handler.buf[handler.size++] = dir == round_direction::up ? '1' : '0';
|
||||
return digits::done;
|
||||
}
|
||||
}
|
||||
// Generate digits for the integral part. This can produce up to 10 digits.
|
||||
do {
|
||||
uint32_t digit = 0;
|
||||
|
@ -699,9 +768,9 @@ FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp,
|
|||
}
|
||||
--exp;
|
||||
auto remainder = (static_cast<uint64_t>(integral) << -one.e) + fractional;
|
||||
result = handler.on_digit(static_cast<char>('0' + digit),
|
||||
power_of_10_64(exp) << -one.e, remainder, error,
|
||||
exp, true);
|
||||
auto result = handler.on_digit(static_cast<char>('0' + digit),
|
||||
impl_data::power_of_10_64[exp] << -one.e,
|
||||
remainder, error, true);
|
||||
if (result != digits::more) return result;
|
||||
} while (exp > 0);
|
||||
// Generate digits for the fractional part.
|
||||
|
@ -711,69 +780,11 @@ FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp,
|
|||
char digit = static_cast<char>('0' + (fractional >> -one.e));
|
||||
fractional &= one.f - 1;
|
||||
--exp;
|
||||
result = handler.on_digit(digit, one.f, fractional, error, exp, false);
|
||||
auto result = handler.on_digit(digit, one.f, fractional, error, false);
|
||||
if (result != digits::more) return result;
|
||||
}
|
||||
}
|
||||
|
||||
// The fixed precision digit handler.
|
||||
struct fixed_handler {
|
||||
char* buf;
|
||||
int size;
|
||||
int precision;
|
||||
int exp10;
|
||||
bool fixed;
|
||||
|
||||
digits::result on_start(uint64_t divisor, uint64_t remainder, uint64_t error,
|
||||
int& exp) {
|
||||
// Non-fixed formats require at least one digit and no precision adjustment.
|
||||
if (!fixed) return digits::more;
|
||||
// Adjust fixed precision by exponent because it is relative to decimal
|
||||
// point.
|
||||
precision += exp + exp10;
|
||||
// Check if precision is satisfied just by leading zeros, e.g.
|
||||
// format("{:.2f}", 0.001) gives "0.00" without generating any digits.
|
||||
if (precision > 0) return digits::more;
|
||||
if (precision < 0) return digits::done;
|
||||
auto dir = get_round_direction(divisor, remainder, error);
|
||||
if (dir == round_direction::unknown) return digits::error;
|
||||
buf[size++] = dir == round_direction::up ? '1' : '0';
|
||||
return digits::done;
|
||||
}
|
||||
|
||||
digits::result on_digit(char digit, uint64_t divisor, uint64_t remainder,
|
||||
uint64_t error, int, bool integral) {
|
||||
FMT_ASSERT(remainder < divisor, "");
|
||||
buf[size++] = digit;
|
||||
if (!integral && error >= remainder) return digits::error;
|
||||
if (size < precision) return digits::more;
|
||||
if (!integral) {
|
||||
// Check if error * 2 < divisor with overflow prevention.
|
||||
// The check is not needed for the integral part because error = 1
|
||||
// and divisor > (1 << 32) there.
|
||||
if (error >= divisor || error >= divisor - error) return digits::error;
|
||||
} else {
|
||||
FMT_ASSERT(error == 1 && divisor > 2, "");
|
||||
}
|
||||
auto dir = get_round_direction(divisor, remainder, error);
|
||||
if (dir != round_direction::up)
|
||||
return dir == round_direction::down ? digits::done : digits::error;
|
||||
++buf[size - 1];
|
||||
for (int i = size - 1; i > 0 && buf[i] > '9'; --i) {
|
||||
buf[i] = '0';
|
||||
++buf[i - 1];
|
||||
}
|
||||
if (buf[0] > '9') {
|
||||
buf[0] = '1';
|
||||
if (fixed)
|
||||
buf[size++] = '0';
|
||||
else
|
||||
++exp10;
|
||||
}
|
||||
return digits::done;
|
||||
}
|
||||
};
|
||||
|
||||
// A 128-bit integer type used internally,
|
||||
struct uint128_wrapper {
|
||||
uint128_wrapper() = default;
|
||||
|
@ -897,8 +908,7 @@ inline uint64_t umul96_lower64(uint32_t x, uint64_t y) FMT_NOEXCEPT {
|
|||
inline int floor_log10_pow2(int e) FMT_NOEXCEPT {
|
||||
FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent");
|
||||
const int shift = 22;
|
||||
return (e * static_cast<int>(data::log10_2_significand >> (64 - shift))) >>
|
||||
shift;
|
||||
return (e * static_cast<int>(log10_2_significand >> (64 - shift))) >> shift;
|
||||
}
|
||||
|
||||
// Various fast log computations.
|
||||
|
@ -916,8 +926,7 @@ inline int floor_log10_pow2_minus_log10_4_over_3(int e) FMT_NOEXCEPT {
|
|||
FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent");
|
||||
const uint64_t log10_4_over_3_fractional_digits = 0x1ffbfc2bbc780375;
|
||||
const int shift_amount = 22;
|
||||
return (e * static_cast<int>(data::log10_2_significand >>
|
||||
(64 - shift_amount)) -
|
||||
return (e * static_cast<int>(log10_2_significand >> (64 - shift_amount)) -
|
||||
static_cast<int>(log10_4_over_3_fractional_digits >>
|
||||
(64 - shift_amount))) >>
|
||||
shift_amount;
|
||||
|
@ -1042,7 +1051,7 @@ template <> struct cache_accessor<float> {
|
|||
static uint64_t get_cached_power(int k) FMT_NOEXCEPT {
|
||||
FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k,
|
||||
"k is out of range");
|
||||
constexpr const uint64_t pow10_significands[] = {
|
||||
static constexpr const uint64_t pow10_significands[] = {
|
||||
0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f,
|
||||
0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb,
|
||||
0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28,
|
||||
|
@ -2210,24 +2219,21 @@ small_divisor_case_label:
|
|||
}
|
||||
} // namespace dragonbox
|
||||
|
||||
// Formats value using a variation of the Fixed-Precision Positive
|
||||
// Floating-Point Printout ((FPP)^2) algorithm by Steele & White:
|
||||
// Formats a floating-point number using a variation of the Fixed-Precision
|
||||
// Positive Floating-Point Printout ((FPP)^2) algorithm by Steele & White:
|
||||
// https://fmt.dev/papers/p372-steele.pdf.
|
||||
template <typename Double>
|
||||
void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf,
|
||||
int& exp10) {
|
||||
FMT_CONSTEXPR20 inline void format_dragon(fp value, bool is_predecessor_closer,
|
||||
int num_digits, buffer<char>& buf,
|
||||
int& exp10) {
|
||||
bigint numerator; // 2 * R in (FPP)^2.
|
||||
bigint denominator; // 2 * S in (FPP)^2.
|
||||
// lower and upper are differences between value and corresponding boundaries.
|
||||
bigint lower; // (M^- in (FPP)^2).
|
||||
bigint upper_store; // upper's value if different from lower.
|
||||
bigint* upper = nullptr; // (M^+ in (FPP)^2).
|
||||
fp value;
|
||||
// Shift numerator and denominator by an extra bit or two (if lower boundary
|
||||
// is closer) to make lower and upper integers. This eliminates multiplication
|
||||
// by 2 during later computations.
|
||||
const bool is_predecessor_closer =
|
||||
binary32 ? value.assign(static_cast<float>(d)) : value.assign(d);
|
||||
int shift = is_predecessor_closer ? 2 : 1;
|
||||
uint64_t significand = value.f << shift;
|
||||
if (value.e >= 0) {
|
||||
|
@ -2297,9 +2303,9 @@ void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf,
|
|||
// Generate the given number of digits.
|
||||
exp10 -= num_digits - 1;
|
||||
if (num_digits == 0) {
|
||||
buf.try_resize(1);
|
||||
denominator *= 10;
|
||||
buf[0] = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0';
|
||||
auto digit = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0';
|
||||
buf.push_back(digit);
|
||||
return;
|
||||
}
|
||||
buf.try_resize(to_unsigned(num_digits));
|
||||
|
@ -2330,9 +2336,12 @@ void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf,
|
|||
buf[num_digits - 1] = static_cast<char>('0' + digit);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
|
||||
static_assert(!std::is_same<T, float>::value, "");
|
||||
template <typename Float>
|
||||
FMT_HEADER_ONLY_CONSTEXPR20 int format_float(Float value, int precision,
|
||||
float_specs specs,
|
||||
buffer<char>& buf) {
|
||||
// float is passed as double to reduce the number of instantiations.
|
||||
static_assert(!std::is_same<Float, float>::value, "");
|
||||
FMT_ASSERT(value >= 0, "value is negative");
|
||||
|
||||
const bool fixed = specs.format == float_format::fixed;
|
||||
|
@ -2342,13 +2351,13 @@ int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
|
|||
return 0;
|
||||
}
|
||||
buf.try_resize(to_unsigned(precision));
|
||||
std::uninitialized_fill_n(buf.data(), precision, '0');
|
||||
fill_n(buf.data(), precision, '0');
|
||||
return -precision;
|
||||
}
|
||||
|
||||
if (!specs.use_grisu) return snprintf_float(value, precision, specs, buf);
|
||||
if (specs.fallback) return snprintf_float(value, precision, specs, buf);
|
||||
|
||||
if (precision < 0) {
|
||||
if (!is_constant_evaluated() && precision < 0) {
|
||||
// Use Dragonbox for the shortest format.
|
||||
if (specs.binary32) {
|
||||
auto dec = dragonbox::to_decimal(static_cast<float>(value));
|
||||
|
@ -2360,26 +2369,37 @@ int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
|
|||
return dec.exponent;
|
||||
}
|
||||
|
||||
// Use Grisu + Dragon4 for the given precision:
|
||||
// https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf.
|
||||
int exp = 0;
|
||||
const int min_exp = -60; // alpha in Grisu.
|
||||
int cached_exp10 = 0; // K in Grisu.
|
||||
fp normalized = normalize(fp(value));
|
||||
const auto cached_pow = get_cached_power(
|
||||
min_exp - (normalized.e + fp::significand_size), cached_exp10);
|
||||
normalized = normalized * cached_pow;
|
||||
// Limit precision to the maximum possible number of significant digits in an
|
||||
// IEEE754 double because we don't need to generate zeros.
|
||||
const int max_double_digits = 767;
|
||||
if (precision > max_double_digits) precision = max_double_digits;
|
||||
fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
|
||||
if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error) {
|
||||
exp += handler.size - cached_exp10 - 1;
|
||||
fallback_format(value, handler.precision, specs.binary32, buf, exp);
|
||||
} else {
|
||||
exp += handler.exp10;
|
||||
buf.try_resize(to_unsigned(handler.size));
|
||||
bool use_dragon = true;
|
||||
if (is_fast_float<Float>()) {
|
||||
// Use Grisu + Dragon4 for the given precision:
|
||||
// https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf.
|
||||
const int min_exp = -60; // alpha in Grisu.
|
||||
int cached_exp10 = 0; // K in Grisu.
|
||||
fp normalized = normalize(fp(value));
|
||||
const auto cached_pow = get_cached_power(
|
||||
min_exp - (normalized.e + fp::num_significand_bits), cached_exp10);
|
||||
normalized = normalized * cached_pow;
|
||||
gen_digits_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
|
||||
if (grisu_gen_digits(normalized, 1, exp, handler) != digits::error &&
|
||||
!is_constant_evaluated()) {
|
||||
exp += handler.exp10;
|
||||
buf.try_resize(to_unsigned(handler.size));
|
||||
use_dragon = false;
|
||||
} else {
|
||||
exp += handler.size - cached_exp10 - 1;
|
||||
precision = handler.precision;
|
||||
}
|
||||
}
|
||||
if (use_dragon) {
|
||||
auto f = fp();
|
||||
bool is_predecessor_closer =
|
||||
specs.binary32 ? f.assign(static_cast<float>(value)) : f.assign(value);
|
||||
// Limit precision to the maximum possible number of significant digits in
|
||||
// an IEEE754 double because we don't need to generate zeros.
|
||||
const int max_double_digits = 767;
|
||||
if (precision > max_double_digits) precision = max_double_digits;
|
||||
format_dragon(f, is_predecessor_closer, precision, buf, exp);
|
||||
}
|
||||
if (!fixed && !specs.showpoint) {
|
||||
// Remove trailing zeros.
|
||||
|
@ -2391,7 +2411,7 @@ int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
|
|||
buf.try_resize(num_digits);
|
||||
}
|
||||
return exp;
|
||||
} // namespace detail
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int snprintf_float(T value, int precision, float_specs specs,
|
||||
|
@ -2525,8 +2545,8 @@ template <> struct formatter<detail::bigint> {
|
|||
};
|
||||
|
||||
FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) {
|
||||
for_each_codepoint(s, [this](uint32_t cp, int error) {
|
||||
if (error != 0) FMT_THROW(std::runtime_error("invalid utf8"));
|
||||
for_each_codepoint(s, [this](uint32_t cp, string_view) {
|
||||
if (cp == invalid_code_point) FMT_THROW(std::runtime_error("invalid utf8"));
|
||||
if (cp <= 0xFFFF) {
|
||||
buffer_.push_back(static_cast<wchar_t>(cp));
|
||||
} else {
|
||||
|
@ -2534,6 +2554,7 @@ FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) {
|
|||
buffer_.push_back(static_cast<wchar_t>(0xD800 + (cp >> 10)));
|
||||
buffer_.push_back(static_cast<wchar_t>(0xDC00 + (cp & 0x3FF)));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
buffer_.push_back(0);
|
||||
}
|
||||
|
@ -2549,15 +2570,17 @@ FMT_FUNC void format_system_error(detail::buffer<char>& out, int error_code,
|
|||
format_error_code(out, error_code, message);
|
||||
}
|
||||
|
||||
FMT_FUNC void detail::error_handler::on_error(const char* message) {
|
||||
FMT_THROW(format_error(message));
|
||||
}
|
||||
|
||||
FMT_FUNC void report_system_error(int error_code,
|
||||
const char* message) FMT_NOEXCEPT {
|
||||
report_error(format_system_error, error_code, message);
|
||||
}
|
||||
|
||||
// DEPRECATED!
|
||||
// This function is defined here and not inline for ABI compatiblity.
|
||||
FMT_FUNC void detail::error_handler::on_error(const char* message) {
|
||||
throw_format_error(message);
|
||||
}
|
||||
|
||||
FMT_FUNC std::string vformat(string_view fmt, format_args args) {
|
||||
// Don't optimize the "{}" case to keep the binary size small and because it
|
||||
// can be better optimized in fmt::format anyway.
|
File diff suppressed because it is too large
Load Diff
|
@ -21,17 +21,20 @@
|
|||
|
||||
#include "format.h"
|
||||
|
||||
#ifndef FMT_USE_FCNTL
|
||||
// UWP doesn't provide _pipe.
|
||||
#if FMT_HAS_INCLUDE("winapifamily.h")
|
||||
# include <winapifamily.h>
|
||||
#endif
|
||||
#if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
|
||||
defined(__linux__)) && \
|
||||
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
||||
# include <fcntl.h> // for O_RDONLY
|
||||
# define FMT_USE_FCNTL 1
|
||||
#else
|
||||
# define FMT_USE_FCNTL 0
|
||||
# if FMT_HAS_INCLUDE("winapifamily.h")
|
||||
# include <winapifamily.h>
|
||||
# endif
|
||||
# if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
|
||||
defined(__linux__)) && \
|
||||
(!defined(WINAPI_FAMILY) || \
|
||||
(WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
||||
# include <fcntl.h> // for O_RDONLY
|
||||
# define FMT_USE_FCNTL 1
|
||||
# else
|
||||
# define FMT_USE_FCNTL 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef FMT_POSIX
|
||||
|
@ -390,23 +393,26 @@ struct ostream_params {
|
|||
: ostream_params(params...) {
|
||||
this->buffer_size = bs.value;
|
||||
}
|
||||
|
||||
// Intel has a bug that results in failure to deduce a constructor
|
||||
// for empty parameter packs.
|
||||
# if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 2000
|
||||
ostream_params(int new_oflag) : oflag(new_oflag) {}
|
||||
ostream_params(detail::buffer_size bs) : buffer_size(bs.value) {}
|
||||
# endif
|
||||
};
|
||||
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
|
||||
constexpr detail::buffer_size buffer_size;
|
||||
// Added {} below to work around default constructor error known to
|
||||
// occur in Xcode versions 7.2.1 and 8.2.1.
|
||||
constexpr detail::buffer_size buffer_size{};
|
||||
|
||||
/** A fast output stream which is not thread-safe. */
|
||||
class FMT_API ostream final : private detail::buffer<char> {
|
||||
private:
|
||||
file file_;
|
||||
|
||||
void flush() {
|
||||
if (size() == 0) return;
|
||||
file_.write(data(), size());
|
||||
clear();
|
||||
}
|
||||
|
||||
void grow(size_t) override;
|
||||
|
||||
ostream(cstring_view path, const detail::ostream_params& params)
|
||||
|
@ -426,6 +432,12 @@ class FMT_API ostream final : private detail::buffer<char> {
|
|||
delete[] data();
|
||||
}
|
||||
|
||||
void flush() {
|
||||
if (size() == 0) return;
|
||||
file_.write(data(), size());
|
||||
clear();
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
friend ostream output_file(cstring_view path, T... params);
|
||||
|
||||
|
@ -500,7 +512,7 @@ class locale {
|
|||
|
||||
// Converts string to floating-point number and advances str past the end
|
||||
// of the parsed input.
|
||||
double strtod(const char*& str) const {
|
||||
FMT_DEPRECATED double strtod(const char*& str) const {
|
||||
char* end = nullptr;
|
||||
double result = strtod_l(str, &end, locale_);
|
||||
str = end;
|
|
@ -0,0 +1,135 @@
|
|||
// Formatting library for C++ - std::ostream support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_OSTREAM_H_
|
||||
#define FMT_OSTREAM_H_
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
template <typename OutputIt, typename Char> class basic_printf_context;
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Checks if T has a user-defined operator<<.
|
||||
template <typename T, typename Char, typename Enable = void>
|
||||
class is_streamable {
|
||||
private:
|
||||
template <typename U>
|
||||
static auto test(int)
|
||||
-> bool_constant<sizeof(std::declval<std::basic_ostream<Char>&>()
|
||||
<< std::declval<U>()) != 0>;
|
||||
|
||||
template <typename> static auto test(...) -> std::false_type;
|
||||
|
||||
using result = decltype(test<T>(0));
|
||||
|
||||
public:
|
||||
is_streamable() = default;
|
||||
|
||||
static const bool value = result::value;
|
||||
};
|
||||
|
||||
// Formatting of built-in types and arrays is intentionally disabled because
|
||||
// it's handled by standard (non-ostream) formatters.
|
||||
template <typename T, typename Char>
|
||||
struct is_streamable<
|
||||
T, Char,
|
||||
enable_if_t<
|
||||
std::is_arithmetic<T>::value || std::is_array<T>::value ||
|
||||
std::is_pointer<T>::value || std::is_same<T, char8_type>::value ||
|
||||
std::is_same<T, std::basic_string<Char>>::value ||
|
||||
std::is_same<T, std_string_view<Char>>::value ||
|
||||
(std::is_convertible<T, int>::value && !std::is_enum<T>::value)>>
|
||||
: std::false_type {};
|
||||
|
||||
// Write the content of buf to os.
|
||||
// It is a separate function rather than a part of vprint to simplify testing.
|
||||
template <typename Char>
|
||||
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||
const Char* buf_data = buf.data();
|
||||
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
|
||||
unsigned_streamsize size = buf.size();
|
||||
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
|
||||
do {
|
||||
unsigned_streamsize n = size <= max_size ? size : max_size;
|
||||
os.write(buf_data, static_cast<std::streamsize>(n));
|
||||
buf_data += n;
|
||||
size -= n;
|
||||
} while (size != 0);
|
||||
}
|
||||
|
||||
template <typename Char, typename T>
|
||||
void format_value(buffer<Char>& buf, const T& value,
|
||||
locale_ref loc = locale_ref()) {
|
||||
auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
|
||||
auto&& output = std::basic_ostream<Char>(&format_buf);
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
if (loc) output.imbue(loc.get<std::locale>());
|
||||
#endif
|
||||
output << value;
|
||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
buf.try_resize(buf.size());
|
||||
}
|
||||
|
||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||
template <typename T, typename Char>
|
||||
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
|
||||
: private formatter<basic_string_view<Char>, Char> {
|
||||
using formatter<basic_string_view<Char>, Char>::parse;
|
||||
|
||||
template <typename OutputIt>
|
||||
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx)
|
||||
-> OutputIt {
|
||||
auto buffer = basic_memory_buffer<Char>();
|
||||
format_value(buffer, value, ctx.locale());
|
||||
return formatter<basic_string_view<Char>, Char>::format(
|
||||
{buffer.data(), buffer.size()}, ctx);
|
||||
}
|
||||
|
||||
// DEPRECATED!
|
||||
template <typename OutputIt>
|
||||
auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx)
|
||||
-> OutputIt {
|
||||
auto buffer = basic_memory_buffer<Char>();
|
||||
format_value(buffer, value, ctx.locale());
|
||||
return std::copy(buffer.begin(), buffer.end(), ctx.out());
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
FMT_MODULE_EXPORT
|
||||
template <typename Char>
|
||||
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
auto buffer = basic_memory_buffer<Char>();
|
||||
detail::vformat_to(buffer, format_str, args);
|
||||
detail::write_buffer(os, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the stream *os*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print(cerr, "Don't {}!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
FMT_MODULE_EXPORT
|
||||
template <typename S, typename... Args,
|
||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
|
||||
vprint(os, to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OSTREAM_H_
|
|
@ -233,7 +233,7 @@ class printf_arg_formatter : public arg_formatter<Char> {
|
|||
|
||||
OutputIt write_null_pointer(bool is_string = false) {
|
||||
auto s = this->specs;
|
||||
s.type = 0;
|
||||
s.type = presentation_type::none;
|
||||
return write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
|
||||
}
|
||||
|
||||
|
@ -249,8 +249,10 @@ class printf_arg_formatter : public arg_formatter<Char> {
|
|||
// std::is_same instead.
|
||||
if (std::is_same<T, Char>::value) {
|
||||
format_specs fmt_specs = this->specs;
|
||||
if (fmt_specs.type && fmt_specs.type != 'c')
|
||||
if (fmt_specs.type != presentation_type::none &&
|
||||
fmt_specs.type != presentation_type::chr) {
|
||||
return (*this)(static_cast<int>(value));
|
||||
}
|
||||
fmt_specs.sign = sign::none;
|
||||
fmt_specs.alt = false;
|
||||
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
|
||||
|
@ -271,13 +273,13 @@ class printf_arg_formatter : public arg_formatter<Char> {
|
|||
/** Formats a null-terminated C string. */
|
||||
OutputIt operator()(const char* value) {
|
||||
if (value) return base::operator()(value);
|
||||
return write_null_pointer(this->specs.type != 'p');
|
||||
return write_null_pointer(this->specs.type != presentation_type::pointer);
|
||||
}
|
||||
|
||||
/** Formats a null-terminated wide C string. */
|
||||
OutputIt operator()(const wchar_t* value) {
|
||||
if (value) return base::operator()(value);
|
||||
return write_null_pointer(this->specs.type != 'p');
|
||||
return write_null_pointer(this->specs.type != presentation_type::pointer);
|
||||
}
|
||||
|
||||
OutputIt operator()(basic_string_view<Char> value) {
|
||||
|
@ -490,13 +492,13 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
|||
|
||||
// Parse type.
|
||||
if (it == end) FMT_THROW(format_error("invalid format string"));
|
||||
specs.type = static_cast<char>(*it++);
|
||||
char type = static_cast<char>(*it++);
|
||||
if (arg.is_integral()) {
|
||||
// Normalize type.
|
||||
switch (specs.type) {
|
||||
switch (type) {
|
||||
case 'i':
|
||||
case 'u':
|
||||
specs.type = 'd';
|
||||
type = 'd';
|
||||
break;
|
||||
case 'c':
|
||||
visit_format_arg(
|
||||
|
@ -505,6 +507,9 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
|||
break;
|
||||
}
|
||||
}
|
||||
specs.type = parse_presentation_type(type);
|
||||
if (specs.type == presentation_type::none)
|
||||
parse_ctx.on_error("invalid type specifier");
|
||||
|
||||
start = it;
|
||||
|
|
@ -0,0 +1,793 @@
|
|||
// Formatting library for C++ - experimental range support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
//
|
||||
// Copyright (c) 2018 - present, Remotion (Igor Schulz)
|
||||
// All Rights Reserved
|
||||
// {fmt} support for ranges, containers and types tuple interface.
|
||||
|
||||
#ifndef FMT_RANGES_H_
|
||||
#define FMT_RANGES_H_
|
||||
|
||||
#include <initializer_list>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename RangeT, typename OutputIterator>
|
||||
OutputIterator copy(const RangeT& range, OutputIterator out) {
|
||||
for (auto it = range.begin(), end = range.end(); it != end; ++it)
|
||||
*out++ = *it;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
OutputIterator copy(const char* str, OutputIterator out) {
|
||||
while (*str) *out++ = *str++;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
OutputIterator copy(char ch, OutputIterator out) {
|
||||
*out++ = ch;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
OutputIterator copy(wchar_t ch, OutputIterator out) {
|
||||
*out++ = ch;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Returns true if T has a std::string-like interface, like std::string_view.
|
||||
template <typename T> class is_std_string_like {
|
||||
template <typename U>
|
||||
static auto check(U* p)
|
||||
-> decltype((void)p->find('a'), p->length(), (void)p->data(), int());
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
is_string<T>::value ||
|
||||
std::is_convertible<T, std_string_view<char>>::value ||
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {};
|
||||
|
||||
template <typename T> class is_map {
|
||||
template <typename U> static auto check(U*) -> typename U::mapped_type;
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
#ifdef FMT_FORMAT_MAP_AS_LIST
|
||||
static FMT_CONSTEXPR_DECL const bool value = false;
|
||||
#else
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
#endif
|
||||
};
|
||||
|
||||
template <typename T> class is_set {
|
||||
template <typename U> static auto check(U*) -> typename U::key_type;
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
#ifdef FMT_FORMAT_SET_AS_LIST
|
||||
static FMT_CONSTEXPR_DECL const bool value = false;
|
||||
#else
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
|
||||
#endif
|
||||
};
|
||||
|
||||
template <typename... Ts> struct conditional_helper {};
|
||||
|
||||
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
|
||||
|
||||
#if !FMT_MSC_VER || FMT_MSC_VER > 1800
|
||||
|
||||
# define FMT_DECLTYPE_RETURN(val) \
|
||||
->decltype(val) { return val; } \
|
||||
static_assert( \
|
||||
true, "") // This makes it so that a semicolon is required after the
|
||||
// macro, which helps clang-format handle the formatting.
|
||||
|
||||
// C array overload
|
||||
template <typename T, std::size_t N>
|
||||
auto range_begin(const T (&arr)[N]) -> const T* {
|
||||
return arr;
|
||||
}
|
||||
template <typename T, std::size_t N>
|
||||
auto range_end(const T (&arr)[N]) -> const T* {
|
||||
return arr + N;
|
||||
}
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_member_fn_begin_end_t : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_member_fn_begin_end_t<T, void_t<decltype(std::declval<T>().begin()),
|
||||
decltype(std::declval<T>().end())>>
|
||||
: std::true_type {};
|
||||
|
||||
// Member function overload
|
||||
template <typename T>
|
||||
auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
|
||||
template <typename T>
|
||||
auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end());
|
||||
|
||||
// ADL overload. Only participates in overload resolution if member functions
|
||||
// are not found.
|
||||
template <typename T>
|
||||
auto range_begin(T&& rng)
|
||||
-> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
|
||||
decltype(begin(static_cast<T&&>(rng)))> {
|
||||
return begin(static_cast<T&&>(rng));
|
||||
}
|
||||
template <typename T>
|
||||
auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
|
||||
decltype(end(static_cast<T&&>(rng)))> {
|
||||
return end(static_cast<T&&>(rng));
|
||||
}
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_const_begin_end : std::false_type {};
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_mutable_begin_end : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_const_begin_end<
|
||||
T,
|
||||
void_t<
|
||||
decltype(detail::range_begin(std::declval<const remove_cvref_t<T>&>())),
|
||||
decltype(detail::range_end(std::declval<const remove_cvref_t<T>&>()))>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_mutable_begin_end<
|
||||
T, void_t<decltype(detail::range_begin(std::declval<T>())),
|
||||
decltype(detail::range_end(std::declval<T>())),
|
||||
enable_if_t<std::is_copy_constructible<T>::value>>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
struct is_range_<T, void>
|
||||
: std::integral_constant<bool, (has_const_begin_end<T>::value ||
|
||||
has_mutable_begin_end<T>::value)> {};
|
||||
# undef FMT_DECLTYPE_RETURN
|
||||
#endif
|
||||
|
||||
// tuple_size and tuple_element check.
|
||||
template <typename T> class is_tuple_like_ {
|
||||
template <typename U>
|
||||
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
};
|
||||
|
||||
// Check for integer_sequence
|
||||
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900
|
||||
template <typename T, T... N>
|
||||
using integer_sequence = std::integer_sequence<T, N...>;
|
||||
template <size_t... N> using index_sequence = std::index_sequence<N...>;
|
||||
template <size_t N> using make_index_sequence = std::make_index_sequence<N>;
|
||||
#else
|
||||
template <typename T, T... N> struct integer_sequence {
|
||||
using value_type = T;
|
||||
|
||||
static FMT_CONSTEXPR size_t size() { return sizeof...(N); }
|
||||
};
|
||||
|
||||
template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
|
||||
|
||||
template <typename T, size_t N, T... Ns>
|
||||
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
|
||||
template <typename T, T... Ns>
|
||||
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
|
||||
|
||||
template <size_t N>
|
||||
using make_index_sequence = make_integer_sequence<size_t, N>;
|
||||
#endif
|
||||
|
||||
template <class Tuple, class F, size_t... Is>
|
||||
void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) FMT_NOEXCEPT {
|
||||
using std::get;
|
||||
// using free function get<I>(T) now.
|
||||
const int _[] = {0, ((void)f(get<Is>(tup)), 0)...};
|
||||
(void)_; // blocks warnings
|
||||
}
|
||||
|
||||
template <class T>
|
||||
FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes(
|
||||
T const&) {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
|
||||
const auto indexes = get_indexes(tup);
|
||||
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <typename Range>
|
||||
using value_type =
|
||||
remove_cvref_t<decltype(*detail::range_begin(std::declval<Range>()))>;
|
||||
|
||||
template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
|
||||
*out++ = ',';
|
||||
*out++ = ' ';
|
||||
return out;
|
||||
}
|
||||
|
||||
struct singleton {
|
||||
unsigned char upper;
|
||||
unsigned char lower_count;
|
||||
};
|
||||
|
||||
inline auto is_printable(uint16_t x, const singleton* singletons,
|
||||
size_t singletons_size,
|
||||
const unsigned char* singleton_lowers,
|
||||
const unsigned char* normal, size_t normal_size)
|
||||
-> bool {
|
||||
auto upper = x >> 8;
|
||||
auto lower_start = 0;
|
||||
for (size_t i = 0; i < singletons_size; ++i) {
|
||||
auto s = singletons[i];
|
||||
auto lower_end = lower_start + s.lower_count;
|
||||
if (upper < s.upper) break;
|
||||
if (upper == s.upper) {
|
||||
for (auto j = lower_start; j < lower_end; ++j) {
|
||||
if (singleton_lowers[j] == (x & 0xff)) return false;
|
||||
}
|
||||
}
|
||||
lower_start = lower_end;
|
||||
}
|
||||
|
||||
auto xsigned = static_cast<int>(x);
|
||||
auto current = true;
|
||||
for (size_t i = 0; i < normal_size; ++i) {
|
||||
auto v = static_cast<int>(normal[i]);
|
||||
auto len = (v & 0x80) != 0 ? (v & 0x7f) << 8 | normal[++i] : v;
|
||||
xsigned -= len;
|
||||
if (xsigned < 0) break;
|
||||
current = !current;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
// Returns true iff the code point cp is printable.
|
||||
// This code is generated by support/printable.py.
|
||||
inline auto is_printable(uint32_t cp) -> bool {
|
||||
static constexpr singleton singletons0[] = {
|
||||
{0x00, 1}, {0x03, 5}, {0x05, 6}, {0x06, 3}, {0x07, 6}, {0x08, 8},
|
||||
{0x09, 17}, {0x0a, 28}, {0x0b, 25}, {0x0c, 20}, {0x0d, 16}, {0x0e, 13},
|
||||
{0x0f, 4}, {0x10, 3}, {0x12, 18}, {0x13, 9}, {0x16, 1}, {0x17, 5},
|
||||
{0x18, 2}, {0x19, 3}, {0x1a, 7}, {0x1c, 2}, {0x1d, 1}, {0x1f, 22},
|
||||
{0x20, 3}, {0x2b, 3}, {0x2c, 2}, {0x2d, 11}, {0x2e, 1}, {0x30, 3},
|
||||
{0x31, 2}, {0x32, 1}, {0xa7, 2}, {0xa9, 2}, {0xaa, 4}, {0xab, 8},
|
||||
{0xfa, 2}, {0xfb, 5}, {0xfd, 4}, {0xfe, 3}, {0xff, 9},
|
||||
};
|
||||
static constexpr unsigned char singletons0_lower[] = {
|
||||
0xad, 0x78, 0x79, 0x8b, 0x8d, 0xa2, 0x30, 0x57, 0x58, 0x8b, 0x8c, 0x90,
|
||||
0x1c, 0x1d, 0xdd, 0x0e, 0x0f, 0x4b, 0x4c, 0xfb, 0xfc, 0x2e, 0x2f, 0x3f,
|
||||
0x5c, 0x5d, 0x5f, 0xb5, 0xe2, 0x84, 0x8d, 0x8e, 0x91, 0x92, 0xa9, 0xb1,
|
||||
0xba, 0xbb, 0xc5, 0xc6, 0xc9, 0xca, 0xde, 0xe4, 0xe5, 0xff, 0x00, 0x04,
|
||||
0x11, 0x12, 0x29, 0x31, 0x34, 0x37, 0x3a, 0x3b, 0x3d, 0x49, 0x4a, 0x5d,
|
||||
0x84, 0x8e, 0x92, 0xa9, 0xb1, 0xb4, 0xba, 0xbb, 0xc6, 0xca, 0xce, 0xcf,
|
||||
0xe4, 0xe5, 0x00, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a,
|
||||
0x3b, 0x45, 0x46, 0x49, 0x4a, 0x5e, 0x64, 0x65, 0x84, 0x91, 0x9b, 0x9d,
|
||||
0xc9, 0xce, 0xcf, 0x0d, 0x11, 0x29, 0x45, 0x49, 0x57, 0x64, 0x65, 0x8d,
|
||||
0x91, 0xa9, 0xb4, 0xba, 0xbb, 0xc5, 0xc9, 0xdf, 0xe4, 0xe5, 0xf0, 0x0d,
|
||||
0x11, 0x45, 0x49, 0x64, 0x65, 0x80, 0x84, 0xb2, 0xbc, 0xbe, 0xbf, 0xd5,
|
||||
0xd7, 0xf0, 0xf1, 0x83, 0x85, 0x8b, 0xa4, 0xa6, 0xbe, 0xbf, 0xc5, 0xc7,
|
||||
0xce, 0xcf, 0xda, 0xdb, 0x48, 0x98, 0xbd, 0xcd, 0xc6, 0xce, 0xcf, 0x49,
|
||||
0x4e, 0x4f, 0x57, 0x59, 0x5e, 0x5f, 0x89, 0x8e, 0x8f, 0xb1, 0xb6, 0xb7,
|
||||
0xbf, 0xc1, 0xc6, 0xc7, 0xd7, 0x11, 0x16, 0x17, 0x5b, 0x5c, 0xf6, 0xf7,
|
||||
0xfe, 0xff, 0x80, 0x0d, 0x6d, 0x71, 0xde, 0xdf, 0x0e, 0x0f, 0x1f, 0x6e,
|
||||
0x6f, 0x1c, 0x1d, 0x5f, 0x7d, 0x7e, 0xae, 0xaf, 0xbb, 0xbc, 0xfa, 0x16,
|
||||
0x17, 0x1e, 0x1f, 0x46, 0x47, 0x4e, 0x4f, 0x58, 0x5a, 0x5c, 0x5e, 0x7e,
|
||||
0x7f, 0xb5, 0xc5, 0xd4, 0xd5, 0xdc, 0xf0, 0xf1, 0xf5, 0x72, 0x73, 0x8f,
|
||||
0x74, 0x75, 0x96, 0x2f, 0x5f, 0x26, 0x2e, 0x2f, 0xa7, 0xaf, 0xb7, 0xbf,
|
||||
0xc7, 0xcf, 0xd7, 0xdf, 0x9a, 0x40, 0x97, 0x98, 0x30, 0x8f, 0x1f, 0xc0,
|
||||
0xc1, 0xce, 0xff, 0x4e, 0x4f, 0x5a, 0x5b, 0x07, 0x08, 0x0f, 0x10, 0x27,
|
||||
0x2f, 0xee, 0xef, 0x6e, 0x6f, 0x37, 0x3d, 0x3f, 0x42, 0x45, 0x90, 0x91,
|
||||
0xfe, 0xff, 0x53, 0x67, 0x75, 0xc8, 0xc9, 0xd0, 0xd1, 0xd8, 0xd9, 0xe7,
|
||||
0xfe, 0xff,
|
||||
};
|
||||
static constexpr singleton singletons1[] = {
|
||||
{0x00, 6}, {0x01, 1}, {0x03, 1}, {0x04, 2}, {0x08, 8}, {0x09, 2},
|
||||
{0x0a, 5}, {0x0b, 2}, {0x0e, 4}, {0x10, 1}, {0x11, 2}, {0x12, 5},
|
||||
{0x13, 17}, {0x14, 1}, {0x15, 2}, {0x17, 2}, {0x19, 13}, {0x1c, 5},
|
||||
{0x1d, 8}, {0x24, 1}, {0x6a, 3}, {0x6b, 2}, {0xbc, 2}, {0xd1, 2},
|
||||
{0xd4, 12}, {0xd5, 9}, {0xd6, 2}, {0xd7, 2}, {0xda, 1}, {0xe0, 5},
|
||||
{0xe1, 2}, {0xe8, 2}, {0xee, 32}, {0xf0, 4}, {0xf8, 2}, {0xf9, 2},
|
||||
{0xfa, 2}, {0xfb, 1},
|
||||
};
|
||||
static constexpr unsigned char singletons1_lower[] = {
|
||||
0x0c, 0x27, 0x3b, 0x3e, 0x4e, 0x4f, 0x8f, 0x9e, 0x9e, 0x9f, 0x06, 0x07,
|
||||
0x09, 0x36, 0x3d, 0x3e, 0x56, 0xf3, 0xd0, 0xd1, 0x04, 0x14, 0x18, 0x36,
|
||||
0x37, 0x56, 0x57, 0x7f, 0xaa, 0xae, 0xaf, 0xbd, 0x35, 0xe0, 0x12, 0x87,
|
||||
0x89, 0x8e, 0x9e, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a,
|
||||
0x45, 0x46, 0x49, 0x4a, 0x4e, 0x4f, 0x64, 0x65, 0x5c, 0xb6, 0xb7, 0x1b,
|
||||
0x1c, 0x07, 0x08, 0x0a, 0x0b, 0x14, 0x17, 0x36, 0x39, 0x3a, 0xa8, 0xa9,
|
||||
0xd8, 0xd9, 0x09, 0x37, 0x90, 0x91, 0xa8, 0x07, 0x0a, 0x3b, 0x3e, 0x66,
|
||||
0x69, 0x8f, 0x92, 0x6f, 0x5f, 0xee, 0xef, 0x5a, 0x62, 0x9a, 0x9b, 0x27,
|
||||
0x28, 0x55, 0x9d, 0xa0, 0xa1, 0xa3, 0xa4, 0xa7, 0xa8, 0xad, 0xba, 0xbc,
|
||||
0xc4, 0x06, 0x0b, 0x0c, 0x15, 0x1d, 0x3a, 0x3f, 0x45, 0x51, 0xa6, 0xa7,
|
||||
0xcc, 0xcd, 0xa0, 0x07, 0x19, 0x1a, 0x22, 0x25, 0x3e, 0x3f, 0xc5, 0xc6,
|
||||
0x04, 0x20, 0x23, 0x25, 0x26, 0x28, 0x33, 0x38, 0x3a, 0x48, 0x4a, 0x4c,
|
||||
0x50, 0x53, 0x55, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x63, 0x65, 0x66,
|
||||
0x6b, 0x73, 0x78, 0x7d, 0x7f, 0x8a, 0xa4, 0xaa, 0xaf, 0xb0, 0xc0, 0xd0,
|
||||
0xae, 0xaf, 0x79, 0xcc, 0x6e, 0x6f, 0x93,
|
||||
};
|
||||
static constexpr unsigned char normal0[] = {
|
||||
0x00, 0x20, 0x5f, 0x22, 0x82, 0xdf, 0x04, 0x82, 0x44, 0x08, 0x1b, 0x04,
|
||||
0x06, 0x11, 0x81, 0xac, 0x0e, 0x80, 0xab, 0x35, 0x28, 0x0b, 0x80, 0xe0,
|
||||
0x03, 0x19, 0x08, 0x01, 0x04, 0x2f, 0x04, 0x34, 0x04, 0x07, 0x03, 0x01,
|
||||
0x07, 0x06, 0x07, 0x11, 0x0a, 0x50, 0x0f, 0x12, 0x07, 0x55, 0x07, 0x03,
|
||||
0x04, 0x1c, 0x0a, 0x09, 0x03, 0x08, 0x03, 0x07, 0x03, 0x02, 0x03, 0x03,
|
||||
0x03, 0x0c, 0x04, 0x05, 0x03, 0x0b, 0x06, 0x01, 0x0e, 0x15, 0x05, 0x3a,
|
||||
0x03, 0x11, 0x07, 0x06, 0x05, 0x10, 0x07, 0x57, 0x07, 0x02, 0x07, 0x15,
|
||||
0x0d, 0x50, 0x04, 0x43, 0x03, 0x2d, 0x03, 0x01, 0x04, 0x11, 0x06, 0x0f,
|
||||
0x0c, 0x3a, 0x04, 0x1d, 0x25, 0x5f, 0x20, 0x6d, 0x04, 0x6a, 0x25, 0x80,
|
||||
0xc8, 0x05, 0x82, 0xb0, 0x03, 0x1a, 0x06, 0x82, 0xfd, 0x03, 0x59, 0x07,
|
||||
0x15, 0x0b, 0x17, 0x09, 0x14, 0x0c, 0x14, 0x0c, 0x6a, 0x06, 0x0a, 0x06,
|
||||
0x1a, 0x06, 0x59, 0x07, 0x2b, 0x05, 0x46, 0x0a, 0x2c, 0x04, 0x0c, 0x04,
|
||||
0x01, 0x03, 0x31, 0x0b, 0x2c, 0x04, 0x1a, 0x06, 0x0b, 0x03, 0x80, 0xac,
|
||||
0x06, 0x0a, 0x06, 0x21, 0x3f, 0x4c, 0x04, 0x2d, 0x03, 0x74, 0x08, 0x3c,
|
||||
0x03, 0x0f, 0x03, 0x3c, 0x07, 0x38, 0x08, 0x2b, 0x05, 0x82, 0xff, 0x11,
|
||||
0x18, 0x08, 0x2f, 0x11, 0x2d, 0x03, 0x20, 0x10, 0x21, 0x0f, 0x80, 0x8c,
|
||||
0x04, 0x82, 0x97, 0x19, 0x0b, 0x15, 0x88, 0x94, 0x05, 0x2f, 0x05, 0x3b,
|
||||
0x07, 0x02, 0x0e, 0x18, 0x09, 0x80, 0xb3, 0x2d, 0x74, 0x0c, 0x80, 0xd6,
|
||||
0x1a, 0x0c, 0x05, 0x80, 0xff, 0x05, 0x80, 0xdf, 0x0c, 0xee, 0x0d, 0x03,
|
||||
0x84, 0x8d, 0x03, 0x37, 0x09, 0x81, 0x5c, 0x14, 0x80, 0xb8, 0x08, 0x80,
|
||||
0xcb, 0x2a, 0x38, 0x03, 0x0a, 0x06, 0x38, 0x08, 0x46, 0x08, 0x0c, 0x06,
|
||||
0x74, 0x0b, 0x1e, 0x03, 0x5a, 0x04, 0x59, 0x09, 0x80, 0x83, 0x18, 0x1c,
|
||||
0x0a, 0x16, 0x09, 0x4c, 0x04, 0x80, 0x8a, 0x06, 0xab, 0xa4, 0x0c, 0x17,
|
||||
0x04, 0x31, 0xa1, 0x04, 0x81, 0xda, 0x26, 0x07, 0x0c, 0x05, 0x05, 0x80,
|
||||
0xa5, 0x11, 0x81, 0x6d, 0x10, 0x78, 0x28, 0x2a, 0x06, 0x4c, 0x04, 0x80,
|
||||
0x8d, 0x04, 0x80, 0xbe, 0x03, 0x1b, 0x03, 0x0f, 0x0d,
|
||||
};
|
||||
static constexpr unsigned char normal1[] = {
|
||||
0x5e, 0x22, 0x7b, 0x05, 0x03, 0x04, 0x2d, 0x03, 0x66, 0x03, 0x01, 0x2f,
|
||||
0x2e, 0x80, 0x82, 0x1d, 0x03, 0x31, 0x0f, 0x1c, 0x04, 0x24, 0x09, 0x1e,
|
||||
0x05, 0x2b, 0x05, 0x44, 0x04, 0x0e, 0x2a, 0x80, 0xaa, 0x06, 0x24, 0x04,
|
||||
0x24, 0x04, 0x28, 0x08, 0x34, 0x0b, 0x01, 0x80, 0x90, 0x81, 0x37, 0x09,
|
||||
0x16, 0x0a, 0x08, 0x80, 0x98, 0x39, 0x03, 0x63, 0x08, 0x09, 0x30, 0x16,
|
||||
0x05, 0x21, 0x03, 0x1b, 0x05, 0x01, 0x40, 0x38, 0x04, 0x4b, 0x05, 0x2f,
|
||||
0x04, 0x0a, 0x07, 0x09, 0x07, 0x40, 0x20, 0x27, 0x04, 0x0c, 0x09, 0x36,
|
||||
0x03, 0x3a, 0x05, 0x1a, 0x07, 0x04, 0x0c, 0x07, 0x50, 0x49, 0x37, 0x33,
|
||||
0x0d, 0x33, 0x07, 0x2e, 0x08, 0x0a, 0x81, 0x26, 0x52, 0x4e, 0x28, 0x08,
|
||||
0x2a, 0x56, 0x1c, 0x14, 0x17, 0x09, 0x4e, 0x04, 0x1e, 0x0f, 0x43, 0x0e,
|
||||
0x19, 0x07, 0x0a, 0x06, 0x48, 0x08, 0x27, 0x09, 0x75, 0x0b, 0x3f, 0x41,
|
||||
0x2a, 0x06, 0x3b, 0x05, 0x0a, 0x06, 0x51, 0x06, 0x01, 0x05, 0x10, 0x03,
|
||||
0x05, 0x80, 0x8b, 0x62, 0x1e, 0x48, 0x08, 0x0a, 0x80, 0xa6, 0x5e, 0x22,
|
||||
0x45, 0x0b, 0x0a, 0x06, 0x0d, 0x13, 0x39, 0x07, 0x0a, 0x36, 0x2c, 0x04,
|
||||
0x10, 0x80, 0xc0, 0x3c, 0x64, 0x53, 0x0c, 0x48, 0x09, 0x0a, 0x46, 0x45,
|
||||
0x1b, 0x48, 0x08, 0x53, 0x1d, 0x39, 0x81, 0x07, 0x46, 0x0a, 0x1d, 0x03,
|
||||
0x47, 0x49, 0x37, 0x03, 0x0e, 0x08, 0x0a, 0x06, 0x39, 0x07, 0x0a, 0x81,
|
||||
0x36, 0x19, 0x80, 0xb7, 0x01, 0x0f, 0x32, 0x0d, 0x83, 0x9b, 0x66, 0x75,
|
||||
0x0b, 0x80, 0xc4, 0x8a, 0xbc, 0x84, 0x2f, 0x8f, 0xd1, 0x82, 0x47, 0xa1,
|
||||
0xb9, 0x82, 0x39, 0x07, 0x2a, 0x04, 0x02, 0x60, 0x26, 0x0a, 0x46, 0x0a,
|
||||
0x28, 0x05, 0x13, 0x82, 0xb0, 0x5b, 0x65, 0x4b, 0x04, 0x39, 0x07, 0x11,
|
||||
0x40, 0x05, 0x0b, 0x02, 0x0e, 0x97, 0xf8, 0x08, 0x84, 0xd6, 0x2a, 0x09,
|
||||
0xa2, 0xf7, 0x81, 0x1f, 0x31, 0x03, 0x11, 0x04, 0x08, 0x81, 0x8c, 0x89,
|
||||
0x04, 0x6b, 0x05, 0x0d, 0x03, 0x09, 0x07, 0x10, 0x93, 0x60, 0x80, 0xf6,
|
||||
0x0a, 0x73, 0x08, 0x6e, 0x17, 0x46, 0x80, 0x9a, 0x14, 0x0c, 0x57, 0x09,
|
||||
0x19, 0x80, 0x87, 0x81, 0x47, 0x03, 0x85, 0x42, 0x0f, 0x15, 0x85, 0x50,
|
||||
0x2b, 0x80, 0xd5, 0x2d, 0x03, 0x1a, 0x04, 0x02, 0x81, 0x70, 0x3a, 0x05,
|
||||
0x01, 0x85, 0x00, 0x80, 0xd7, 0x29, 0x4c, 0x04, 0x0a, 0x04, 0x02, 0x83,
|
||||
0x11, 0x44, 0x4c, 0x3d, 0x80, 0xc2, 0x3c, 0x06, 0x01, 0x04, 0x55, 0x05,
|
||||
0x1b, 0x34, 0x02, 0x81, 0x0e, 0x2c, 0x04, 0x64, 0x0c, 0x56, 0x0a, 0x80,
|
||||
0xae, 0x38, 0x1d, 0x0d, 0x2c, 0x04, 0x09, 0x07, 0x02, 0x0e, 0x06, 0x80,
|
||||
0x9a, 0x83, 0xd8, 0x08, 0x0d, 0x03, 0x0d, 0x03, 0x74, 0x0c, 0x59, 0x07,
|
||||
0x0c, 0x14, 0x0c, 0x04, 0x38, 0x08, 0x0a, 0x06, 0x28, 0x08, 0x22, 0x4e,
|
||||
0x81, 0x54, 0x0c, 0x15, 0x03, 0x03, 0x05, 0x07, 0x09, 0x19, 0x07, 0x07,
|
||||
0x09, 0x03, 0x0d, 0x07, 0x29, 0x80, 0xcb, 0x25, 0x0a, 0x84, 0x06,
|
||||
};
|
||||
auto lower = static_cast<uint16_t>(cp);
|
||||
if (cp < 0x10000) {
|
||||
return is_printable(lower, singletons0,
|
||||
sizeof(singletons0) / sizeof(*singletons0),
|
||||
singletons0_lower, normal0, sizeof(normal0));
|
||||
}
|
||||
if (cp < 0x20000) {
|
||||
return is_printable(lower, singletons1,
|
||||
sizeof(singletons1) / sizeof(*singletons1),
|
||||
singletons1_lower, normal1, sizeof(normal1));
|
||||
}
|
||||
if (0x2a6de <= cp && cp < 0x2a700) return false;
|
||||
if (0x2b735 <= cp && cp < 0x2b740) return false;
|
||||
if (0x2b81e <= cp && cp < 0x2b820) return false;
|
||||
if (0x2cea2 <= cp && cp < 0x2ceb0) return false;
|
||||
if (0x2ebe1 <= cp && cp < 0x2f800) return false;
|
||||
if (0x2fa1e <= cp && cp < 0x30000) return false;
|
||||
if (0x3134b <= cp && cp < 0xe0100) return false;
|
||||
if (0xe01f0 <= cp && cp < 0x110000) return false;
|
||||
return cp < 0x110000;
|
||||
}
|
||||
|
||||
inline auto needs_escape(uint32_t cp) -> bool {
|
||||
return cp < 0x20 || cp == 0x7f || cp == '"' || cp == '\\' ||
|
||||
!is_printable(cp);
|
||||
}
|
||||
|
||||
template <typename Char> struct find_escape_result {
|
||||
const Char* begin;
|
||||
const Char* end;
|
||||
uint32_t cp;
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
auto find_escape(const Char* begin, const Char* end)
|
||||
-> find_escape_result<Char> {
|
||||
for (; begin != end; ++begin) {
|
||||
auto cp = static_cast<typename std::make_unsigned<Char>::type>(*begin);
|
||||
if (sizeof(Char) == 1 && cp >= 0x80) continue;
|
||||
if (needs_escape(cp)) return {begin, begin + 1, cp};
|
||||
}
|
||||
return {begin, nullptr, 0};
|
||||
}
|
||||
|
||||
inline auto find_escape(const char* begin, const char* end)
|
||||
-> find_escape_result<char> {
|
||||
if (!is_utf8()) return find_escape<char>(begin, end);
|
||||
auto result = find_escape_result<char>{end, nullptr, 0};
|
||||
for_each_codepoint(string_view(begin, to_unsigned(end - begin)),
|
||||
[&](uint32_t cp, string_view sv) {
|
||||
if (needs_escape(cp)) {
|
||||
result = {sv.begin(), sv.end(), cp};
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
auto write_range_entry(OutputIt out, basic_string_view<Char> str) -> OutputIt {
|
||||
*out++ = '"';
|
||||
auto begin = str.begin(), end = str.end();
|
||||
do {
|
||||
auto escape = find_escape(begin, end);
|
||||
out = copy_str<Char>(begin, escape.begin, out);
|
||||
begin = escape.end;
|
||||
if (!begin) break;
|
||||
auto c = static_cast<Char>(escape.cp);
|
||||
switch (escape.cp) {
|
||||
case '\n':
|
||||
*out++ = '\\';
|
||||
c = 'n';
|
||||
break;
|
||||
case '\r':
|
||||
*out++ = '\\';
|
||||
c = 'r';
|
||||
break;
|
||||
case '\t':
|
||||
*out++ = '\\';
|
||||
c = 't';
|
||||
break;
|
||||
case '"':
|
||||
FMT_FALLTHROUGH;
|
||||
case '\\':
|
||||
*out++ = '\\';
|
||||
break;
|
||||
default:
|
||||
if (is_utf8()) {
|
||||
if (escape.cp < 0x100) {
|
||||
out = format_to(out, "\\x{:02x}", escape.cp);
|
||||
continue;
|
||||
}
|
||||
if (escape.cp < 0x10000) {
|
||||
out = format_to(out, "\\u{:04x}", escape.cp);
|
||||
continue;
|
||||
}
|
||||
if (escape.cp < 0x110000) {
|
||||
out = format_to(out, "\\U{:08x}", escape.cp);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (Char escape_char : basic_string_view<Char>(
|
||||
escape.begin, to_unsigned(escape.end - escape.begin))) {
|
||||
out = format_to(
|
||||
out, "\\x{:02x}",
|
||||
static_cast<typename std::make_unsigned<Char>::type>(escape_char));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
*out++ = c;
|
||||
} while (begin != end);
|
||||
*out++ = '"';
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt, typename T,
|
||||
FMT_ENABLE_IF(std::is_convertible<T, std_string_view<char>>::value)>
|
||||
inline auto write_range_entry(OutputIt out, const T& str) -> OutputIt {
|
||||
auto sv = std_string_view<Char>(str);
|
||||
return write_range_entry<Char>(out, basic_string_view<Char>(sv));
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt, typename Arg,
|
||||
FMT_ENABLE_IF(std::is_same<Arg, Char>::value)>
|
||||
OutputIt write_range_entry(OutputIt out, const Arg v) {
|
||||
*out++ = '\'';
|
||||
*out++ = v;
|
||||
*out++ = '\'';
|
||||
return out;
|
||||
}
|
||||
|
||||
template <
|
||||
typename Char, typename OutputIt, typename Arg,
|
||||
FMT_ENABLE_IF(!is_std_string_like<typename std::decay<Arg>::type>::value &&
|
||||
!std::is_same<Arg, Char>::value)>
|
||||
OutputIt write_range_entry(OutputIt out, const Arg& v) {
|
||||
return write<Char>(out, v);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T> struct is_tuple_like {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
|
||||
};
|
||||
|
||||
template <typename TupleT, typename Char>
|
||||
struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
|
||||
private:
|
||||
// C++11 generic lambda for format().
|
||||
template <typename FormatContext> struct format_each {
|
||||
template <typename T> void operator()(const T& v) {
|
||||
if (i > 0) out = detail::write_delimiter(out);
|
||||
out = detail::write_range_entry<Char>(out, v);
|
||||
++i;
|
||||
}
|
||||
int i;
|
||||
typename FormatContext::iterator& out;
|
||||
};
|
||||
|
||||
public:
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext = format_context>
|
||||
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
*out++ = '(';
|
||||
detail::for_each(values, format_each<FormatContext>{0, out});
|
||||
*out++ = ')';
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Char> struct is_range {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
detail::is_range_<T>::value && !detail::is_std_string_like<T>::value &&
|
||||
!detail::is_map<T>::value &&
|
||||
!std::is_convertible<T, std::basic_string<Char>>::value &&
|
||||
!std::is_constructible<detail::std_string_view<Char>, T>::value;
|
||||
};
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct formatter<
|
||||
T, Char,
|
||||
enable_if_t<
|
||||
fmt::is_range<T, Char>::value
|
||||
// Workaround a bug in MSVC 2019 and earlier.
|
||||
#if !FMT_MSC_VER
|
||||
&& (is_formattable<detail::value_type<T>, Char>::value ||
|
||||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
|
||||
#endif
|
||||
>> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <
|
||||
typename FormatContext, typename U,
|
||||
FMT_ENABLE_IF(
|
||||
std::is_same<U, conditional_t<detail::has_const_begin_end<T>::value,
|
||||
const T, T>>::value)>
|
||||
auto format(U& range, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
#ifdef FMT_DEPRECATED_BRACED_RANGES
|
||||
Char prefix = '{';
|
||||
Char postfix = '}';
|
||||
#else
|
||||
Char prefix = detail::is_set<T>::value ? '{' : '[';
|
||||
Char postfix = detail::is_set<T>::value ? '}' : ']';
|
||||
#endif
|
||||
auto out = ctx.out();
|
||||
*out++ = prefix;
|
||||
int i = 0;
|
||||
auto it = std::begin(range);
|
||||
auto end = std::end(range);
|
||||
for (; it != end; ++it) {
|
||||
if (i > 0) out = detail::write_delimiter(out);
|
||||
out = detail::write_range_entry<Char>(out, *it);
|
||||
++i;
|
||||
}
|
||||
*out++ = postfix;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct formatter<
|
||||
T, Char,
|
||||
enable_if_t<
|
||||
detail::is_map<T>::value
|
||||
// Workaround a bug in MSVC 2019 and earlier.
|
||||
#if !FMT_MSC_VER
|
||||
&& (is_formattable<detail::value_type<T>, Char>::value ||
|
||||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
|
||||
#endif
|
||||
>> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <
|
||||
typename FormatContext, typename U,
|
||||
FMT_ENABLE_IF(
|
||||
std::is_same<U, conditional_t<detail::has_const_begin_end<T>::value,
|
||||
const T, T>>::value)>
|
||||
auto format(U& map, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
*out++ = '{';
|
||||
int i = 0;
|
||||
for (const auto& item : map) {
|
||||
if (i > 0) out = detail::write_delimiter(out);
|
||||
out = detail::write_range_entry<Char>(out, item.first);
|
||||
*out++ = ':';
|
||||
*out++ = ' ';
|
||||
out = detail::write_range_entry<Char>(out, item.second);
|
||||
++i;
|
||||
}
|
||||
*out++ = '}';
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename... T> struct tuple_join_view : detail::view {
|
||||
const std::tuple<T...>& tuple;
|
||||
basic_string_view<Char> sep;
|
||||
|
||||
tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s)
|
||||
: tuple(t), sep{s} {}
|
||||
};
|
||||
|
||||
template <typename Char, typename... T>
|
||||
using tuple_arg_join = tuple_join_view<Char, T...>;
|
||||
|
||||
// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
|
||||
// support in tuple_join. It is disabled by default because of issues with
|
||||
// the dynamic width and precision.
|
||||
#ifndef FMT_TUPLE_JOIN_SPECIFIERS
|
||||
# define FMT_TUPLE_JOIN_SPECIFIERS 0
|
||||
#endif
|
||||
|
||||
template <typename Char, typename... T>
|
||||
struct formatter<tuple_join_view<Char, T...>, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return do_parse(ctx, std::integral_constant<size_t, sizeof...(T)>());
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const tuple_join_view<Char, T...>& value,
|
||||
FormatContext& ctx) const -> typename FormatContext::iterator {
|
||||
return do_format(value, ctx,
|
||||
std::integral_constant<size_t, sizeof...(T)>());
|
||||
}
|
||||
|
||||
private:
|
||||
std::tuple<formatter<typename std::decay<T>::type, Char>...> formatters_;
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
|
||||
std::integral_constant<size_t, 0>)
|
||||
-> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename ParseContext, size_t N>
|
||||
FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
|
||||
std::integral_constant<size_t, N>)
|
||||
-> decltype(ctx.begin()) {
|
||||
auto end = ctx.begin();
|
||||
#if FMT_TUPLE_JOIN_SPECIFIERS
|
||||
end = std::get<sizeof...(T) - N>(formatters_).parse(ctx);
|
||||
if (N > 1) {
|
||||
auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
|
||||
if (end != end1)
|
||||
FMT_THROW(format_error("incompatible format specs for tuple elements"));
|
||||
}
|
||||
#endif
|
||||
return end;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto do_format(const tuple_join_view<Char, T...>&, FormatContext& ctx,
|
||||
std::integral_constant<size_t, 0>) const ->
|
||||
typename FormatContext::iterator {
|
||||
return ctx.out();
|
||||
}
|
||||
|
||||
template <typename FormatContext, size_t N>
|
||||
auto do_format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
|
||||
std::integral_constant<size_t, N>) const ->
|
||||
typename FormatContext::iterator {
|
||||
auto out = std::get<sizeof...(T) - N>(formatters_)
|
||||
.format(std::get<sizeof...(T) - N>(value.tuple), ctx);
|
||||
if (N > 1) {
|
||||
out = std::copy(value.sep.begin(), value.sep.end(), out);
|
||||
ctx.advance_to(out);
|
||||
return do_format(value, ctx, std::integral_constant<size_t, N - 1>());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
|
||||
/**
|
||||
\rst
|
||||
Returns an object that formats `tuple` with elements separated by `sep`.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::tuple<int, char> t = {1, 'a'};
|
||||
fmt::print("{}", fmt::join(t, ", "));
|
||||
// Output: "1, a"
|
||||
\endrst
|
||||
*/
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
|
||||
-> tuple_join_view<char, T...> {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple,
|
||||
basic_string_view<wchar_t> sep)
|
||||
-> tuple_join_view<wchar_t, T...> {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Returns an object that formats `initializer_list` with elements separated by
|
||||
`sep`.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print("{}", fmt::join({1, 2, 3}, ", "));
|
||||
// Output: "1, 2, 3"
|
||||
\endrst
|
||||
*/
|
||||
template <typename T>
|
||||
auto join(std::initializer_list<T> list, string_view sep)
|
||||
-> join_view<const T*, const T*> {
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_RANGES_H_
|
|
@ -5,8 +5,8 @@
|
|||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_WCHAR_H_
|
||||
#define FMT_WCHAR_H_
|
||||
#ifndef FMT_XCHAR_H_
|
||||
#define FMT_XCHAR_H_
|
||||
|
||||
#include <cwchar>
|
||||
#include <tuple>
|
||||
|
@ -217,11 +217,11 @@ inline void vprint(wstring_view fmt, wformat_args args) {
|
|||
|
||||
template <typename... T>
|
||||
void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
|
||||
return vprint(f, wstring_view(fmt), make_wformat_args(args...));
|
||||
return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
|
||||
return vprint(wstring_view(fmt), make_wformat_args(args...));
|
||||
return vprint(wstring_view(fmt), fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -233,4 +233,4 @@ template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
|
|||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_WCHAR_H_
|
||||
#endif // FMT_XCHAR_H_
|
|
@ -79,7 +79,6 @@ export module fmt;
|
|||
#define FMT_END_DETAIL_NAMESPACE \
|
||||
} \
|
||||
export {
|
||||
|
||||
// all library-provided declarations and definitions
|
||||
// must be in the module purview to be exported
|
||||
#include "fmt/args.h"
|
|
@ -10,6 +10,52 @@
|
|||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
// DEPRECATED!
|
||||
template <typename T = void> struct basic_data {
|
||||
FMT_API static constexpr const char digits[100][2] = {
|
||||
{'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'},
|
||||
{'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'},
|
||||
{'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'},
|
||||
{'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'},
|
||||
{'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'},
|
||||
{'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'},
|
||||
{'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'},
|
||||
{'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'},
|
||||
{'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'},
|
||||
{'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'},
|
||||
{'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'},
|
||||
{'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'},
|
||||
{'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'},
|
||||
{'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'},
|
||||
{'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'},
|
||||
{'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'},
|
||||
{'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}};
|
||||
FMT_API static constexpr const char hex_digits[] = "0123456789abcdef";
|
||||
FMT_API static constexpr const char signs[4] = {0, '-', '+', ' '};
|
||||
FMT_API static constexpr const char left_padding_shifts[5] = {31, 31, 0, 1,
|
||||
0};
|
||||
FMT_API static constexpr const char right_padding_shifts[5] = {0, 31, 0, 1,
|
||||
0};
|
||||
FMT_API static constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+',
|
||||
0x1000000u | ' '};
|
||||
};
|
||||
|
||||
#ifdef FMT_SHARED
|
||||
// Required for -flto, -fivisibility=hidden and -shared to work
|
||||
extern template struct basic_data<void>;
|
||||
#endif
|
||||
|
||||
#if __cplusplus < 201703L
|
||||
// DEPRECATED! These are here only for ABI compatiblity.
|
||||
template <typename T> constexpr const char basic_data<T>::digits[][2];
|
||||
template <typename T> constexpr const char basic_data<T>::hex_digits[];
|
||||
template <typename T> constexpr const char basic_data<T>::signs[];
|
||||
template <typename T> constexpr const char basic_data<T>::left_padding_shifts[];
|
||||
template <typename T>
|
||||
constexpr const char basic_data<T>::right_padding_shifts[];
|
||||
template <typename T> constexpr const unsigned basic_data<T>::prefixes[];
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
int format_float(char* buf, std::size_t size, const char* format, int precision,
|
||||
T value) {
|
|
@ -26,19 +26,17 @@
|
|||
# endif
|
||||
# include <io.h>
|
||||
|
||||
# define O_CREAT _O_CREAT
|
||||
# define O_TRUNC _O_TRUNC
|
||||
|
||||
# ifndef S_IRUSR
|
||||
# define S_IRUSR _S_IREAD
|
||||
# endif
|
||||
|
||||
# ifndef S_IWUSR
|
||||
# define S_IWUSR _S_IWRITE
|
||||
# endif
|
||||
|
||||
# ifdef __MINGW32__
|
||||
# define _SH_DENYNO 0x40
|
||||
# ifndef S_IRGRP
|
||||
# define S_IRGRP 0
|
||||
# endif
|
||||
# ifndef S_IROTH
|
||||
# define S_IROTH 0
|
||||
# endif
|
||||
# endif // _WIN32
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
@ -213,7 +211,10 @@ int buffered_file::fileno() const {
|
|||
|
||||
#if FMT_USE_FCNTL
|
||||
file::file(cstring_view path, int oflag) {
|
||||
int mode = S_IRUSR | S_IWUSR;
|
||||
# ifdef _WIN32
|
||||
using mode_t = int;
|
||||
# endif
|
||||
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
fd_ = -1;
|
||||
FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode));
|
|
@ -0,0 +1,37 @@
|
|||
#!/bin/bash
|
||||
if [ $# -eq 0 ] || [ $# -gt 2 ]
|
||||
then
|
||||
echo "Usage: generate_profile.sh Path_Of_Foundation_Build_Directory Storage_Engine"
|
||||
exit 1
|
||||
fi
|
||||
fdbdir=$1
|
||||
storage_engine='ssd'
|
||||
if [ $# -eq 2 ]
|
||||
then
|
||||
storage_engine=$2
|
||||
fi
|
||||
|
||||
export LD_LIBRARY_PATH=$fdbdir/lib:$LD_LIBRARY_PATH
|
||||
export FDB_CLUSTER_FILE=$fdbdir/fdb.cluster
|
||||
export LLVM_PROFILE_FILE=$fdbdir/sandbox/fdb-%p.profraw
|
||||
$fdbdir/bin/fdbmonitor --conffile $fdbdir/sandbox/foundationdb.conf --lockfile $fdbdir/sandbox/fdbmonitor.pid &
|
||||
# This profile will be ignored
|
||||
export LLVM_PROFILE_FILE=$fdbdir/sandbox/cli-%m.profraw
|
||||
$fdbdir/bin/fdbcli -C $fdbdir/fdb.cluster --exec "configure new $storage_engine single"
|
||||
export LLVM_PROFILE_FILE=$fdbdir/sandbox/mako-build-%m.profraw
|
||||
$fdbdir/bin/mako -p 64 -t 1 --keylen 32 --vallen 16 --mode build --rows 10000 --trace --trace_format json
|
||||
export LLVM_PROFILE_FILE=$fdbdir/sandbox/mako-run-%m.profraw
|
||||
$fdbdir/bin/mako -p 1 -t 2 --keylen 32 --vallen 16 --mode run --rows 10000 --transaction grvg7i2gr1:48cr1:48 --seconds 60 --trace $fdbdir/sandbox/logs --trace_format json
|
||||
|
||||
# Shutdown fdbserver to trigger profile dumping
|
||||
fdbmonitor_pid=$(cat $fdbdir/sandbox/fdbmonitor.pid)
|
||||
fdbserver_pid=$(cat /proc/$fdbmonitor_pid/task/$fdbmonitor_pid/children)
|
||||
gdb --batch --eval-command 'call (void)exit(0)' --pid $fdbserver_pid
|
||||
|
||||
# Clean up
|
||||
kill -9 $fdbmonitor_pid
|
||||
|
||||
# Profile for server
|
||||
llvm-profdata merge -output=$fdbdir/fdb.profdata $fdbdir/sandbox/fdb-*.profraw
|
||||
# Profile for client
|
||||
llvm-profdata merge -output=$fdbdir/mako.profdata $fdbdir/sandbox/mako-*.profraw
|
|
@ -147,7 +147,7 @@ def centos_image_with_fdb_helper(versioned: bool) -> Iterator[Optional[Image]]:
|
|||
container = None
|
||||
image = None
|
||||
try:
|
||||
container = Container("centos", initd=True)
|
||||
container = Container("centos:7", initd=True)
|
||||
for rpm in rpms:
|
||||
container.copy_to(rpm, "/opt")
|
||||
container.run(["bash", "-c", "yum update -y"])
|
||||
|
@ -237,10 +237,6 @@ def test_write(linux_container: Container, snapshot):
|
|||
assert snapshot == linux_container.run(["fdbcli", "--exec", "get x"])
|
||||
|
||||
|
||||
def test_fdbcli_help_text(linux_container: Container, snapshot):
|
||||
assert snapshot == linux_container.run(["fdbcli", "--help"])
|
||||
|
||||
|
||||
def test_execstack_permissions_libfdb_c(linux_container: Container, snapshot):
|
||||
linux_container.run(["ldconfig"])
|
||||
assert snapshot == linux_container.run(
|
||||
|
|
|
@ -160,6 +160,22 @@ FoundationDB may return the following error codes from API functions. If you nee
|
|||
| special_keys_api_failure | 2117| Api call through special keys failed. For more information, read the |
|
||||
| | | ``0xff0xff/error_message`` key |
|
||||
+-----------------------------------------------+-----+--------------------------------------------------------------------------------+
|
||||
| tenant_name_required | 2130| Tenant name must be specified to access data in the cluster |
|
||||
+-----------------------------------------------+-----+--------------------------------------------------------------------------------+
|
||||
| tenant_not_found | 2131| Tenant does not exist |
|
||||
+-----------------------------------------------+-----+--------------------------------------------------------------------------------+
|
||||
| tenant_already_exists | 2132| A tenant with the given name already exists |
|
||||
+-----------------------------------------------+-----+--------------------------------------------------------------------------------+
|
||||
| tenant_not_empty | 2133| Cannot delete a non-empty tenant |
|
||||
+-----------------------------------------------+-----+--------------------------------------------------------------------------------+
|
||||
| invalid_tenant_name | 2134| Tenant name cannot begin with \xff |
|
||||
+-----------------------------------------------+-----+--------------------------------------------------------------------------------+
|
||||
| tenant_prefix_allocator_conflict | 2135| The database already has keys stored at the prefix allocated for the tenant |
|
||||
+-----------------------------------------------+-----+--------------------------------------------------------------------------------+
|
||||
| tenants_disabled | 2136| Tenants have been disabled in the cluster |
|
||||
+-----------------------------------------------+-----+--------------------------------------------------------------------------------+
|
||||
| unknown_tenant | 2137| Tenant is not available from this server |
|
||||
+-----------------------------------------------+-----+--------------------------------------------------------------------------------+
|
||||
| api_version_unset | 2200| API version is not set |
|
||||
+-----------------------------------------------+-----+--------------------------------------------------------------------------------+
|
||||
| api_version_already_set | 2201| API version may be set only once |
|
||||
|
|
|
@ -742,6 +742,12 @@
|
|||
"disabled",
|
||||
"gradual",
|
||||
"aggressive"
|
||||
]},
|
||||
"tenant_mode": {
|
||||
"$enum":[
|
||||
"disabled",
|
||||
"optional_experimental",
|
||||
"required_experimental"
|
||||
]}
|
||||
},
|
||||
"data":{
|
||||
|
|
|
@ -2,8 +2,16 @@
|
|||
Release Notes
|
||||
#############
|
||||
|
||||
6.3.24
|
||||
======
|
||||
* Fixed a bug where get key location can overload proxies. `(PR #6453) <https://github.com/apple/foundationdb/pull/6453>`_
|
||||
* Added a mechanism that can reduce the number of empty peek reply by not always returning empty peek reply immediately. `(PR #6413) <https://github.com/apple/foundationdb/pull/6413>`_
|
||||
* Enable TLS support for Windows. `(PR #6193) <https://github.com/apple/foundationdb/pull/6193>`_
|
||||
* Fixed a bug where a shard gets merged too soon. `(PR #6115) <https://github.com/apple/foundationdb/pull/6115>`_
|
||||
|
||||
6.3.23
|
||||
======
|
||||
* Add AWS v4 header support for backup. `(PR #6025) <https://github.com/apple/foundationdb/pull/6025>`_
|
||||
* Fixed a bug that remoteDCIsHealthy logic is not guarded by CC_ENABLE_WORKER_HEALTH_MONITOR, which may prevent HA failback. `(PR #6106) <https://github.com/apple/foundationdb/pull/6106>`_
|
||||
* Fixed a race condition with updating the coordinated state and updating the master registration. `(PR #6088) <https://github.com/apple/foundationdb/pull/6088>`_
|
||||
* Changed dbinfo broadcast to be explicitly requested by the worker registration message. `(PR #6073) <https://github.com/apple/foundationdb/pull/6073>`_
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "contrib/fmt-8.1.1/include/fmt/format.h"
|
||||
#include "flow/flow.h"
|
||||
#include "flow/Platform.h"
|
||||
#include "flow/DeterministicRandom.h"
|
||||
|
@ -413,7 +414,7 @@ ACTOR Future<Void> logThroughput(int64_t* v, Key* next) {
|
|||
loop {
|
||||
state int64_t last = *v;
|
||||
wait(delay(1));
|
||||
printf("throughput: %ld bytes/s, next: %s\n", *v - last, printable(*next).c_str());
|
||||
fmt::print("throughput: {} bytes/s, next: {}\n", *v - last, printable(*next).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "contrib/fmt-8.1.1/include/fmt/format.h"
|
||||
#include "fdbbackup/BackupTLSConfig.h"
|
||||
#include "fdbclient/JsonBuilder.h"
|
||||
#include "flow/Arena.h"
|
||||
|
@ -1690,7 +1690,7 @@ ACTOR Future<Void> cleanupStatus(Reference<ReadYourWritesTransaction> tr,
|
|||
readMore = true;
|
||||
} catch (Error& e) {
|
||||
// If doc can't be parsed or isn't alive, delete it.
|
||||
TraceEvent(SevWarn, "RemovedDeadBackupLayerStatus").detail("Key", docs[i].key).error(e, true);
|
||||
TraceEvent(SevWarn, "RemovedDeadBackupLayerStatus").errorUnsuppressed(e).detail("Key", docs[i].key);
|
||||
tr->clear(docs[i].key);
|
||||
// If limit is 1 then read more.
|
||||
if (limit == 1)
|
||||
|
@ -2754,7 +2754,7 @@ ACTOR Future<Void> queryBackup(const char* name,
|
|||
reportBackupQueryError(operationId,
|
||||
result,
|
||||
errorMessage =
|
||||
format("the specified restorable version %ld is not valid", restoreVersion));
|
||||
format("the specified restorable version %lld is not valid", restoreVersion));
|
||||
return Void();
|
||||
}
|
||||
Optional<RestorableFileSet> fileSet = wait(bc->getRestoreSet(restoreVersion, keyRangesFilter));
|
||||
|
@ -3081,7 +3081,7 @@ static void addKeyRange(std::string optionValue, Standalone<VectorRef<KeyRangeRe
|
|||
|
||||
// Too many keys
|
||||
default:
|
||||
fprintf(stderr, "ERROR: Invalid key range identified with %ld keys", tokens.size());
|
||||
fmt::print(stderr, "ERROR: Invalid key range identified with {} keys", tokens.size());
|
||||
throw invalid_option_value();
|
||||
break;
|
||||
}
|
||||
|
@ -3887,9 +3887,9 @@ int main(int argc, char* argv[]) {
|
|||
} else {
|
||||
fprintf(stderr, "ERROR: Failed to set knob option '%s': %s\n", knobName.c_str(), e.what());
|
||||
TraceEvent(SevError, "FailedToSetKnob")
|
||||
.error(e)
|
||||
.detail("Knob", printable(knobName))
|
||||
.detail("Value", printable(knobValueString))
|
||||
.error(e);
|
||||
.detail("Value", printable(knobValueString));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
*/
|
||||
|
||||
#include "boost/lexical_cast.hpp"
|
||||
|
||||
#include "contrib/fmt-8.1.1/include/fmt/format.h"
|
||||
#include "fdbcli/fdbcli.actor.h"
|
||||
|
||||
#include "fdbclient/IClientApi.h"
|
||||
|
@ -40,7 +40,7 @@ ACTOR Future<bool> advanceVersionCommandActor(Reference<IDatabase> db, std::vect
|
|||
} else {
|
||||
state Version v;
|
||||
int n = 0;
|
||||
if (sscanf(tokens[1].toString().c_str(), "%ld%n", &v, &n) != 1 || n != tokens[1].size()) {
|
||||
if (sscanf(tokens[1].toString().c_str(), "%" PRId64 "%n", &v, &n) != 1 || n != tokens[1].size()) {
|
||||
printUsage(tokens[0]);
|
||||
return false;
|
||||
} else {
|
||||
|
@ -53,7 +53,7 @@ ACTOR Future<bool> advanceVersionCommandActor(Reference<IDatabase> db, std::vect
|
|||
tr->set(advanceVersionSpecialKey, boost::lexical_cast<std::string>(v));
|
||||
wait(safeThreadFutureToFuture(tr->commit()));
|
||||
} else {
|
||||
printf("Current read version is %ld\n", rv);
|
||||
fmt::print("Current read version is {}\n", rv);
|
||||
return true;
|
||||
}
|
||||
} catch (Error& e) {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "contrib/fmt-8.1.1/include/fmt/format.h"
|
||||
|
||||
#include "fdbcli/fdbcli.actor.h"
|
||||
|
||||
|
@ -115,7 +115,7 @@ ACTOR Future<bool> changeFeedCommandActor(Database localDb, std::vector<StringRe
|
|||
Version end = std::numeric_limits<Version>::max();
|
||||
if (tokens.size() > 3) {
|
||||
int n = 0;
|
||||
if (sscanf(tokens[3].toString().c_str(), "%ld%n", &begin, &n) != 1 || n != tokens[3].size()) {
|
||||
if (sscanf(tokens[3].toString().c_str(), "%" PRId64 "%n", &begin, &n) != 1 || n != tokens[3].size()) {
|
||||
printUsage(tokens[0]);
|
||||
return false;
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ ACTOR Future<bool> changeFeedCommandActor(Database localDb, std::vector<StringRe
|
|||
}
|
||||
Version v;
|
||||
int n = 0;
|
||||
if (sscanf(tokens[3].toString().c_str(), "%ld%n", &v, &n) != 1 || n != tokens[3].size()) {
|
||||
if (sscanf(tokens[3].toString().c_str(), "%" PRId64 "%n", &v, &n) != 1 || n != tokens[3].size()) {
|
||||
printUsage(tokens[0]);
|
||||
return false;
|
||||
} else {
|
||||
|
|
|
@ -176,7 +176,7 @@ ACTOR Future<bool> configureCommandActor(Reference<IDatabase> db,
|
|||
case ConfigurationResult::STORAGE_MIGRATION_DISABLED:
|
||||
fprintf(stderr,
|
||||
"ERROR: Storage engine type cannot be changed because "
|
||||
"storage_migration_mode=disabled.\n");
|
||||
"storage_migration_type=disabled.\n");
|
||||
fprintf(stderr,
|
||||
"Type `configure perpetual_storage_wiggle=1 storage_migration_type=gradual' to enable gradual "
|
||||
"migration with the perpetual wiggle, or `configure "
|
||||
|
|
|
@ -65,13 +65,14 @@ ACTOR Future<bool> changeCoordinators(Reference<IDatabase> db, std::vector<Strin
|
|||
state StringRef new_cluster_description;
|
||||
state std::string auto_coordinators_str;
|
||||
StringRef nameTokenBegin = LiteralStringRef("description=");
|
||||
for (auto tok = tokens.begin() + 1; tok != tokens.end(); ++tok)
|
||||
for (auto tok = tokens.begin() + 1; tok != tokens.end(); ++tok) {
|
||||
if (tok->startsWith(nameTokenBegin)) {
|
||||
new_cluster_description = tok->substr(nameTokenBegin.size());
|
||||
std::copy(tok + 1, tokens.end(), tok);
|
||||
tokens.resize(tokens.size() - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
state bool automatic = tokens.size() == 2 && tokens[1] == LiteralStringRef("auto");
|
||||
state Reference<ITransaction> tr = db->createTransaction();
|
||||
|
@ -96,17 +97,32 @@ ACTOR Future<bool> changeCoordinators(Reference<IDatabase> db, std::vector<Strin
|
|||
tr->set(fdb_cli::coordinatorsProcessSpecialKey, auto_coordinators_str);
|
||||
} else if (tokens.size() > 1) {
|
||||
state std::set<NetworkAddress> new_coordinators_addresses;
|
||||
state std::vector<std::string> newAddresslist;
|
||||
state std::set<Hostname> new_coordinators_hostnames;
|
||||
state std::vector<std::string> newCoordinatorslist;
|
||||
state std::vector<StringRef>::iterator t;
|
||||
for (t = tokens.begin() + 1; t != tokens.end(); ++t) {
|
||||
try {
|
||||
auto const& addr = NetworkAddress::parse(t->toString());
|
||||
if (new_coordinators_addresses.count(addr)) {
|
||||
fprintf(stderr, "ERROR: passed redundant coordinators: `%s'\n", addr.toString().c_str());
|
||||
return true;
|
||||
if (Hostname::isHostname(t->toString())) {
|
||||
// We do not resolve hostnames here. We commit them as is.
|
||||
const auto& hostname = Hostname::parse(t->toString());
|
||||
if (new_coordinators_hostnames.count(hostname)) {
|
||||
fprintf(stderr,
|
||||
"ERROR: passed redundant coordinators: `%s'\n",
|
||||
hostname.toString().c_str());
|
||||
return true;
|
||||
}
|
||||
new_coordinators_hostnames.insert(hostname);
|
||||
newCoordinatorslist.push_back(hostname.toString());
|
||||
} else {
|
||||
const auto& addr = NetworkAddress::parse(t->toString());
|
||||
if (new_coordinators_addresses.count(addr)) {
|
||||
fprintf(
|
||||
stderr, "ERROR: passed redundant coordinators: `%s'\n", addr.toString().c_str());
|
||||
return true;
|
||||
}
|
||||
new_coordinators_addresses.insert(addr);
|
||||
newCoordinatorslist.push_back(addr.toString());
|
||||
}
|
||||
new_coordinators_addresses.insert(addr);
|
||||
newAddresslist.push_back(addr.toString());
|
||||
} catch (Error& e) {
|
||||
if (e.code() == error_code_connection_string_invalid) {
|
||||
fprintf(
|
||||
|
@ -116,12 +132,12 @@ ACTOR Future<bool> changeCoordinators(Reference<IDatabase> db, std::vector<Strin
|
|||
throw;
|
||||
}
|
||||
}
|
||||
std::string new_addresses_str = boost::algorithm::join(newAddresslist, ", ");
|
||||
tr->set(fdb_cli::coordinatorsProcessSpecialKey, new_addresses_str);
|
||||
std::string new_coordinators_str = boost::algorithm::join(newCoordinatorslist, ", ");
|
||||
tr->set(fdb_cli::coordinatorsProcessSpecialKey, new_coordinators_str);
|
||||
}
|
||||
wait(safeThreadFutureToFuture(tr->commit()));
|
||||
// commit should always fail here
|
||||
// if coordinators are changed, we should get commit_unknown() error
|
||||
// If the commit succeeds, the coordinators change and the commit will fail with commit_unknown_result().
|
||||
ASSERT(false);
|
||||
} catch (Error& e) {
|
||||
state Error err(e);
|
||||
|
|
|
@ -59,7 +59,7 @@ ACTOR Future<Void> includeLocalities(Reference<IDatabase> db,
|
|||
wait(safeThreadFutureToFuture(tr->commit()));
|
||||
return Void();
|
||||
} catch (Error& e) {
|
||||
TraceEvent("IncludeLocalitiesError").error(e, true);
|
||||
TraceEvent("IncludeLocalitiesError").errorUnsuppressed(e);
|
||||
wait(safeThreadFutureToFuture(tr->onError(e)));
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ ACTOR Future<Void> includeServers(Reference<IDatabase> db, std::vector<AddressEx
|
|||
wait(safeThreadFutureToFuture(tr->commit()));
|
||||
return Void();
|
||||
} catch (Error& e) {
|
||||
TraceEvent("IncludeServersError").error(e, true);
|
||||
TraceEvent("IncludeServersError").errorUnsuppressed(e);
|
||||
wait(safeThreadFutureToFuture(tr->onError(e)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
#include <cinttypes>
|
||||
|
||||
#include "boost/lexical_cast.hpp"
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "contrib/fmt-8.1.1/include/fmt/format.h"
|
||||
|
||||
#include "fdbcli/fdbcli.actor.h"
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "contrib/fmt-8.1.1/include/fmt/format.h"
|
||||
|
||||
#include "fdbcli/fdbcli.actor.h"
|
||||
|
||||
|
|
|
@ -705,12 +705,12 @@ void printStatus(StatusObjectReader statusObj,
|
|||
}
|
||||
}
|
||||
outputString += format(
|
||||
" %s log epoch: %ld begin: %ld end: %s, missing "
|
||||
" %s log epoch: %lld begin: %lld end: %s, missing "
|
||||
"log interfaces(id,address): %s\n",
|
||||
current ? "Current" : "Old",
|
||||
epoch,
|
||||
beginVersion,
|
||||
endVersion == invalidVersion ? "(unknown)" : format("%ld", endVersion).c_str(),
|
||||
endVersion == invalidVersion ? "(unknown)" : format("%lld", endVersion).c_str(),
|
||||
missing_log_interfaces.c_str());
|
||||
}
|
||||
}
|
||||
|
@ -810,6 +810,28 @@ void printStatus(StatusObjectReader statusObj,
|
|||
outputString = outputStringCache;
|
||||
outputString += "\n Unable to retrieve data status";
|
||||
}
|
||||
// Storage Wiggle section
|
||||
StatusObjectReader storageWigglerObj;
|
||||
std::string storageWigglerString;
|
||||
try {
|
||||
if (statusObjCluster.get("storage_wiggler", storageWigglerObj)) {
|
||||
int size = 0;
|
||||
if (storageWigglerObj.has("wiggle_server_addresses")) {
|
||||
storageWigglerString += "\n Wiggle server addresses-";
|
||||
for (auto& v : storageWigglerObj.obj().at("wiggle_server_addresses").get_array()) {
|
||||
storageWigglerString += " " + v.get_str();
|
||||
size += 1;
|
||||
}
|
||||
}
|
||||
storageWigglerString += "\n Wiggle server count - " + std::to_string(size);
|
||||
}
|
||||
} catch (std::runtime_error&) {
|
||||
storageWigglerString += "\n Unable to retrieve storage wiggler status";
|
||||
}
|
||||
if (storageWigglerString.size()) {
|
||||
outputString += "\n\nStorage wiggle:";
|
||||
outputString += storageWigglerString;
|
||||
}
|
||||
|
||||
// Operating space section
|
||||
outputString += "\n\nOperating space:";
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
*/
|
||||
|
||||
#include "boost/lexical_cast.hpp"
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "contrib/fmt-8.1.1/include/fmt/format.h"
|
||||
#include "fdbclient/ClusterConnectionFile.h"
|
||||
#include "fdbclient/NativeAPI.actor.h"
|
||||
#include "fdbclient/FDBTypes.h"
|
||||
|
@ -1015,9 +1015,9 @@ struct CLIOptions {
|
|||
} else {
|
||||
fprintf(stderr, "ERROR: Failed to set knob option '%s': %s\n", knobName.c_str(), e.what());
|
||||
TraceEvent(SevError, "FailedToSetKnob")
|
||||
.error(e)
|
||||
.detail("Knob", printable(knobName))
|
||||
.detail("Value", printable(knobValueString))
|
||||
.error(e);
|
||||
.detail("Value", printable(knobValueString));
|
||||
exit_code = FDB_EXIT_ERROR;
|
||||
}
|
||||
}
|
||||
|
@ -1158,7 +1158,6 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
|
|||
|
||||
state bool writeMode = false;
|
||||
|
||||
state std::string clusterConnectString;
|
||||
state std::map<Key, std::pair<Value, ClientLeaderRegInterface>> address_interface;
|
||||
|
||||
state FdbOptions globalOptions;
|
||||
|
@ -1172,6 +1171,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
|
|||
ClusterConnectionFile::lookupClusterFileName(opt.clusterFile);
|
||||
try {
|
||||
ccf = makeReference<ClusterConnectionFile>(resolvedClusterFile.first);
|
||||
wait(ccf->resolveHostnames());
|
||||
} catch (Error& e) {
|
||||
fprintf(stderr, "%s\n", ClusterConnectionFile::getErrorString(resolvedClusterFile, e).c_str());
|
||||
return 1;
|
||||
|
@ -1616,7 +1616,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
|
|||
} else {
|
||||
Version v = wait(makeInterruptable(
|
||||
safeThreadFutureToFuture(getTransaction(db, tr, options, intrans)->getReadVersion())));
|
||||
printf("%ld\n", v);
|
||||
fmt::print("{}\n", v);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
#include "fdbclient/CoordinationInterface.h"
|
||||
|
||||
// Determine public IP address by calling the first coordinator.
|
||||
IPAddress determinePublicIPAutomatically(ClusterConnectionString& ccs) {
|
||||
try {
|
||||
using namespace boost::asio;
|
||||
|
@ -35,6 +36,7 @@ IPAddress determinePublicIPAutomatically(ClusterConnectionString& ccs) {
|
|||
io_service ioService;
|
||||
ip::udp::socket socket(ioService);
|
||||
|
||||
ccs.resolveHostnamesBlocking();
|
||||
const auto& coordAddr = ccs.coordinators()[0];
|
||||
const auto boostIp = coordAddr.ip.isV6() ? ip::address(ip::address_v6(coordAddr.ip.toV6()))
|
||||
: ip::address(ip::address_v4(coordAddr.ip.toV4()));
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
#include <ostream>
|
||||
|
||||
// FIXME: Trim this down
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "contrib/fmt-8.1.1/include/fmt/format.h"
|
||||
#include "flow/Platform.actor.h"
|
||||
#include "fdbclient/AsyncTaskThread.h"
|
||||
#include "fdbclient/BackupContainer.h"
|
||||
|
@ -305,9 +305,9 @@ Reference<IBackupContainer> IBackupContainer::openContainer(const std::string& u
|
|||
throw;
|
||||
|
||||
TraceEvent m(SevWarn, "BackupContainer");
|
||||
m.error(e);
|
||||
m.detail("Description", "Invalid container specification. See help.");
|
||||
m.detail("URL", url);
|
||||
m.error(e);
|
||||
if (e.code() == error_code_backup_invalid_url)
|
||||
m.detail("LastOpenError", lastOpenError);
|
||||
|
||||
|
@ -360,10 +360,9 @@ ACTOR Future<std::vector<std::string>> listContainers_impl(std::string baseURL)
|
|||
throw;
|
||||
|
||||
TraceEvent m(SevWarn, "BackupContainer");
|
||||
|
||||
m.error(e);
|
||||
m.detail("Description", "Invalid backup container URL prefix. See help.");
|
||||
m.detail("URL", baseURL);
|
||||
m.error(e);
|
||||
if (e.code() == error_code_backup_invalid_url)
|
||||
m.detail("LastOpenError", IBackupContainer::lastOpenError);
|
||||
|
||||
|
|
|
@ -1149,8 +1149,8 @@ public:
|
|||
keyFile = _keyFile;
|
||||
} catch (Error& e) {
|
||||
TraceEvent(SevWarnAlways, "FailedToOpenEncryptionKeyFile")
|
||||
.detail("FileName", encryptionKeyFileName)
|
||||
.error(e);
|
||||
.error(e)
|
||||
.detail("FileName", encryptionKeyFileName);
|
||||
throw e;
|
||||
}
|
||||
int bytesRead = wait(keyFile->read(cipherKey->data(), cipherKey->size(), 0));
|
||||
|
@ -1377,8 +1377,8 @@ ACTOR static Future<KeyRange> getSnapshotFileKeyRange_impl(Reference<BackupConta
|
|||
e.code() == error_code_timed_out || e.code() == error_code_lookup_failed) {
|
||||
// blob http request failure, retry
|
||||
TraceEvent(SevWarnAlways, "BackupContainerGetSnapshotFileKeyRangeConnectionFailure")
|
||||
.detail("Retries", ++readFileRetries)
|
||||
.error(e);
|
||||
.error(e)
|
||||
.detail("Retries", ++readFileRetries);
|
||||
wait(delayJittered(0.1));
|
||||
} else {
|
||||
TraceEvent(SevError, "BackupContainerGetSnapshotFileKeyRangeUnexpectedError").error(e);
|
||||
|
@ -1549,9 +1549,9 @@ Reference<BackupContainerFileSystem> BackupContainerFileSystem::openContainerFS(
|
|||
throw;
|
||||
|
||||
TraceEvent m(SevWarn, "BackupContainer");
|
||||
m.error(e);
|
||||
m.detail("Description", "Invalid container specification. See help.");
|
||||
m.detail("URL", url);
|
||||
m.error(e);
|
||||
if (e.code() == error_code_backup_invalid_url)
|
||||
m.detail("LastOpenError", lastOpenError);
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
#define FDBCLIENT_BACKUP_CONTAINER_FILESYSTEM_H
|
||||
#pragma once
|
||||
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "contrib/fmt-8.1.1/include/fmt/format.h"
|
||||
#include "fdbclient/BackupContainer.h"
|
||||
#include "fdbclient/FDBTypes.h"
|
||||
#include "flow/Trace.h"
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "contrib/fmt-8.1.1/include/fmt/format.h"
|
||||
#include "flow/serialize.h"
|
||||
#include "fdbclient/BlobGranuleFiles.h"
|
||||
#include "fdbclient/SystemData.h" // for allKeys unit test - could remove
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "contrib/fmt-8.1.1/include/fmt/format.h"
|
||||
#include "fdbclient/AsyncFileS3BlobStore.actor.h"
|
||||
#include "fdbclient/BlobGranuleCommon.h"
|
||||
#include "fdbclient/BlobGranuleFiles.h"
|
||||
|
|
|
@ -114,12 +114,10 @@ struct RevokeBlobRangeRequest {
|
|||
};
|
||||
|
||||
/*
|
||||
* Continue: when a worker should continue handling a granule that was evaluated for a split
|
||||
* Reassign: when a new blob manager takes over, it sends Reassign requests to workers to redistribute granules
|
||||
* Normal: Neither continue nor reassign
|
||||
* Continue: Blob worker should continue handling a granule that was evaluated for a split
|
||||
* Normal: Blob worker should open the granule and start processing it
|
||||
*/
|
||||
// TODO REMOVE reassign now!
|
||||
enum AssignRequestType { Normal = 0, Continue = 1, Reassign = 2 };
|
||||
enum AssignRequestType { Normal = 0, Continue = 1 };
|
||||
|
||||
struct AssignBlobRangeRequest {
|
||||
constexpr static FileIdentifier file_identifier = 905381;
|
||||
|
@ -154,7 +152,6 @@ struct GranuleStatusReply : public ReplyPromiseStreamReply {
|
|||
int64_t seqno;
|
||||
UID granuleID;
|
||||
Version startVersion;
|
||||
Version latestVersion;
|
||||
|
||||
GranuleStatusReply() {}
|
||||
explicit GranuleStatusReply(KeyRange range,
|
||||
|
@ -163,10 +160,9 @@ struct GranuleStatusReply : public ReplyPromiseStreamReply {
|
|||
int64_t epoch,
|
||||
int64_t seqno,
|
||||
UID granuleID,
|
||||
Version startVersion,
|
||||
Version latestVersion)
|
||||
Version startVersion)
|
||||
: granuleRange(range), doSplit(doSplit), writeHotSplit(writeHotSplit), epoch(epoch), seqno(seqno),
|
||||
granuleID(granuleID), startVersion(startVersion), latestVersion(latestVersion) {}
|
||||
granuleID(granuleID), startVersion(startVersion) {}
|
||||
|
||||
int expectedSize() const { return sizeof(GranuleStatusReply) + granuleRange.expectedSize(); }
|
||||
|
||||
|
@ -181,8 +177,7 @@ struct GranuleStatusReply : public ReplyPromiseStreamReply {
|
|||
epoch,
|
||||
seqno,
|
||||
granuleID,
|
||||
startVersion,
|
||||
latestVersion);
|
||||
startVersion);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -138,6 +138,7 @@ set(FDBCLIENT_SRCS
|
|||
TagThrottle.actor.h
|
||||
TaskBucket.actor.cpp
|
||||
TaskBucket.h
|
||||
Tenant.h
|
||||
TestKnobCollection.cpp
|
||||
TestKnobCollection.h
|
||||
ThreadSafeTransaction.cpp
|
||||
|
|
|
@ -36,6 +36,7 @@ void ClientKnobs::initialize(Randomize randomize) {
|
|||
|
||||
init( SYSTEM_MONITOR_INTERVAL, 5.0 );
|
||||
init( NETWORK_BUSYNESS_MONITOR_INTERVAL, 1.0 );
|
||||
init( TSS_METRICS_LOGGING_INTERVAL, 120.0 ); // 2 minutes by default
|
||||
|
||||
init( FAILURE_MAX_DELAY, 5.0 );
|
||||
init( FAILURE_MIN_DELAY, 4.0 ); if( randomize && BUGGIFY ) FAILURE_MIN_DELAY = 1.0;
|
||||
|
@ -87,6 +88,8 @@ void ClientKnobs::initialize(Randomize randomize) {
|
|||
|
||||
init( LOCATION_CACHE_EVICTION_SIZE, 600000 );
|
||||
init( LOCATION_CACHE_EVICTION_SIZE_SIM, 10 ); if( randomize && BUGGIFY ) LOCATION_CACHE_EVICTION_SIZE_SIM = 3;
|
||||
init( LOCATION_CACHE_ENDPOINT_FAILURE_GRACE_PERIOD, 60 );
|
||||
init( LOCATION_CACHE_FAILED_ENDPOINT_RETRY_INTERVAL, 60 );
|
||||
|
||||
init( GET_RANGE_SHARD_LIMIT, 2 );
|
||||
init( WARM_RANGE_SHARD_LIMIT, 100 );
|
||||
|
@ -118,6 +121,12 @@ void ClientKnobs::initialize(Randomize randomize) {
|
|||
init( CORE_VERSIONSPERSECOND, 1e6 );
|
||||
init( LOG_RANGE_BLOCK_SIZE, CORE_VERSIONSPERSECOND );
|
||||
init( MUTATION_BLOCK_SIZE, 10000);
|
||||
init( MAX_VERSION_CACHE_LAG, 0.1 );
|
||||
init( MAX_PROXY_CONTACT_LAG, 0.2 );
|
||||
init( DEBUG_USE_GRV_CACHE_CHANCE, -1.0 ); // For 100% chance at 1.0, this means 0.0 is not 0%. We don't want the default to be 0.
|
||||
init( FORCE_GRV_CACHE_OFF, false );
|
||||
init( GRV_CACHE_RK_COOLDOWN, 60.0 );
|
||||
init( GRV_SUSTAINED_THROTTLING_THRESHOLD, 0.1 );
|
||||
|
||||
// TaskBucket
|
||||
init( TASKBUCKET_LOGGING_DELAY, 5.0 );
|
||||
|
|
|
@ -35,6 +35,7 @@ public:
|
|||
|
||||
double SYSTEM_MONITOR_INTERVAL;
|
||||
double NETWORK_BUSYNESS_MONITOR_INTERVAL; // The interval in which we should update the network busyness metric
|
||||
double TSS_METRICS_LOGGING_INTERVAL;
|
||||
|
||||
double FAILURE_MAX_DELAY;
|
||||
double FAILURE_MIN_DELAY;
|
||||
|
@ -87,6 +88,8 @@ public:
|
|||
// When locationCache in DatabaseContext gets to be this size, items will be evicted
|
||||
int LOCATION_CACHE_EVICTION_SIZE;
|
||||
int LOCATION_CACHE_EVICTION_SIZE_SIM;
|
||||
double LOCATION_CACHE_ENDPOINT_FAILURE_GRACE_PERIOD;
|
||||
double LOCATION_CACHE_FAILED_ENDPOINT_RETRY_INTERVAL;
|
||||
|
||||
int GET_RANGE_SHARD_LIMIT;
|
||||
int WARM_RANGE_SHARD_LIMIT;
|
||||
|
@ -121,6 +124,13 @@ public:
|
|||
int64_t CORE_VERSIONSPERSECOND; // This is defined within the server but used for knobs based on server value
|
||||
int LOG_RANGE_BLOCK_SIZE;
|
||||
int MUTATION_BLOCK_SIZE;
|
||||
double MAX_VERSION_CACHE_LAG; // The upper bound in seconds for OK amount of staleness when using a cached RV
|
||||
double MAX_PROXY_CONTACT_LAG; // The upper bound in seconds for how often we want a response from the GRV proxies
|
||||
double DEBUG_USE_GRV_CACHE_CHANCE; // Debug setting to change the chance for a regular GRV request to use the cache
|
||||
bool FORCE_GRV_CACHE_OFF; // Panic button to turn off cache. Holds priority over other options.
|
||||
double GRV_CACHE_RK_COOLDOWN; // Required number of seconds to pass after throttling to re-allow cache use
|
||||
double GRV_SUSTAINED_THROTTLING_THRESHOLD; // If ALL GRV requests have been throttled in the last number of seconds
|
||||
// specified here, ratekeeper is throttling and not a false positive
|
||||
|
||||
// Taskbucket
|
||||
double TASKBUCKET_LOGGING_DELAY;
|
||||
|
|
|
@ -116,6 +116,8 @@ struct ClientDBInfo {
|
|||
Optional<Value> forward;
|
||||
std::vector<VersionHistory> history;
|
||||
|
||||
TenantMode tenantMode;
|
||||
|
||||
ClientDBInfo() {}
|
||||
|
||||
bool operator==(ClientDBInfo const& r) const { return id == r.id; }
|
||||
|
@ -126,7 +128,7 @@ struct ClientDBInfo {
|
|||
if constexpr (!is_fb_function<Archive>) {
|
||||
ASSERT(ar.protocolVersion().isValid());
|
||||
}
|
||||
serializer(ar, grvProxies, commitProxies, id, forward, history);
|
||||
serializer(ar, grvProxies, commitProxies, id, forward, history, tenantMode);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -167,12 +169,16 @@ struct CommitTransactionRequest : TimedRequest {
|
|||
Optional<ClientTrCommitCostEstimation> commitCostEstimation;
|
||||
Optional<TagSet> tagSet;
|
||||
|
||||
CommitTransactionRequest() : CommitTransactionRequest(SpanID()) {}
|
||||
CommitTransactionRequest(SpanID const& context) : spanContext(context), flags(0) {}
|
||||
TenantInfo tenantInfo;
|
||||
|
||||
CommitTransactionRequest() : CommitTransactionRequest(TenantInfo(), SpanID()) {}
|
||||
CommitTransactionRequest(TenantInfo const& tenantInfo, SpanID const& context)
|
||||
: spanContext(context), flags(0), tenantInfo(tenantInfo) {}
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, transaction, reply, arena, flags, debugID, commitCostEstimation, tagSet, spanContext);
|
||||
serializer(
|
||||
ar, transaction, reply, arena, flags, debugID, commitCostEstimation, tagSet, spanContext, tenantInfo);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -195,6 +201,8 @@ struct GetReadVersionReply : public BasicLoadBalancedReply {
|
|||
bool locked;
|
||||
Optional<Value> metadataVersion;
|
||||
int64_t midShardSize = 0;
|
||||
bool rkDefaultThrottled = false;
|
||||
bool rkBatchThrottled = false;
|
||||
|
||||
TransactionTagMap<ClientTagThrottleLimits> tagThrottleInfo;
|
||||
|
||||
|
@ -208,7 +216,9 @@ struct GetReadVersionReply : public BasicLoadBalancedReply {
|
|||
locked,
|
||||
metadataVersion,
|
||||
tagThrottleInfo,
|
||||
midShardSize);
|
||||
midShardSize,
|
||||
rkDefaultThrottled,
|
||||
rkBatchThrottled);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -285,6 +295,7 @@ struct GetReadVersionRequest : TimedRequest {
|
|||
struct GetKeyServerLocationsReply {
|
||||
constexpr static FileIdentifier file_identifier = 10636023;
|
||||
Arena arena;
|
||||
TenantMapEntry tenantEntry;
|
||||
std::vector<std::pair<KeyRangeRef, std::vector<StorageServerInterface>>> results;
|
||||
|
||||
// if any storage servers in results have a TSS pair, that mapping is in here
|
||||
|
@ -292,7 +303,7 @@ struct GetKeyServerLocationsReply {
|
|||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, results, resultsTssMapping, arena);
|
||||
serializer(ar, results, resultsTssMapping, tenantEntry, arena);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -300,24 +311,34 @@ struct GetKeyServerLocationsRequest {
|
|||
constexpr static FileIdentifier file_identifier = 9144680;
|
||||
Arena arena;
|
||||
SpanID spanContext;
|
||||
Optional<TenantNameRef> tenant;
|
||||
KeyRef begin;
|
||||
Optional<KeyRef> end;
|
||||
int limit;
|
||||
bool reverse;
|
||||
ReplyPromise<GetKeyServerLocationsReply> reply;
|
||||
|
||||
GetKeyServerLocationsRequest() : limit(0), reverse(false) {}
|
||||
// This version is used to specify the minimum metadata version a proxy must have in order to declare that
|
||||
// a tenant is not present. If the metadata version is lower, the proxy must wait in case the tenant gets
|
||||
// created. If latestVersion is specified, then the proxy will wait until it is sure that it has received
|
||||
// updates from other proxies before answering.
|
||||
Version minTenantVersion;
|
||||
|
||||
GetKeyServerLocationsRequest() : limit(0), reverse(false), minTenantVersion(latestVersion) {}
|
||||
GetKeyServerLocationsRequest(SpanID spanContext,
|
||||
Optional<TenantNameRef> const& tenant,
|
||||
KeyRef const& begin,
|
||||
Optional<KeyRef> const& end,
|
||||
int limit,
|
||||
bool reverse,
|
||||
Version minTenantVersion,
|
||||
Arena const& arena)
|
||||
: arena(arena), spanContext(spanContext), begin(begin), end(end), limit(limit), reverse(reverse) {}
|
||||
: arena(arena), spanContext(spanContext), tenant(tenant), begin(begin), end(end), limit(limit), reverse(reverse),
|
||||
minTenantVersion(minTenantVersion) {}
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, begin, end, limit, reverse, reply, spanContext, arena);
|
||||
serializer(ar, begin, end, limit, reverse, reply, spanContext, tenant, minTenantVersion, arena);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ ConfigKey ConfigKeyRef::decodeKey(KeyRef const& key) {
|
|||
try {
|
||||
tuple = Tuple::unpack(key);
|
||||
} catch (Error& e) {
|
||||
TraceEvent(SevWarnAlways, "FailedToUnpackConfigKey").detail("Key", printable(key)).error(e);
|
||||
TraceEvent(SevWarnAlways, "FailedToUnpackConfigKey").error(e).detail("Key", printable(key));
|
||||
throw invalid_config_db_key();
|
||||
}
|
||||
if (tuple.size() != 2) {
|
||||
|
@ -96,7 +96,7 @@ public:
|
|||
|
||||
struct ToStringFunc {
|
||||
std::string operator()(int v) const { return format("int:%d", v); }
|
||||
std::string operator()(int64_t v) const { return format("int64_t:%ld", v); }
|
||||
std::string operator()(int64_t v) const { return format("int64_t:%lld", v); }
|
||||
std::string operator()(bool v) const { return format("bool:%d", v); }
|
||||
std::string operator()(ValueRef v) const { return "string:" + v.toString(); }
|
||||
std::string operator()(double v) const { return format("double:%lf", v); }
|
||||
|
|
|
@ -58,13 +58,28 @@ struct ClientLeaderRegInterface {
|
|||
// - There is no address present more than once
|
||||
class ClusterConnectionString {
|
||||
public:
|
||||
enum ConnectionStringStatus { RESOLVED, RESOLVING, UNRESOLVED };
|
||||
|
||||
ClusterConnectionString() {}
|
||||
ClusterConnectionString(const std::string& connStr);
|
||||
ClusterConnectionString(const std::vector<NetworkAddress>& coordinators, Key key);
|
||||
ClusterConnectionString(const std::vector<Hostname>& hosts, Key key);
|
||||
|
||||
ClusterConnectionString(const ClusterConnectionString& rhs) { operator=(rhs); }
|
||||
ClusterConnectionString& operator=(const ClusterConnectionString& rhs) {
|
||||
// Copy everything except AsyncTrigger resolveFinish.
|
||||
status = rhs.status;
|
||||
coords = rhs.coords;
|
||||
hostnames = rhs.hostnames;
|
||||
networkAddressToHostname = rhs.networkAddressToHostname;
|
||||
key = rhs.key;
|
||||
keyDesc = rhs.keyDesc;
|
||||
connectionString = rhs.connectionString;
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::vector<NetworkAddress> const& coordinators() const { return coords; }
|
||||
void addResolved(Hostname hostname, NetworkAddress address) {
|
||||
void addResolved(const Hostname& hostname, const NetworkAddress& address) {
|
||||
coords.push_back(address);
|
||||
networkAddressToHostname.emplace(address, hostname);
|
||||
}
|
||||
|
@ -78,16 +93,20 @@ public:
|
|||
// This one should only be used when resolving asynchronously is impossible. For all other cases, resolveHostnames()
|
||||
// should be preferred.
|
||||
void resolveHostnamesBlocking();
|
||||
void resetToUnresolved();
|
||||
// This function derives the member connectionString from the current key, coordinators and hostnames.
|
||||
void resetConnectionString();
|
||||
|
||||
bool hasUnresolvedHostnames = false;
|
||||
void resetToUnresolved();
|
||||
void parseKey(const std::string& key);
|
||||
|
||||
ConnectionStringStatus status = RESOLVED;
|
||||
AsyncTrigger resolveFinish;
|
||||
std::vector<NetworkAddress> coords;
|
||||
std::vector<Hostname> hostnames;
|
||||
std::unordered_map<NetworkAddress, Hostname> networkAddressToHostname;
|
||||
|
||||
private:
|
||||
void parseConnString();
|
||||
void parseKey(const std::string& key);
|
||||
std::unordered_map<NetworkAddress, Hostname> networkAddressToHostname;
|
||||
Key key, keyDesc;
|
||||
std::string connectionString;
|
||||
};
|
||||
|
@ -139,7 +158,7 @@ public:
|
|||
// Signals to the connection record that it was successfully used to connect to a cluster.
|
||||
void notifyConnected();
|
||||
|
||||
bool hasUnresolvedHostnames() const;
|
||||
ClusterConnectionString::ConnectionStringStatus connectionStringStatus() const;
|
||||
Future<Void> resolveHostnames();
|
||||
// This one should only be used when resolving asynchronously is impossible. For all other cases, resolveHostnames()
|
||||
// should be preferred.
|
||||
|
|
|
@ -2142,7 +2142,7 @@ struct StartFullBackupTaskFunc : TaskFuncBase {
|
|||
wait(tr->commit());
|
||||
break;
|
||||
} catch (Error& e) {
|
||||
TraceEvent("SetDestUidOrBeginVersionError").error(e, true);
|
||||
TraceEvent("SetDestUidOrBeginVersionError").errorUnsuppressed(e);
|
||||
wait(tr->onError(e));
|
||||
}
|
||||
}
|
||||
|
@ -2907,7 +2907,7 @@ public:
|
|||
TraceEvent("DBA_Abort").detail("CommitVersion", tr->getCommittedVersion());
|
||||
break;
|
||||
} catch (Error& e) {
|
||||
TraceEvent("DBA_AbortError").error(e, true);
|
||||
TraceEvent("DBA_AbortError").errorUnsuppressed(e);
|
||||
wait(tr->onError(e));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ void DatabaseConfiguration::resetInternal() {
|
|||
perpetualStorageWiggleLocality = "0";
|
||||
storageMigrationType = StorageMigrationType::DEFAULT;
|
||||
blobGranulesEnabled = false;
|
||||
tenantMode = TenantMode::DISABLED;
|
||||
}
|
||||
|
||||
int toInt(ValueRef const& v) {
|
||||
|
@ -210,7 +211,8 @@ bool DatabaseConfiguration::isValid() const {
|
|||
// We cannot specify regions with three_datacenter replication
|
||||
(perpetualStorageWiggleSpeed == 0 || perpetualStorageWiggleSpeed == 1) &&
|
||||
isValidPerpetualStorageWiggleLocality(perpetualStorageWiggleLocality) &&
|
||||
storageMigrationType != StorageMigrationType::UNSET)) {
|
||||
storageMigrationType != StorageMigrationType::UNSET && tenantMode >= TenantMode::DISABLED &&
|
||||
tenantMode < TenantMode::END)) {
|
||||
return false;
|
||||
}
|
||||
std::set<Key> dcIds;
|
||||
|
@ -404,6 +406,7 @@ StatusObject DatabaseConfiguration::toJSON(bool noPolicies) const {
|
|||
result["perpetual_storage_wiggle_locality"] = perpetualStorageWiggleLocality;
|
||||
result["storage_migration_type"] = storageMigrationType.toString();
|
||||
result["blob_granules_enabled"] = (int32_t)blobGranulesEnabled;
|
||||
result["tenant_mode"] = tenantMode.toString();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -627,6 +630,9 @@ bool DatabaseConfiguration::setInternal(KeyRef key, ValueRef value) {
|
|||
} else if (ck == LiteralStringRef("storage_migration_type")) {
|
||||
parse((&type), value);
|
||||
storageMigrationType = (StorageMigrationType::MigrationType)type;
|
||||
} else if (ck == LiteralStringRef("tenant_mode")) {
|
||||
parse((&type), value);
|
||||
tenantMode = (TenantMode::Mode)type;
|
||||
} else if (ck == LiteralStringRef("proxies")) {
|
||||
overwriteProxiesCount();
|
||||
} else if (ck == LiteralStringRef("blob_granules_enabled")) {
|
||||
|
|
|
@ -252,6 +252,7 @@ struct DatabaseConfiguration {
|
|||
|
||||
// Blob Granules
|
||||
bool blobGranulesEnabled;
|
||||
TenantMode tenantMode;
|
||||
|
||||
// Excluded servers (no state should be here)
|
||||
bool isExcludedServer(NetworkAddressList) const;
|
||||
|
|
|
@ -181,8 +181,6 @@ struct ChangeFeedStorageData : ReferenceCounted<ChangeFeedStorageData> {
|
|||
Promise<Void> destroyed;
|
||||
UID interfToken;
|
||||
|
||||
bool debug = false; // TODO REMOVE
|
||||
|
||||
~ChangeFeedStorageData() { destroyed.send(Void()); }
|
||||
};
|
||||
|
||||
|
@ -193,7 +191,6 @@ struct ChangeFeedData : ReferenceCounted<ChangeFeedData> {
|
|||
Version getVersion();
|
||||
Future<Void> whenAtLeast(Version version);
|
||||
|
||||
Key id; // TODO REMOVE eventually? for debugging
|
||||
NotifiedVersion lastReturnedVersion;
|
||||
std::vector<Reference<ChangeFeedStorageData>> storageData;
|
||||
AsyncVar<int> notAtLatest;
|
||||
|
@ -206,6 +203,11 @@ struct ChangeFeedData : ReferenceCounted<ChangeFeedData> {
|
|||
ChangeFeedData() : notAtLatest(1) {}
|
||||
};
|
||||
|
||||
struct EndpointFailureInfo {
|
||||
double startTime = 0;
|
||||
double lastRefreshTime = 0;
|
||||
};
|
||||
|
||||
class DatabaseContext : public ReferenceCounted<DatabaseContext>, public FastAllocated<DatabaseContext>, NonCopyable {
|
||||
public:
|
||||
static DatabaseContext* allocateOnForeignThread() {
|
||||
|
@ -249,6 +251,14 @@ public:
|
|||
void invalidateCache(const KeyRef&, Reverse isBackward = Reverse::False);
|
||||
void invalidateCache(const KeyRangeRef&);
|
||||
|
||||
// Records that `endpoint` is failed on a healthy server.
|
||||
void setFailedEndpointOnHealthyServer(const Endpoint& endpoint);
|
||||
|
||||
// Updates `endpoint` refresh time if the `endpoint` is a failed endpoint. If not, this does nothing.
|
||||
void updateFailedEndpointRefreshTime(const Endpoint& endpoint);
|
||||
Optional<EndpointFailureInfo> getEndpointFailureInfo(const Endpoint& endpoint);
|
||||
void clearFailedEndpointOnHealthyServer(const Endpoint& endpoint);
|
||||
|
||||
bool sampleReadTags() const;
|
||||
bool sampleOnCost(uint64_t cost) const;
|
||||
|
||||
|
@ -364,6 +374,7 @@ public:
|
|||
Future<Void> monitorTssInfoChange;
|
||||
Future<Void> tssMismatchHandler;
|
||||
PromiseStream<std::pair<UID, std::vector<DetailedTSSMismatch>>> tssMismatchStream;
|
||||
Future<Void> grvUpdateHandler;
|
||||
Reference<CommitProxyInfo> commitProxies;
|
||||
Reference<GrvProxyInfo> grvProxies;
|
||||
bool proxyProvisional; // Provisional commit proxy and grv proxy are used at the same time.
|
||||
|
@ -408,6 +419,7 @@ public:
|
|||
// Cache of location information
|
||||
int locationCacheSize;
|
||||
CoalescedKeyRangeMap<Reference<LocationInfo>> locationCache;
|
||||
std::unordered_map<Endpoint, EndpointFailureInfo> failedEndpointsOnHealthyServersInfo;
|
||||
|
||||
std::map<UID, StorageServerInfo*> server_interf;
|
||||
std::map<UID, BlobWorkerInterface> blobWorker_interf; // blob workers don't change endpoints for the same ID
|
||||
|
@ -479,6 +491,20 @@ public:
|
|||
int outstandingWatches;
|
||||
int maxOutstandingWatches;
|
||||
|
||||
// GRV Cache
|
||||
// Database-level read version cache storing the most recent successful GRV as well as the time it was requested.
|
||||
double lastGrvTime;
|
||||
Version cachedReadVersion;
|
||||
void updateCachedReadVersion(double t, Version v);
|
||||
Version getCachedReadVersion();
|
||||
double getLastGrvTime();
|
||||
double lastRkBatchThrottleTime;
|
||||
double lastRkDefaultThrottleTime;
|
||||
// Cached RVs can be updated through commits, and using cached RVs avoids the proxies altogether
|
||||
// Because our checks for ratekeeper throttling requires communication with the proxies,
|
||||
// we want to track the last time in order to periodically contact the proxy to check for throttling
|
||||
double lastProxyRequestTime;
|
||||
|
||||
int snapshotRywEnabled;
|
||||
|
||||
bool transactionTracingSample;
|
||||
|
|
|
@ -52,6 +52,10 @@ void KeySelectorRef::setKey(KeyRef const& key) {
|
|||
this->key = key;
|
||||
}
|
||||
|
||||
void KeySelectorRef::setKeyUnlimited(KeyRef const& key) {
|
||||
this->key = key;
|
||||
}
|
||||
|
||||
std::string KeySelectorRef::toString() const {
|
||||
if (offset > 0) {
|
||||
if (orEqual)
|
||||
|
|
|
@ -546,6 +546,7 @@ public:
|
|||
KeyRef getKey() const { return key; }
|
||||
|
||||
void setKey(KeyRef const& key);
|
||||
void setKeyUnlimited(KeyRef const& key);
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
|
@ -593,6 +594,11 @@ inline bool selectorInRange(KeySelectorRef const& sel, KeyRangeRef const& range)
|
|||
return sel.getKey() >= range.begin && (sel.isBackward() ? sel.getKey() <= range.end : sel.getKey() < range.end);
|
||||
}
|
||||
|
||||
template <>
|
||||
struct Traceable<KeySelectorRef> : std::true_type {
|
||||
static std::string toString(const KeySelectorRef& value) { return value.toString(); }
|
||||
};
|
||||
|
||||
template <class Val>
|
||||
struct KeyRangeWith : KeyRange {
|
||||
Val value;
|
||||
|
@ -1175,6 +1181,42 @@ struct StorageMigrationType {
|
|||
uint32_t type;
|
||||
};
|
||||
|
||||
struct TenantMode {
|
||||
// These enumerated values are stored in the database configuration, so can NEVER be changed. Only add new ones
|
||||
// just before END.
|
||||
// Note: OPTIONAL_TENANT is not named OPTIONAL because of a collision with a Windows macro.
|
||||
enum Mode { DISABLED = 0, OPTIONAL_TENANT = 1, REQUIRED = 2, END = 3 };
|
||||
|
||||
TenantMode() : mode(DISABLED) {}
|
||||
TenantMode(Mode mode) : mode(mode) {
|
||||
if ((uint32_t)mode >= END) {
|
||||
this->mode = DISABLED;
|
||||
}
|
||||
}
|
||||
operator Mode() const { return Mode(mode); }
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, mode);
|
||||
}
|
||||
|
||||
std::string toString() const {
|
||||
switch (mode) {
|
||||
case DISABLED:
|
||||
return "disabled";
|
||||
case OPTIONAL_TENANT:
|
||||
return "optional_experimental";
|
||||
case REQUIRED:
|
||||
return "required_experimental";
|
||||
default:
|
||||
ASSERT(false);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
uint32_t mode;
|
||||
};
|
||||
|
||||
inline bool isValidPerpetualStorageWiggleLocality(std::string locality) {
|
||||
int pos = locality.find(':');
|
||||
// locality should be either 0 or in the format '<non_empty_string>:<non_empty_string>'
|
||||
|
@ -1204,10 +1246,12 @@ struct ReadBlobGranuleContext {
|
|||
struct StorageMetadataType {
|
||||
constexpr static FileIdentifier file_identifier = 732123;
|
||||
// when the SS is initialized
|
||||
uint64_t createdTime; // comes from Platform::timer_int()
|
||||
uint64_t createdTime; // comes from currentTime()
|
||||
StorageMetadataType() : createdTime(0) {}
|
||||
StorageMetadataType(uint64_t t) : createdTime(t) {}
|
||||
|
||||
static uint64_t currentTime() { return g_network->timer() * 1e9; }
|
||||
|
||||
// To change this serialization, ProtocolVersion::StorageMetadata must be updated, and downgrades need
|
||||
// to be considered
|
||||
template <class Ar>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "contrib/fmt-8.1.1/include/fmt/format.h"
|
||||
#include "fdbclient/BackupAgent.actor.h"
|
||||
#include "fdbclient/BackupContainer.h"
|
||||
#include "fdbclient/DatabaseContext.h"
|
||||
|
@ -87,7 +87,7 @@ std::string secondsToTimeFormat(int64_t seconds) {
|
|||
else if (seconds >= 60)
|
||||
return format("%.2f minute(s)", seconds / 60.0);
|
||||
else
|
||||
return format("%ld second(s)", seconds);
|
||||
return format("%lld second(s)", seconds);
|
||||
}
|
||||
|
||||
const Key FileBackupAgent::keyLastRestorable = LiteralStringRef("last_restorable");
|
||||
|
@ -4407,9 +4407,9 @@ public:
|
|||
break;
|
||||
} catch (Error& e) {
|
||||
TraceEvent(numTries > 50 ? SevError : SevInfo, "FastRestoreToolSubmitRestoreRequestsMayFail")
|
||||
.error(e)
|
||||
.detail("Reason", "DB is not properly locked")
|
||||
.detail("ExpectedLockID", randomUID)
|
||||
.error(e);
|
||||
.detail("ExpectedLockID", randomUID);
|
||||
numTries++;
|
||||
wait(tr->onError(e));
|
||||
}
|
||||
|
@ -4443,8 +4443,8 @@ public:
|
|||
break;
|
||||
} catch (Error& e) {
|
||||
TraceEvent(numTries > 50 ? SevError : SevInfo, "FastRestoreToolSubmitRestoreRequestsRetry")
|
||||
.detail("RestoreIndex", restoreIndex)
|
||||
.error(e);
|
||||
.error(e)
|
||||
.detail("RestoreIndex", restoreIndex);
|
||||
numTries++;
|
||||
wait(tr->onError(e));
|
||||
}
|
||||
|
@ -5183,7 +5183,7 @@ public:
|
|||
else
|
||||
statusText += "The initial snapshot is still running.\n";
|
||||
|
||||
statusText += format("\nDetails:\n LogBytes written - %ld\n RangeBytes written - %ld\n "
|
||||
statusText += format("\nDetails:\n LogBytes written - %lld\n RangeBytes written - %lld\n "
|
||||
"Last complete log version and timestamp - %s, %s\n "
|
||||
"Last complete snapshot version and timestamp - %s, %s\n "
|
||||
"Current Snapshot start version and timestamp - %s, %s\n "
|
||||
|
@ -5800,9 +5800,9 @@ ACTOR static Future<Void> transformDatabaseContents(Database cx,
|
|||
break;
|
||||
} catch (Error& e) {
|
||||
TraceEvent("FastRestoreWorkloadTransformDatabaseContentsGetAllKeys")
|
||||
.error(e)
|
||||
.detail("Index", i)
|
||||
.detail("RestoreRange", restoreRanges[i])
|
||||
.error(e);
|
||||
.detail("RestoreRange", restoreRanges[i]);
|
||||
oldData = Standalone<VectorRef<KeyValueRef>>(); // clear the vector
|
||||
wait(tr.onError(e));
|
||||
}
|
||||
|
|
|
@ -448,6 +448,7 @@ ACTOR Future<Reference<HTTP::Response>> doRequest(Reference<IConnection> conn,
|
|||
err = http_bad_request_id();
|
||||
|
||||
TraceEvent(SevError, "HTTPRequestFailedIDMismatch")
|
||||
.error(err.get())
|
||||
.detail("DebugID", conn->getDebugID())
|
||||
.detail("RemoteAddress", conn->getPeerAddress())
|
||||
.detail("Verb", verb)
|
||||
|
@ -456,8 +457,7 @@ ACTOR Future<Reference<HTTP::Response>> doRequest(Reference<IConnection> conn,
|
|||
.detail("ResponseCode", r->code)
|
||||
.detail("ResponseContentLen", r->contentLen)
|
||||
.detail("RequestIDSent", requestID)
|
||||
.detail("RequestIDReceived", responseID)
|
||||
.error(err.get());
|
||||
.detail("RequestIDReceived", responseID);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -501,7 +501,7 @@ ACTOR Future<Reference<HTTP::Response>> doRequest(Reference<IConnection> conn,
|
|||
contentLen,
|
||||
total_sent);
|
||||
}
|
||||
event.error(e);
|
||||
event.errorUnsuppressed(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "contrib/fmt-8.1.1/include/fmt/format.h"
|
||||
#include "fdbclient/Knobs.h"
|
||||
#include "flow/Arena.h"
|
||||
#include "fdbclient/ClusterConnectionMemoryRecord.h"
|
||||
|
@ -169,7 +169,7 @@ std::map<std::string, std::string> configForToken(std::string const& mode) {
|
|||
} else if (value == "gradual") {
|
||||
type = StorageMigrationType::GRADUAL;
|
||||
} else {
|
||||
printf("Error: Only disabled|aggressive|gradual are valid for storage_migration_mode.\n");
|
||||
printf("Error: Only disabled|aggressive|gradual are valid for storage_migration_type.\n");
|
||||
return out;
|
||||
}
|
||||
out[p + key] = format("%d", type);
|
||||
|
@ -184,6 +184,21 @@ std::map<std::string, std::string> configForToken(std::string const& mode) {
|
|||
}
|
||||
out[p + key] = value;
|
||||
}
|
||||
|
||||
if (key == "tenant_mode") {
|
||||
TenantMode tenantMode;
|
||||
if (value == "disabled") {
|
||||
tenantMode = TenantMode::DISABLED;
|
||||
} else if (value == "optional_experimental") {
|
||||
tenantMode = TenantMode::OPTIONAL_TENANT;
|
||||
} else if (value == "required_experimental") {
|
||||
tenantMode = TenantMode::REQUIRED;
|
||||
} else {
|
||||
printf("Error: Only disabled|optional_experimental|required_experimental are valid for tenant_mode.\n");
|
||||
return out;
|
||||
}
|
||||
out[p + key] = format("%d", tenantMode);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
@ -782,7 +797,7 @@ ACTOR Future<std::vector<NetworkAddress>> getCoordinators(Database cx) {
|
|||
|
||||
ACTOR Future<Optional<CoordinatorsResult>> changeQuorumChecker(Transaction* tr,
|
||||
Reference<IQuorumChange> change,
|
||||
std::vector<NetworkAddress>* desiredCoordinators) {
|
||||
ClusterConnectionString* conn) {
|
||||
tr->setOption(FDBTransactionOptions::LOCK_AWARE);
|
||||
tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS);
|
||||
tr->setOption(FDBTransactionOptions::USE_PROVISIONAL_PROXIES);
|
||||
|
@ -793,44 +808,47 @@ ACTOR Future<Optional<CoordinatorsResult>> changeQuorumChecker(Transaction* tr,
|
|||
return CoordinatorsResult::BAD_DATABASE_STATE; // Someone deleted this key entirely?
|
||||
|
||||
state ClusterConnectionString old(currentKey.get().toString());
|
||||
wait(old.resolveHostnames());
|
||||
if (tr->getDatabase()->getConnectionRecord() &&
|
||||
old.clusterKeyName().toString() !=
|
||||
tr->getDatabase()->getConnectionRecord()->getConnectionString().clusterKeyName())
|
||||
return CoordinatorsResult::BAD_DATABASE_STATE; // Someone changed the "name" of the database??
|
||||
|
||||
state CoordinatorsResult result = CoordinatorsResult::SUCCESS;
|
||||
if (!desiredCoordinators->size()) {
|
||||
std::vector<NetworkAddress> _desiredCoordinators = wait(change->getDesiredCoordinators(
|
||||
if (!conn->coords.size()) {
|
||||
std::vector<NetworkAddress> desiredCoordinatorAddresses = wait(change->getDesiredCoordinators(
|
||||
tr,
|
||||
old.coordinators(),
|
||||
Reference<ClusterConnectionMemoryRecord>(new ClusterConnectionMemoryRecord(old)),
|
||||
result));
|
||||
*desiredCoordinators = _desiredCoordinators;
|
||||
conn->coords = desiredCoordinatorAddresses;
|
||||
}
|
||||
|
||||
if (result != CoordinatorsResult::SUCCESS)
|
||||
return result;
|
||||
|
||||
if (!desiredCoordinators->size())
|
||||
if (!conn->coordinators().size())
|
||||
return CoordinatorsResult::INVALID_NETWORK_ADDRESSES;
|
||||
|
||||
std::sort(desiredCoordinators->begin(), desiredCoordinators->end());
|
||||
std::sort(conn->coords.begin(), conn->coords.end());
|
||||
std::sort(conn->hostnames.begin(), conn->hostnames.end());
|
||||
|
||||
std::string newName = change->getDesiredClusterKeyName();
|
||||
if (newName.empty())
|
||||
newName = old.clusterKeyName().toString();
|
||||
|
||||
if (old.coordinators() == *desiredCoordinators && old.clusterKeyName() == newName)
|
||||
if (old.coordinators() == conn->coordinators() && old.clusterKeyName() == newName)
|
||||
return CoordinatorsResult::SAME_NETWORK_ADDRESSES;
|
||||
|
||||
state ClusterConnectionString conn(*desiredCoordinators,
|
||||
StringRef(newName + ':' + deterministicRandom()->randomAlphaNumeric(32)));
|
||||
std::string key(newName + ':' + deterministicRandom()->randomAlphaNumeric(32));
|
||||
conn->parseKey(key);
|
||||
conn->resetConnectionString();
|
||||
|
||||
if (g_network->isSimulated()) {
|
||||
int i = 0;
|
||||
int protectedCount = 0;
|
||||
while ((protectedCount < ((desiredCoordinators->size() / 2) + 1)) && (i < desiredCoordinators->size())) {
|
||||
auto process = g_simulator.getProcessByAddress((*desiredCoordinators)[i]);
|
||||
while ((protectedCount < ((conn->coordinators().size() / 2) + 1)) && (i < conn->coordinators().size())) {
|
||||
auto process = g_simulator.getProcessByAddress(conn->coordinators()[i]);
|
||||
auto addresses = process->addresses;
|
||||
|
||||
if (!process->isReliable()) {
|
||||
|
@ -842,14 +860,14 @@ ACTOR Future<Optional<CoordinatorsResult>> changeQuorumChecker(Transaction* tr,
|
|||
if (addresses.secondaryAddress.present()) {
|
||||
g_simulator.protectedAddresses.insert(process->addresses.secondaryAddress.get());
|
||||
}
|
||||
TraceEvent("ProtectCoordinator").detail("Address", (*desiredCoordinators)[i]).backtrace();
|
||||
TraceEvent("ProtectCoordinator").detail("Address", conn->coordinators()[i]).backtrace();
|
||||
protectedCount++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Future<Optional<LeaderInfo>>> leaderServers;
|
||||
ClientCoordinators coord(Reference<ClusterConnectionMemoryRecord>(new ClusterConnectionMemoryRecord(conn)));
|
||||
ClientCoordinators coord(Reference<ClusterConnectionMemoryRecord>(new ClusterConnectionMemoryRecord(*conn)));
|
||||
|
||||
leaderServers.reserve(coord.clientLeaderServers.size());
|
||||
for (int i = 0; i < coord.clientLeaderServers.size(); i++)
|
||||
|
@ -861,7 +879,7 @@ ACTOR Future<Optional<CoordinatorsResult>> changeQuorumChecker(Transaction* tr,
|
|||
when(wait(waitForAll(leaderServers))) {}
|
||||
when(wait(delay(5.0))) { return CoordinatorsResult::COORDINATOR_UNREACHABLE; }
|
||||
}
|
||||
tr->set(coordinatorsKey, conn.toString());
|
||||
tr->set(coordinatorsKey, conn->toString());
|
||||
return Optional<CoordinatorsResult>();
|
||||
}
|
||||
|
||||
|
@ -1283,7 +1301,7 @@ ACTOR Future<Void> excludeServers(Database cx, std::vector<AddressExclusion> ser
|
|||
wait(ryw.commit());
|
||||
return Void();
|
||||
} catch (Error& e) {
|
||||
TraceEvent("ExcludeServersError").error(e, true);
|
||||
TraceEvent("ExcludeServersError").errorUnsuppressed(e);
|
||||
wait(ryw.onError(e));
|
||||
}
|
||||
}
|
||||
|
@ -1295,7 +1313,7 @@ ACTOR Future<Void> excludeServers(Database cx, std::vector<AddressExclusion> ser
|
|||
wait(tr.commit());
|
||||
return Void();
|
||||
} catch (Error& e) {
|
||||
TraceEvent("ExcludeServersError").error(e, true);
|
||||
TraceEvent("ExcludeServersError").errorUnsuppressed(e);
|
||||
wait(tr.onError(e));
|
||||
}
|
||||
}
|
||||
|
@ -1346,7 +1364,7 @@ ACTOR Future<Void> excludeLocalities(Database cx, std::unordered_set<std::string
|
|||
wait(ryw.commit());
|
||||
return Void();
|
||||
} catch (Error& e) {
|
||||
TraceEvent("ExcludeLocalitiesError").error(e, true);
|
||||
TraceEvent("ExcludeLocalitiesError").errorUnsuppressed(e);
|
||||
wait(ryw.onError(e));
|
||||
}
|
||||
}
|
||||
|
@ -1358,7 +1376,7 @@ ACTOR Future<Void> excludeLocalities(Database cx, std::unordered_set<std::string
|
|||
wait(tr.commit());
|
||||
return Void();
|
||||
} catch (Error& e) {
|
||||
TraceEvent("ExcludeLocalitiesError").error(e, true);
|
||||
TraceEvent("ExcludeLocalitiesError").errorUnsuppressed(e);
|
||||
wait(tr.onError(e));
|
||||
}
|
||||
}
|
||||
|
@ -1375,9 +1393,9 @@ ACTOR Future<Void> includeServers(Database cx, std::vector<AddressExclusion> ser
|
|||
for (auto& s : servers) {
|
||||
if (!s.isValid()) {
|
||||
if (failed) {
|
||||
ryw.clear(SpecialKeySpace::getManamentApiCommandRange("failed"));
|
||||
ryw.clear(SpecialKeySpace::getManagementApiCommandRange("failed"));
|
||||
} else {
|
||||
ryw.clear(SpecialKeySpace::getManamentApiCommandRange("exclude"));
|
||||
ryw.clear(SpecialKeySpace::getManagementApiCommandRange("exclude"));
|
||||
}
|
||||
} else {
|
||||
Key addr =
|
||||
|
@ -1402,7 +1420,7 @@ ACTOR Future<Void> includeServers(Database cx, std::vector<AddressExclusion> ser
|
|||
wait(ryw.commit());
|
||||
return Void();
|
||||
} catch (Error& e) {
|
||||
TraceEvent("IncludeServersError").error(e, true);
|
||||
TraceEvent("IncludeServersError").errorUnsuppressed(e);
|
||||
wait(ryw.onError(e));
|
||||
}
|
||||
}
|
||||
|
@ -1459,7 +1477,7 @@ ACTOR Future<Void> includeServers(Database cx, std::vector<AddressExclusion> ser
|
|||
wait(tr.commit());
|
||||
return Void();
|
||||
} catch (Error& e) {
|
||||
TraceEvent("IncludeServersError").error(e, true);
|
||||
TraceEvent("IncludeServersError").errorUnsuppressed(e);
|
||||
wait(tr.onError(e));
|
||||
}
|
||||
}
|
||||
|
@ -1477,9 +1495,9 @@ ACTOR Future<Void> includeLocalities(Database cx, std::vector<std::string> local
|
|||
ryw.setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES);
|
||||
if (includeAll) {
|
||||
if (failed) {
|
||||
ryw.clear(SpecialKeySpace::getManamentApiCommandRange("failedlocality"));
|
||||
ryw.clear(SpecialKeySpace::getManagementApiCommandRange("failedlocality"));
|
||||
} else {
|
||||
ryw.clear(SpecialKeySpace::getManamentApiCommandRange("excludedlocality"));
|
||||
ryw.clear(SpecialKeySpace::getManagementApiCommandRange("excludedlocality"));
|
||||
}
|
||||
} else {
|
||||
for (const auto& l : localities) {
|
||||
|
@ -1497,7 +1515,7 @@ ACTOR Future<Void> includeLocalities(Database cx, std::vector<std::string> local
|
|||
wait(ryw.commit());
|
||||
return Void();
|
||||
} catch (Error& e) {
|
||||
TraceEvent("IncludeLocalitiesError").error(e, true);
|
||||
TraceEvent("IncludeLocalitiesError").errorUnsuppressed(e);
|
||||
wait(ryw.onError(e));
|
||||
}
|
||||
}
|
||||
|
@ -1545,7 +1563,7 @@ ACTOR Future<Void> includeLocalities(Database cx, std::vector<std::string> local
|
|||
wait(tr.commit());
|
||||
return Void();
|
||||
} catch (Error& e) {
|
||||
TraceEvent("IncludeLocalitiesError").error(e, true);
|
||||
TraceEvent("IncludeLocalitiesError").errorUnsuppressed(e);
|
||||
wait(tr.onError(e));
|
||||
}
|
||||
}
|
||||
|
@ -1917,7 +1935,7 @@ ACTOR Future<Void> mgmtSnapCreate(Database cx, Standalone<StringRef> snapCmd, UI
|
|||
TraceEvent("SnapCreateSucceeded").detail("snapUID", snapUID);
|
||||
return Void();
|
||||
} catch (Error& e) {
|
||||
TraceEvent(SevWarn, "SnapCreateFailed").detail("snapUID", snapUID).error(e);
|
||||
TraceEvent(SevWarn, "SnapCreateFailed").error(e).detail("snapUID", snapUID);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
@ -2208,7 +2226,7 @@ ACTOR Future<Void> advanceVersion(Database cx, Version v) {
|
|||
tr.set(minRequiredCommitVersionKey, BinaryWriter::toValue(v + 1, Unversioned()));
|
||||
wait(tr.commit());
|
||||
} else {
|
||||
printf("Current read version is %ld\n", rv);
|
||||
fmt::print("Current read version is {}\n", rv);
|
||||
return Void();
|
||||
}
|
||||
} catch (Error& e) {
|
||||
|
|
|
@ -56,7 +56,7 @@ struct IQuorumChange : ReferenceCounted<IQuorumChange> {
|
|||
// Change to use the given set of coordination servers
|
||||
ACTOR Future<Optional<CoordinatorsResult>> changeQuorumChecker(Transaction* tr,
|
||||
Reference<IQuorumChange> change,
|
||||
std::vector<NetworkAddress>* desiredCoordinators);
|
||||
ClusterConnectionString* conn);
|
||||
ACTOR Future<CoordinatorsResult> changeQuorum(Database cx, Reference<IQuorumChange> change);
|
||||
Reference<IQuorumChange> autoQuorumChange(int desired = -1);
|
||||
Reference<IQuorumChange> noQuorumChange();
|
||||
|
|
|
@ -77,8 +77,8 @@ void IClusterConnectionRecord::setPersisted() {
|
|||
connectionStringNeedsPersisted = false;
|
||||
}
|
||||
|
||||
bool IClusterConnectionRecord::hasUnresolvedHostnames() const {
|
||||
return cs.hasUnresolvedHostnames;
|
||||
ClusterConnectionString::ConnectionStringStatus IClusterConnectionRecord::connectionStringStatus() const {
|
||||
return cs.status;
|
||||
}
|
||||
|
||||
Future<Void> IClusterConnectionRecord::resolveHostnames() {
|
||||
|
@ -98,39 +98,56 @@ std::string ClusterConnectionString::getErrorString(std::string const& source, E
|
|||
}
|
||||
|
||||
ACTOR Future<Void> resolveHostnamesImpl(ClusterConnectionString* self) {
|
||||
std::vector<Future<Void>> fs;
|
||||
for (auto const& hostName : self->hostnames) {
|
||||
fs.push_back(map(INetworkConnections::net()->resolveTCPEndpoint(hostName.host, hostName.service),
|
||||
[=](std::vector<NetworkAddress> const& addresses) -> Void {
|
||||
NetworkAddress addr = addresses[deterministicRandom()->randomInt(0, addresses.size())];
|
||||
addr.flags = 0; // Reset the parsed address to public
|
||||
addr.fromHostname = NetworkAddressFromHostname::True;
|
||||
if (hostName.isTLS) {
|
||||
addr.flags |= NetworkAddress::FLAG_TLS;
|
||||
}
|
||||
self->addResolved(hostName, addr);
|
||||
return Void();
|
||||
}));
|
||||
loop {
|
||||
if (self->status == ClusterConnectionString::UNRESOLVED) {
|
||||
self->status = ClusterConnectionString::RESOLVING;
|
||||
std::vector<Future<Void>> fs;
|
||||
for (auto const& hostname : self->hostnames) {
|
||||
fs.push_back(map(INetworkConnections::net()->resolveTCPEndpoint(hostname.host, hostname.service),
|
||||
[=](std::vector<NetworkAddress> const& addresses) -> Void {
|
||||
NetworkAddress address =
|
||||
addresses[deterministicRandom()->randomInt(0, addresses.size())];
|
||||
address.flags = 0; // Reset the parsed address to public
|
||||
address.fromHostname = NetworkAddressFromHostname::True;
|
||||
if (hostname.isTLS) {
|
||||
address.flags |= NetworkAddress::FLAG_TLS;
|
||||
}
|
||||
self->addResolved(hostname, address);
|
||||
return Void();
|
||||
}));
|
||||
}
|
||||
wait(waitForAll(fs));
|
||||
std::sort(self->coords.begin(), self->coords.end());
|
||||
if (std::unique(self->coords.begin(), self->coords.end()) != self->coords.end()) {
|
||||
self->status = ClusterConnectionString::UNRESOLVED;
|
||||
self->resolveFinish.trigger();
|
||||
throw connection_string_invalid();
|
||||
}
|
||||
self->status = ClusterConnectionString::RESOLVED;
|
||||
self->resolveFinish.trigger();
|
||||
break;
|
||||
} else if (self->status == ClusterConnectionString::RESOLVING) {
|
||||
wait(self->resolveFinish.onTrigger());
|
||||
if (self->status == ClusterConnectionString::RESOLVED) {
|
||||
break;
|
||||
}
|
||||
// Otherwise, this means other threads failed on resolve, so here we go back to the loop and try to resolve
|
||||
// again.
|
||||
} else {
|
||||
// status is RESOLVED, nothing to do.
|
||||
break;
|
||||
}
|
||||
}
|
||||
wait(waitForAll(fs));
|
||||
std::sort(self->coords.begin(), self->coords.end());
|
||||
if (std::unique(self->coords.begin(), self->coords.end()) != self->coords.end()) {
|
||||
throw connection_string_invalid();
|
||||
}
|
||||
self->hasUnresolvedHostnames = false;
|
||||
return Void();
|
||||
}
|
||||
|
||||
Future<Void> ClusterConnectionString::resolveHostnames() {
|
||||
if (!hasUnresolvedHostnames) {
|
||||
return Void();
|
||||
} else {
|
||||
return resolveHostnamesImpl(this);
|
||||
}
|
||||
return resolveHostnamesImpl(this);
|
||||
}
|
||||
|
||||
void ClusterConnectionString::resolveHostnamesBlocking() {
|
||||
if (hasUnresolvedHostnames) {
|
||||
if (status != RESOLVED) {
|
||||
status = RESOLVING;
|
||||
for (auto const& hostname : hostnames) {
|
||||
std::vector<NetworkAddress> addresses =
|
||||
INetworkConnections::net()->resolveTCPEndpointBlocking(hostname.host, hostname.service);
|
||||
|
@ -140,14 +157,14 @@ void ClusterConnectionString::resolveHostnamesBlocking() {
|
|||
if (hostname.isTLS) {
|
||||
address.flags |= NetworkAddress::FLAG_TLS;
|
||||
}
|
||||
coords.push_back(address);
|
||||
networkAddressToHostname.emplace(address, hostname);
|
||||
addResolved(hostname, address);
|
||||
}
|
||||
std::sort(coords.begin(), coords.end());
|
||||
if (std::unique(coords.begin(), coords.end()) != coords.end()) {
|
||||
status = UNRESOLVED;
|
||||
throw connection_string_invalid();
|
||||
}
|
||||
hasUnresolvedHostnames = false;
|
||||
status = RESOLVED;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,11 +173,15 @@ void ClusterConnectionString::resetToUnresolved() {
|
|||
coords.clear();
|
||||
hostnames.clear();
|
||||
networkAddressToHostname.clear();
|
||||
hasUnresolvedHostnames = true;
|
||||
status = UNRESOLVED;
|
||||
parseConnString();
|
||||
}
|
||||
}
|
||||
|
||||
void ClusterConnectionString::resetConnectionString() {
|
||||
connectionString = toString();
|
||||
}
|
||||
|
||||
void ClusterConnectionString::parseConnString() {
|
||||
// Split on '@' into key@addrs
|
||||
int pAt = connectionString.find_first_of('@');
|
||||
|
@ -184,7 +205,9 @@ void ClusterConnectionString::parseConnString() {
|
|||
}
|
||||
p = pComma + 1;
|
||||
}
|
||||
hasUnresolvedHostnames = hostnames.size() > 0;
|
||||
if (hostnames.size() > 0) {
|
||||
status = UNRESOLVED;
|
||||
}
|
||||
ASSERT((coords.size() + hostnames.size()) > 0);
|
||||
|
||||
std::sort(coords.begin(), coords.end());
|
||||
|
@ -256,7 +279,7 @@ TEST_CASE("/fdbclient/MonitorLeader/parseConnectionString/hostnames") {
|
|||
{
|
||||
input = "asdf:2345@localhost:1234";
|
||||
ClusterConnectionString cs(input);
|
||||
ASSERT(cs.hasUnresolvedHostnames);
|
||||
ASSERT(cs.status == ClusterConnectionString::UNRESOLVED);
|
||||
ASSERT(cs.hostnames.size() == 1);
|
||||
ASSERT(input == cs.toString());
|
||||
}
|
||||
|
@ -264,7 +287,7 @@ TEST_CASE("/fdbclient/MonitorLeader/parseConnectionString/hostnames") {
|
|||
{
|
||||
input = "0xxdeadbeef:100100100@localhost:34534,host-name:23443";
|
||||
ClusterConnectionString cs(input);
|
||||
ASSERT(cs.hasUnresolvedHostnames);
|
||||
ASSERT(cs.status == ClusterConnectionString::UNRESOLVED);
|
||||
ASSERT(cs.hostnames.size() == 2);
|
||||
ASSERT(input == cs.toString());
|
||||
}
|
||||
|
@ -277,7 +300,7 @@ TEST_CASE("/fdbclient/MonitorLeader/parseConnectionString/hostnames") {
|
|||
commented += "# asdfasdf ##";
|
||||
|
||||
ClusterConnectionString cs(commented);
|
||||
ASSERT(cs.hasUnresolvedHostnames);
|
||||
ASSERT(cs.status == ClusterConnectionString::UNRESOLVED);
|
||||
ASSERT(cs.hostnames.size() == 2);
|
||||
ASSERT(input == cs.toString());
|
||||
}
|
||||
|
@ -290,7 +313,7 @@ TEST_CASE("/fdbclient/MonitorLeader/parseConnectionString/hostnames") {
|
|||
commented += "# asdfasdf ##";
|
||||
|
||||
ClusterConnectionString cs(commented);
|
||||
ASSERT(cs.hasUnresolvedHostnames);
|
||||
ASSERT(cs.status == ClusterConnectionString::UNRESOLVED);
|
||||
ASSERT(cs.hostnames.size() == 2);
|
||||
ASSERT(input == cs.toString());
|
||||
}
|
||||
|
@ -314,16 +337,16 @@ TEST_CASE("/fdbclient/MonitorLeader/ConnectionString") {
|
|||
INetworkConnections::net()->addMockTCPEndpoint(hn2, port2, { address2 });
|
||||
|
||||
state ClusterConnectionString cs(hostnames, LiteralStringRef("TestCluster:0"));
|
||||
ASSERT(cs.hasUnresolvedHostnames);
|
||||
ASSERT(cs.status == ClusterConnectionString::UNRESOLVED);
|
||||
ASSERT(cs.hostnames.size() == 2);
|
||||
ASSERT(cs.coordinators().size() == 0);
|
||||
wait(cs.resolveHostnames());
|
||||
ASSERT(!cs.hasUnresolvedHostnames);
|
||||
ASSERT(cs.status == ClusterConnectionString::RESOLVED);
|
||||
ASSERT(cs.hostnames.size() == 2);
|
||||
ASSERT(cs.coordinators().size() == 2);
|
||||
ASSERT(cs.toString() == connectionString);
|
||||
cs.resetToUnresolved();
|
||||
ASSERT(cs.hasUnresolvedHostnames);
|
||||
ASSERT(cs.status == ClusterConnectionString::UNRESOLVED);
|
||||
ASSERT(cs.hostnames.size() == 2);
|
||||
ASSERT(cs.coordinators().size() == 0);
|
||||
ASSERT(cs.toString() == connectionString);
|
||||
|
@ -422,29 +445,17 @@ TEST_CASE("/fdbclient/MonitorLeader/parseConnectionString/fuzz") {
|
|||
}
|
||||
|
||||
ClusterConnectionString::ClusterConnectionString(const std::vector<NetworkAddress>& servers, Key key)
|
||||
: coords(servers) {
|
||||
: status(RESOLVED), coords(servers) {
|
||||
std::string keyString = key.toString();
|
||||
parseKey(keyString);
|
||||
connectionString = keyString + "@";
|
||||
for (int i = 0; i < coords.size(); i++) {
|
||||
if (i) {
|
||||
connectionString += ',';
|
||||
}
|
||||
connectionString += coords[i].toString();
|
||||
}
|
||||
resetConnectionString();
|
||||
}
|
||||
|
||||
ClusterConnectionString::ClusterConnectionString(const std::vector<Hostname>& hosts, Key key)
|
||||
: hasUnresolvedHostnames(true), hostnames(hosts) {
|
||||
: status(UNRESOLVED), hostnames(hosts) {
|
||||
std::string keyString = key.toString();
|
||||
parseKey(keyString);
|
||||
connectionString = keyString + "@";
|
||||
for (int i = 0; i < hostnames.size(); i++) {
|
||||
if (i) {
|
||||
connectionString += ',';
|
||||
}
|
||||
connectionString += hostnames[i].toString();
|
||||
}
|
||||
resetConnectionString();
|
||||
}
|
||||
|
||||
void ClusterConnectionString::parseKey(const std::string& key) {
|
||||
|
@ -497,6 +508,7 @@ std::string ClusterConnectionString::toString() const {
|
|||
}
|
||||
|
||||
ClientCoordinators::ClientCoordinators(Reference<IClusterConnectionRecord> ccr) : ccr(ccr) {
|
||||
ASSERT(ccr->connectionStringStatus() == ClusterConnectionString::RESOLVED);
|
||||
ClusterConnectionString cs = ccr->getConnectionString();
|
||||
for (auto s = cs.coordinators().begin(); s != cs.coordinators().end(); ++s)
|
||||
clientLeaderServers.push_back(ClientLeaderRegInterface(*s));
|
||||
|
@ -525,15 +537,44 @@ ClientLeaderRegInterface::ClientLeaderRegInterface(INetwork* local) {
|
|||
|
||||
// Nominee is the worker among all workers that are considered as leader by one coordinator
|
||||
// This function contacts a coordinator coord to ask who is its nominee.
|
||||
// Note: for coordinators whose NetworkAddress is parsed out of a hostname, a connection failure will cause this actor
|
||||
// to throw `coordinators_changed()` error
|
||||
ACTOR Future<Void> monitorNominee(Key key,
|
||||
ClientLeaderRegInterface coord,
|
||||
AsyncTrigger* nomineeChange,
|
||||
Optional<LeaderInfo>* info) {
|
||||
Optional<LeaderInfo>* info,
|
||||
Optional<Hostname> hostname = Optional<Hostname>()) {
|
||||
loop {
|
||||
state Optional<LeaderInfo> li =
|
||||
wait(retryBrokenPromise(coord.getLeader,
|
||||
GetLeaderRequest(key, info->present() ? info->get().changeID : UID()),
|
||||
TaskPriority::CoordinationReply));
|
||||
state Optional<LeaderInfo> li;
|
||||
|
||||
if (coord.getLeader.getEndpoint().getPrimaryAddress().fromHostname) {
|
||||
state ErrorOr<Optional<LeaderInfo>> rep =
|
||||
wait(coord.getLeader.tryGetReply(GetLeaderRequest(key, info->present() ? info->get().changeID : UID()),
|
||||
TaskPriority::CoordinationReply));
|
||||
if (rep.isError()) {
|
||||
// Connecting to nominee failed, most likely due to connection failed.
|
||||
TraceEvent("MonitorNomineeError")
|
||||
.error(rep.getError())
|
||||
.detail("Hostname", hostname.present() ? hostname.get().toString() : "UnknownHostname")
|
||||
.detail("OldAddr", coord.getLeader.getEndpoint().getPrimaryAddress().toString());
|
||||
if (rep.getError().code() == error_code_request_maybe_delivered) {
|
||||
// 50 milliseconds delay to prevent tight resolving loop due to outdated DNS cache
|
||||
wait(delay(0.05));
|
||||
throw coordinators_changed();
|
||||
} else {
|
||||
throw rep.getError();
|
||||
}
|
||||
} else if (rep.present()) {
|
||||
li = rep.get();
|
||||
}
|
||||
} else {
|
||||
Optional<LeaderInfo> tmp =
|
||||
wait(retryBrokenPromise(coord.getLeader,
|
||||
GetLeaderRequest(key, info->present() ? info->get().changeID : UID()),
|
||||
TaskPriority::CoordinationReply));
|
||||
li = tmp;
|
||||
}
|
||||
|
||||
wait(Future<Void>(Void())); // Make sure we weren't cancelled
|
||||
|
||||
TraceEvent("GetLeaderReply")
|
||||
|
@ -608,53 +649,74 @@ Optional<std::pair<LeaderInfo, bool>> getLeader(const std::vector<Optional<Leade
|
|||
ACTOR Future<MonitorLeaderInfo> monitorLeaderOneGeneration(Reference<IClusterConnectionRecord> connRecord,
|
||||
Reference<AsyncVar<Value>> outSerializedLeaderInfo,
|
||||
MonitorLeaderInfo info) {
|
||||
state ClientCoordinators coordinators(info.intermediateConnRecord);
|
||||
state AsyncTrigger nomineeChange;
|
||||
state std::vector<Optional<LeaderInfo>> nominees;
|
||||
state Future<Void> allActors;
|
||||
|
||||
nominees.resize(coordinators.clientLeaderServers.size());
|
||||
|
||||
std::vector<Future<Void>> actors;
|
||||
// Ask all coordinators if the worker is considered as a leader (leader nominee) by the coordinator.
|
||||
actors.reserve(coordinators.clientLeaderServers.size());
|
||||
for (int i = 0; i < coordinators.clientLeaderServers.size(); i++)
|
||||
actors.push_back(
|
||||
monitorNominee(coordinators.clusterKey, coordinators.clientLeaderServers[i], &nomineeChange, &nominees[i]));
|
||||
allActors = waitForAll(actors);
|
||||
|
||||
loop {
|
||||
Optional<std::pair<LeaderInfo, bool>> leader = getLeader(nominees);
|
||||
TraceEvent("MonitorLeaderChange")
|
||||
.detail("NewLeader", leader.present() ? leader.get().first.changeID : UID(1, 1));
|
||||
if (leader.present()) {
|
||||
if (leader.get().first.forward) {
|
||||
TraceEvent("MonitorLeaderForwarding")
|
||||
.detail("NewConnStr", leader.get().first.serializedInfo.toString())
|
||||
.detail("OldConnStr", info.intermediateConnRecord->getConnectionString().toString())
|
||||
.trackLatest("MonitorLeaderForwarding");
|
||||
info.intermediateConnRecord = connRecord->makeIntermediateRecord(
|
||||
ClusterConnectionString(leader.get().first.serializedInfo.toString()));
|
||||
return info;
|
||||
}
|
||||
if (connRecord != info.intermediateConnRecord) {
|
||||
if (!info.hasConnected) {
|
||||
TraceEvent(SevWarnAlways, "IncorrectClusterFileContentsAtConnection")
|
||||
.detail("ClusterFile", connRecord->toString())
|
||||
.detail("StoredConnectionString", connRecord->getConnectionString().toString())
|
||||
.detail("CurrentConnectionString",
|
||||
info.intermediateConnRecord->getConnectionString().toString());
|
||||
}
|
||||
connRecord->setAndPersistConnectionString(info.intermediateConnRecord->getConnectionString());
|
||||
info.intermediateConnRecord = connRecord;
|
||||
}
|
||||
wait(connRecord->resolveHostnames());
|
||||
wait(info.intermediateConnRecord->resolveHostnames());
|
||||
state ClientCoordinators coordinators(info.intermediateConnRecord);
|
||||
state AsyncTrigger nomineeChange;
|
||||
state std::vector<Optional<LeaderInfo>> nominees;
|
||||
state Future<Void> allActors;
|
||||
|
||||
info.hasConnected = true;
|
||||
connRecord->notifyConnected();
|
||||
nominees.resize(coordinators.clientLeaderServers.size());
|
||||
|
||||
outSerializedLeaderInfo->set(leader.get().first.serializedInfo);
|
||||
state std::vector<Future<Void>> actors;
|
||||
// Ask all coordinators if the worker is considered as a leader (leader nominee) by the coordinator.
|
||||
actors.reserve(coordinators.clientLeaderServers.size());
|
||||
for (int i = 0; i < coordinators.clientLeaderServers.size(); i++) {
|
||||
Optional<Hostname> hostname;
|
||||
auto r = connRecord->getConnectionString().networkAddressToHostname.find(
|
||||
coordinators.clientLeaderServers[i].getLeader.getEndpoint().getPrimaryAddress());
|
||||
if (r != connRecord->getConnectionString().networkAddressToHostname.end()) {
|
||||
hostname = r->second;
|
||||
}
|
||||
actors.push_back(monitorNominee(
|
||||
coordinators.clusterKey, coordinators.clientLeaderServers[i], &nomineeChange, &nominees[i], hostname));
|
||||
}
|
||||
allActors = waitForAll(actors);
|
||||
|
||||
loop {
|
||||
Optional<std::pair<LeaderInfo, bool>> leader = getLeader(nominees);
|
||||
TraceEvent("MonitorLeaderChange")
|
||||
.detail("NewLeader", leader.present() ? leader.get().first.changeID : UID(1, 1));
|
||||
if (leader.present()) {
|
||||
if (leader.get().first.forward) {
|
||||
TraceEvent("MonitorLeaderForwarding")
|
||||
.detail("NewConnStr", leader.get().first.serializedInfo.toString())
|
||||
.detail("OldConnStr", info.intermediateConnRecord->getConnectionString().toString())
|
||||
.trackLatest("MonitorLeaderForwarding");
|
||||
info.intermediateConnRecord = connRecord->makeIntermediateRecord(
|
||||
ClusterConnectionString(leader.get().first.serializedInfo.toString()));
|
||||
return info;
|
||||
}
|
||||
if (connRecord != info.intermediateConnRecord) {
|
||||
if (!info.hasConnected) {
|
||||
TraceEvent(SevWarnAlways, "IncorrectClusterFileContentsAtConnection")
|
||||
.detail("ClusterFile", connRecord->toString())
|
||||
.detail("StoredConnectionString", connRecord->getConnectionString().toString())
|
||||
.detail("CurrentConnectionString",
|
||||
info.intermediateConnRecord->getConnectionString().toString());
|
||||
}
|
||||
connRecord->setAndPersistConnectionString(info.intermediateConnRecord->getConnectionString());
|
||||
info.intermediateConnRecord = connRecord;
|
||||
}
|
||||
|
||||
info.hasConnected = true;
|
||||
connRecord->notifyConnected();
|
||||
|
||||
outSerializedLeaderInfo->set(leader.get().first.serializedInfo);
|
||||
}
|
||||
try {
|
||||
wait(nomineeChange.onTrigger() || allActors);
|
||||
} catch (Error& e) {
|
||||
if (e.code() == error_code_coordinators_changed) {
|
||||
TraceEvent("MonitorLeaderCoordinatorsChanged").suppressFor(1.0);
|
||||
connRecord->getConnectionString().resetToUnresolved();
|
||||
break;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
wait(nomineeChange.onTrigger() || allActors);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -774,8 +836,8 @@ ACTOR Future<Void> getClientInfoFromLeader(Reference<AsyncVar<Optional<ClusterCo
|
|||
when(ClientDBInfo ni =
|
||||
wait(brokenPromiseToNever(knownLeader->get().get().clientInterface.openDatabase.getReply(req)))) {
|
||||
TraceEvent("GetClientInfoFromLeaderGotClientInfo", knownLeader->get().get().clientInterface.id())
|
||||
.detail("CommitProxy0", ni.commitProxies.size() ? ni.commitProxies[0].id() : UID())
|
||||
.detail("GrvProxy0", ni.grvProxies.size() ? ni.grvProxies[0].id() : UID())
|
||||
.detail("CommitProxy0", ni.commitProxies.size() ? ni.commitProxies[0].address().toString() : "")
|
||||
.detail("GrvProxy0", ni.grvProxies.size() ? ni.grvProxies[0].address().toString() : "")
|
||||
.detail("ClientID", ni.id);
|
||||
clientData->clientInfo->set(CachedSerialization<ClientDBInfo>(ni));
|
||||
}
|
||||
|
@ -787,7 +849,8 @@ ACTOR Future<Void> getClientInfoFromLeader(Reference<AsyncVar<Optional<ClusterCo
|
|||
ACTOR Future<Void> monitorLeaderAndGetClientInfo(Key clusterKey,
|
||||
std::vector<NetworkAddress> coordinators,
|
||||
ClientData* clientData,
|
||||
Reference<AsyncVar<Optional<LeaderInfo>>> leaderInfo) {
|
||||
Reference<AsyncVar<Optional<LeaderInfo>>> leaderInfo,
|
||||
Reference<AsyncVar<Void>> coordinatorsChanged) {
|
||||
state std::vector<ClientLeaderRegInterface> clientLeaderServers;
|
||||
state AsyncTrigger nomineeChange;
|
||||
state std::vector<Optional<LeaderInfo>> nominees;
|
||||
|
@ -835,7 +898,14 @@ ACTOR Future<Void> monitorLeaderAndGetClientInfo(Key clusterKey,
|
|||
leaderInfo->set(leader.get().first);
|
||||
}
|
||||
}
|
||||
wait(nomineeChange.onTrigger() || allActors);
|
||||
try {
|
||||
wait(nomineeChange.onTrigger() || allActors);
|
||||
} catch (Error& e) {
|
||||
if (e.code() == error_code_coordinators_changed) {
|
||||
coordinatorsChanged->trigger();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -964,9 +1034,15 @@ ACTOR Future<MonitorLeaderInfo> monitorProxiesOneGeneration(
|
|||
successIndex = index;
|
||||
} else {
|
||||
TEST(rep.getError().code() == error_code_failed_to_progress); // Coordinator cant talk to cluster controller
|
||||
if (rep.getError().code() == error_code_coordinators_changed) {
|
||||
throw coordinators_changed();
|
||||
}
|
||||
index = (index + 1) % addrs.size();
|
||||
if (index == successIndex) {
|
||||
wait(delay(CLIENT_KNOBS->COORDINATOR_RECONNECTION_DELAY));
|
||||
// When the client fails talking to all coordinators, we throw coordinators_changed() and let the caller
|
||||
// re-resolve the connection string and retry.
|
||||
throw coordinators_changed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -978,16 +1054,27 @@ ACTOR Future<Void> monitorProxies(
|
|||
Reference<AsyncVar<Optional<ClientLeaderRegInterface>>> coordinator,
|
||||
Reference<ReferencedObject<Standalone<VectorRef<ClientVersionRef>>>> supportedVersions,
|
||||
Key traceLogGroup) {
|
||||
wait(connRecord->get()->resolveHostnames());
|
||||
state MonitorLeaderInfo info(connRecord->get());
|
||||
loop {
|
||||
choose {
|
||||
when(MonitorLeaderInfo _info = wait(monitorProxiesOneGeneration(
|
||||
connRecord->get(), clientInfo, coordinator, info, supportedVersions, traceLogGroup))) {
|
||||
info = _info;
|
||||
try {
|
||||
wait(info.intermediateConnRecord->resolveHostnames());
|
||||
choose {
|
||||
when(MonitorLeaderInfo _info = wait(monitorProxiesOneGeneration(
|
||||
connRecord->get(), clientInfo, coordinator, info, supportedVersions, traceLogGroup))) {
|
||||
info = _info;
|
||||
}
|
||||
when(wait(connRecord->onChange())) {
|
||||
info.hasConnected = false;
|
||||
info.intermediateConnRecord = connRecord->get();
|
||||
}
|
||||
}
|
||||
when(wait(connRecord->onChange())) {
|
||||
info.hasConnected = false;
|
||||
info.intermediateConnRecord = connRecord->get();
|
||||
} catch (Error& e) {
|
||||
if (e.code() == error_code_coordinators_changed) {
|
||||
TraceEvent("MonitorProxiesCoordinatorsChanged").suppressFor(1.0);
|
||||
info.intermediateConnRecord->getConnectionString().resetToUnresolved();
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,10 +74,11 @@ Future<Void> monitorLeader(Reference<IClusterConnectionRecord> const& connFile,
|
|||
// This is one place where the leader election algorithm is run. The coodinator contacts all coodinators to collect
|
||||
// nominees, the nominee with the most nomination is the leader, and collects client data from the leader. This function
|
||||
// also monitors the change of the leader.
|
||||
Future<Void> monitorLeaderAndGetClientInfo(Value const& key,
|
||||
Future<Void> monitorLeaderAndGetClientInfo(Key const& clusterKey,
|
||||
std::vector<NetworkAddress> const& coordinators,
|
||||
ClientData* const& clientData,
|
||||
Reference<AsyncVar<Optional<LeaderInfo>>> const& leaderInfo);
|
||||
Reference<AsyncVar<Optional<LeaderInfo>>> const& leaderInfo,
|
||||
Reference<AsyncVar<Void>> const& coordinatorsChanged);
|
||||
|
||||
Future<Void> monitorProxies(
|
||||
Reference<AsyncVar<Reference<IClusterConnectionRecord>>> const& connRecord,
|
||||
|
|
|
@ -1202,9 +1202,9 @@ MultiVersionDatabase::MultiVersionDatabase(MultiVersionApi* api,
|
|||
// but we may not see trace logs from this client until a successful connection
|
||||
// is established.
|
||||
TraceEvent(SevWarnAlways, "FailedToInitializeExternalClient")
|
||||
.error(e)
|
||||
.detail("LibraryPath", client->libPath)
|
||||
.detail("ClusterFilePath", clusterFilePath)
|
||||
.error(e);
|
||||
.detail("ClusterFilePath", clusterFilePath);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1218,9 +1218,9 @@ MultiVersionDatabase::MultiVersionDatabase(MultiVersionApi* api,
|
|||
} catch (Error& e) {
|
||||
// This connection is discarded
|
||||
TraceEvent(SevWarnAlways, "FailedToCreateLegacyDatabaseConnection")
|
||||
.error(e)
|
||||
.detail("LibraryPath", client->libPath)
|
||||
.detail("ClusterFilePath", clusterFilePath)
|
||||
.error(e);
|
||||
.detail("ClusterFilePath", clusterFilePath);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1360,8 +1360,8 @@ ThreadFuture<Void> MultiVersionDatabase::DatabaseState::monitorProtocolVersion()
|
|||
}
|
||||
|
||||
TraceEvent("ErrorGettingClusterProtocolVersion")
|
||||
.detail("ExpectedProtocolVersion", expected)
|
||||
.error(cv.getError());
|
||||
.error(cv.getError())
|
||||
.detail("ExpectedProtocolVersion", expected);
|
||||
}
|
||||
|
||||
ProtocolVersion clusterVersion =
|
||||
|
@ -1409,10 +1409,10 @@ void MultiVersionDatabase::DatabaseState::protocolVersionChanged(ProtocolVersion
|
|||
newDb = client->api->createDatabase(clusterFilePath.c_str());
|
||||
} catch (Error& e) {
|
||||
TraceEvent(SevWarnAlways, "MultiVersionClientFailedToCreateDatabase")
|
||||
.error(e)
|
||||
.detail("LibraryPath", client->libPath)
|
||||
.detail("External", client->external)
|
||||
.detail("ClusterFilePath", clusterFilePath)
|
||||
.error(e);
|
||||
.detail("ClusterFilePath", clusterFilePath);
|
||||
|
||||
// Put the client in a disconnected state until the version changes again
|
||||
updateDatabase(Reference<IDatabase>(), Reference<ClientInfo>());
|
||||
|
@ -1486,8 +1486,8 @@ void MultiVersionDatabase::DatabaseState::updateDatabase(Reference<IDatabase> ne
|
|||
// We can't create a new database to monitor the cluster version. This means we will continue using the
|
||||
// previous one, which should hopefully continue to work.
|
||||
TraceEvent(SevWarnAlways, "FailedToCreateDatabaseForVersionMonitoring")
|
||||
.detail("ClusterFilePath", clusterFilePath)
|
||||
.error(e);
|
||||
.error(e)
|
||||
.detail("ClusterFilePath", clusterFilePath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -1499,8 +1499,8 @@ void MultiVersionDatabase::DatabaseState::updateDatabase(Reference<IDatabase> ne
|
|||
// We can't create a new database to monitor the cluster version. This means we will continue using the
|
||||
// previous one, which should hopefully continue to work.
|
||||
TraceEvent(SevWarnAlways, "FailedToCreateDatabaseForVersionMonitoring")
|
||||
.detail("ClusterFilePath", clusterFilePath)
|
||||
.error(e);
|
||||
.error(e)
|
||||
.detail("ClusterFilePath", clusterFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -157,6 +157,8 @@ struct TransactionOptions {
|
|||
bool includePort : 1;
|
||||
bool reportConflictingKeys : 1;
|
||||
bool expensiveClearCostEstimation : 1;
|
||||
bool useGrvCache : 1;
|
||||
bool skipGrvCache : 1;
|
||||
|
||||
TransactionPriority priority;
|
||||
|
||||
|
@ -480,5 +482,9 @@ inline uint64_t getWriteOperationCost(uint64_t bytes) {
|
|||
// will be 1. Otherwise, the value will be 0.
|
||||
ACTOR Future<Void> setPerpetualStorageWiggle(Database cx, bool enable, LockAware lockAware = LockAware::False);
|
||||
|
||||
ACTOR Future<std::vector<std::pair<UID, StorageWiggleValue>>> readStorageWiggleValues(Database cx,
|
||||
bool primary,
|
||||
bool use_system_priority);
|
||||
|
||||
#include "flow/unactorcompiler.h"
|
||||
#endif
|
||||
|
|
|
@ -2585,7 +2585,7 @@ void ReadYourWritesTransaction::debugLogRetries(Optional<Error> error) {
|
|||
{
|
||||
TraceEvent trace = TraceEvent("LongTransaction");
|
||||
if (error.present())
|
||||
trace.error(error.get(), true);
|
||||
trace.errorUnsuppressed(error.get());
|
||||
if (!transactionDebugInfo->transactionName.empty())
|
||||
trace.detail("TransactionName", transactionDebugInfo->transactionName);
|
||||
trace.detail("Elapsed", elapsed).detail("Retries", retries).detail("Committed", committed);
|
||||
|
|
|
@ -500,7 +500,7 @@ ACTOR Future<Optional<json_spirit::mObject>> tryReadJSONFile(std::string path) {
|
|||
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_actor_cancelled)
|
||||
TraceEvent(SevWarn, errorEventType).error(e).suppressFor(60).detail("File", path);
|
||||
TraceEvent(SevWarn, errorEventType).errorUnsuppressed(e).suppressFor(60).detail("File", path);
|
||||
}
|
||||
|
||||
return Optional<json_spirit::mObject>();
|
||||
|
@ -744,7 +744,7 @@ ACTOR Future<Reference<HTTP::Response>> doRequest_impl(Reference<S3BlobStoreEndp
|
|||
|
||||
// Attach err to trace event if present, otherwise extract some stuff from the response
|
||||
if (err.present()) {
|
||||
event.error(err.get());
|
||||
event.errorUnsuppressed(err.get());
|
||||
}
|
||||
event.suppressFor(60);
|
||||
if (!err.present()) {
|
||||
|
@ -954,7 +954,7 @@ ACTOR Future<Void> listObjectsStream_impl(Reference<S3BlobStoreEndpoint> bstore,
|
|||
} catch (Error& e) {
|
||||
if (e.code() != error_code_actor_cancelled)
|
||||
TraceEvent(SevWarn, "S3BlobStoreEndpointListResultParseError")
|
||||
.error(e)
|
||||
.errorUnsuppressed(e)
|
||||
.suppressFor(60)
|
||||
.detail("Resource", fullResource);
|
||||
throw http_bad_response();
|
||||
|
@ -1080,7 +1080,7 @@ ACTOR Future<std::vector<std::string>> listBuckets_impl(Reference<S3BlobStoreEnd
|
|||
} catch (Error& e) {
|
||||
if (e.code() != error_code_actor_cancelled)
|
||||
TraceEvent(SevWarn, "S3BlobStoreEndpointListBucketResultParseError")
|
||||
.error(e)
|
||||
.errorUnsuppressed(e)
|
||||
.suppressFor(60)
|
||||
.detail("Resource", fullResource);
|
||||
throw http_bad_response();
|
||||
|
|
|
@ -25,6 +25,8 @@ const KeyRef JSONSchemas::statusSchema = LiteralStringRef(R"statusSchema(
|
|||
{
|
||||
"cluster":{
|
||||
"storage_wiggler": {
|
||||
"wiggle_server_ids":["0ccb4e0feddb55"],
|
||||
"wiggle_server_addresses": ["127.0.0.1"],
|
||||
"primary": {
|
||||
"last_round_start_datetime": "Wed Feb 4 09:36:37 2022 +0000",
|
||||
"last_round_start_timestamp": 63811229797,
|
||||
|
@ -808,7 +810,13 @@ const KeyRef JSONSchemas::statusSchema = LiteralStringRef(R"statusSchema(
|
|||
"aggressive",
|
||||
"gradual"
|
||||
]},
|
||||
"blob_granules_enabled":0
|
||||
"blob_granules_enabled":0,
|
||||
"tenant_mode": {
|
||||
"$enum":[
|
||||
"disabled",
|
||||
"optional_experimental",
|
||||
"required_experimental"
|
||||
]}
|
||||
},
|
||||
"data":{
|
||||
"least_operating_space_bytes_log_server":0,
|
||||
|
|
|
@ -290,6 +290,7 @@ void ServerKnobs::initialize(Randomize randomize, ClientKnobs* clientKnobs, IsSi
|
|||
init( SQLITE_CHUNK_SIZE_PAGES_SIM, 1024 ); // 4MB
|
||||
init( SQLITE_READER_THREADS, 64 ); // number of read threads
|
||||
init( SQLITE_WRITE_WINDOW_SECONDS, -1 );
|
||||
init( SQLITE_CURSOR_MAX_LIFETIME_BYTES, 1e6 ); if (buggifySmallShards || simulationMediumShards) SQLITE_CURSOR_MAX_LIFETIME_BYTES = MIN_SHARD_BYTES; if( randomize && BUGGIFY ) SQLITE_CURSOR_MAX_LIFETIME_BYTES = 0;
|
||||
init( SQLITE_WRITE_WINDOW_LIMIT, -1 );
|
||||
if( randomize && BUGGIFY ) {
|
||||
// Choose an window between .01 and 1.01 seconds.
|
||||
|
@ -339,7 +340,7 @@ void ServerKnobs::initialize(Randomize randomize, ClientKnobs* clientKnobs, IsSi
|
|||
init( REPLACE_CONTENTS_BYTES, 1e5 );
|
||||
|
||||
// KeyValueStoreRocksDB
|
||||
init( ROCKSDB_BACKGROUND_PARALLELISM, 0 );
|
||||
init( ROCKSDB_BACKGROUND_PARALLELISM, 4 );
|
||||
init( ROCKSDB_READ_PARALLELISM, 4 );
|
||||
// Use a smaller memtable in simulation to avoid OOMs.
|
||||
int64_t memtableBytes = isSimulated ? 32 * 1024 : 512 * 1024 * 1024;
|
||||
|
@ -364,7 +365,17 @@ void ServerKnobs::initialize(Randomize randomize, ClientKnobs* clientKnobs, IsSi
|
|||
init( ROCKSDB_WRITE_RATE_LIMITER_BYTES_PER_SEC, 0 );
|
||||
// If true, enables dynamic adjustment of ROCKSDB_WRITE_RATE_LIMITER_BYTES according to the recent demand of background IO.
|
||||
init( ROCKSDB_WRITE_RATE_LIMITER_AUTO_TUNE, true );
|
||||
|
||||
init( ROCKSDB_PERFCONTEXT_ENABLE, false ); if( randomize && BUGGIFY ) ROCKSDB_PERFCONTEXT_ENABLE = deterministicRandom()->coinflip() ? false : true;
|
||||
init( ROCKSDB_PERFCONTEXT_SAMPLE_RATE, 0.0001 );
|
||||
init( ROCKSDB_MAX_SUBCOMPACTIONS, 2 );
|
||||
init( ROCKSDB_SOFT_PENDING_COMPACT_BYTES_LIMIT, 64000000000 ); // 64GB, Rocksdb option, Writes will slow down.
|
||||
init( ROCKSDB_HARD_PENDING_COMPACT_BYTES_LIMIT, 100000000000 ); // 100GB, Rocksdb option, Writes will stall.
|
||||
init( ROCKSDB_CAN_COMMIT_COMPACT_BYTES_LIMIT, 50000000000 ); // 50GB, Commit waits.
|
||||
// Can commit will delay ROCKSDB_CAN_COMMIT_DELAY_ON_OVERLOAD seconds for
|
||||
// ROCKSDB_CAN_COMMIT_DELAY_TIMES_ON_OVERLOAD times, if rocksdb overloaded.
|
||||
// Set ROCKSDB_CAN_COMMIT_DELAY_TIMES_ON_OVERLOAD to 0, to disable
|
||||
init( ROCKSDB_CAN_COMMIT_DELAY_ON_OVERLOAD, 1 );
|
||||
init( ROCKSDB_CAN_COMMIT_DELAY_TIMES_ON_OVERLOAD, 5 );
|
||||
|
||||
// Leader election
|
||||
bool longLeaderElection = randomize && BUGGIFY;
|
||||
|
@ -582,6 +593,7 @@ void ServerKnobs::initialize(Randomize randomize, ClientKnobs* clientKnobs, IsSi
|
|||
|
||||
init( MIN_AVAILABLE_SPACE, 1e8 );
|
||||
init( MIN_AVAILABLE_SPACE_RATIO, 0.05 );
|
||||
init( MIN_AVAILABLE_SPACE_RATIO_SAFETY_BUFFER, 0.01 );
|
||||
init( TARGET_AVAILABLE_SPACE_RATIO, 0.30 );
|
||||
init( AVAILABLE_SPACE_UPDATE_DELAY, 5.0 );
|
||||
|
||||
|
@ -809,14 +821,23 @@ void ServerKnobs::initialize(Randomize randomize, ClientKnobs* clientKnobs, IsSi
|
|||
init( ENABLE_ENCRYPT_KEY_PROXY, false );
|
||||
|
||||
// Blob granlues
|
||||
init( BG_URL, isSimulated ? "file://fdbblob/" : "" ); // TODO: store in system key space, eventually
|
||||
init( BG_SNAPSHOT_FILE_TARGET_BYTES, 10000000 ); if( buggifySmallShards || (randomize && BUGGIFY) ) { deterministicRandom()->random01() < 0.1 ? BG_SNAPSHOT_FILE_TARGET_BYTES /= 100 : BG_SNAPSHOT_FILE_TARGET_BYTES /= 10; }
|
||||
init( BG_URL, isSimulated ? "file://fdbblob/" : "" ); // TODO: store in system key space or something, eventually
|
||||
init( BG_SNAPSHOT_FILE_TARGET_BYTES, 10000000 ); if( buggifySmallShards ) BG_SNAPSHOT_FILE_TARGET_BYTES = 100000; else if (simulationMediumShards || (randomize && BUGGIFY) ) BG_SNAPSHOT_FILE_TARGET_BYTES = 1000000;
|
||||
init( BG_DELTA_BYTES_BEFORE_COMPACT, BG_SNAPSHOT_FILE_TARGET_BYTES/2 );
|
||||
init( BG_DELTA_FILE_TARGET_BYTES, BG_DELTA_BYTES_BEFORE_COMPACT/10 );
|
||||
init( BG_MAX_SPLIT_FANOUT, 10 ); if( randomize && BUGGIFY ) BG_MAX_SPLIT_FANOUT = deterministicRandom()->randomInt(5, 15);
|
||||
init( BG_HOT_SNAPSHOT_VERSIONS, 5000000 );
|
||||
|
||||
init( BLOB_WORKER_INITIAL_SNAPSHOT_PARALLELISM, 8 ); if( randomize && BUGGIFY ) BLOB_WORKER_INITIAL_SNAPSHOT_PARALLELISM = 1;
|
||||
init( BLOB_WORKER_TIMEOUT, 10.0 ); if( randomize && BUGGIFY ) BLOB_WORKER_TIMEOUT = 1.0;
|
||||
init( BLOB_WORKER_REQUEST_TIMEOUT, 5.0 ); if( randomize && BUGGIFY ) BLOB_WORKER_REQUEST_TIMEOUT = 1.0;
|
||||
init( BLOB_WORKERLIST_FETCH_INTERVAL, 1.0 );
|
||||
init( BLOB_WORKER_BATCH_GRV_INTERVAL, 0.1 );
|
||||
|
||||
|
||||
init( BLOB_MANAGER_STATUS_EXP_BACKOFF_MIN, 0.1 );
|
||||
init( BLOB_MANAGER_STATUS_EXP_BACKOFF_MAX, 5.0 );
|
||||
init( BLOB_MANAGER_STATUS_EXP_BACKOFF_EXPONENT, 1.5 );
|
||||
|
||||
// clang-format on
|
||||
|
||||
|
|
|
@ -254,6 +254,7 @@ public:
|
|||
int SQLITE_READER_THREADS;
|
||||
int SQLITE_WRITE_WINDOW_LIMIT;
|
||||
double SQLITE_WRITE_WINDOW_SECONDS;
|
||||
int64_t SQLITE_CURSOR_MAX_LIFETIME_BYTES;
|
||||
|
||||
// KeyValueStoreSqlite spring cleaning
|
||||
double SPRING_CLEANING_NO_ACTION_INTERVAL;
|
||||
|
@ -295,6 +296,14 @@ public:
|
|||
bool ROCKSDB_READ_RANGE_REUSE_ITERATORS;
|
||||
int64_t ROCKSDB_WRITE_RATE_LIMITER_BYTES_PER_SEC;
|
||||
bool ROCKSDB_WRITE_RATE_LIMITER_AUTO_TUNE;
|
||||
bool ROCKSDB_PERFCONTEXT_ENABLE; // Enable rocks perf context metrics. May cause performance overhead
|
||||
double ROCKSDB_PERFCONTEXT_SAMPLE_RATE;
|
||||
int ROCKSDB_MAX_SUBCOMPACTIONS;
|
||||
int64_t ROCKSDB_SOFT_PENDING_COMPACT_BYTES_LIMIT;
|
||||
int64_t ROCKSDB_HARD_PENDING_COMPACT_BYTES_LIMIT;
|
||||
int64_t ROCKSDB_CAN_COMMIT_COMPACT_BYTES_LIMIT;
|
||||
int ROCKSDB_CAN_COMMIT_DELAY_ON_OVERLOAD;
|
||||
int ROCKSDB_CAN_COMMIT_DELAY_TIMES_ON_OVERLOAD;
|
||||
|
||||
// Leader election
|
||||
int MAX_NOTIFICATIONS;
|
||||
|
@ -528,6 +537,7 @@ public:
|
|||
|
||||
int64_t MIN_AVAILABLE_SPACE;
|
||||
double MIN_AVAILABLE_SPACE_RATIO;
|
||||
double MIN_AVAILABLE_SPACE_RATIO_SAFETY_BUFFER;
|
||||
double TARGET_AVAILABLE_SPACE_RATIO;
|
||||
double AVAILABLE_SPACE_UPDATE_DELAY;
|
||||
|
||||
|
@ -766,10 +776,18 @@ public:
|
|||
int BG_SNAPSHOT_FILE_TARGET_BYTES;
|
||||
int BG_DELTA_FILE_TARGET_BYTES;
|
||||
int BG_DELTA_BYTES_BEFORE_COMPACT;
|
||||
int BG_MAX_SPLIT_FANOUT;
|
||||
int BG_HOT_SNAPSHOT_VERSIONS;
|
||||
|
||||
int BLOB_WORKER_INITIAL_SNAPSHOT_PARALLELISM;
|
||||
double BLOB_WORKER_TIMEOUT; // Blob Manager's reaction time to a blob worker failure
|
||||
double BLOB_WORKER_REQUEST_TIMEOUT; // Blob Worker's server-side request timeout
|
||||
double BLOB_WORKERLIST_FETCH_INTERVAL;
|
||||
double BLOB_WORKER_BATCH_GRV_INTERVAL;
|
||||
|
||||
double BLOB_MANAGER_STATUS_EXP_BACKOFF_MIN;
|
||||
double BLOB_MANAGER_STATUS_EXP_BACKOFF_MAX;
|
||||
double BLOB_MANAGER_STATUS_EXP_BACKOFF_EXPONENT;
|
||||
|
||||
ServerKnobs(Randomize, ClientKnobs*, IsSimulated);
|
||||
void initialize(Randomize, ClientKnobs*, IsSimulated);
|
||||
|
|
|
@ -146,8 +146,11 @@ ACTOR Future<Void> moveKeySelectorOverRangeActor(const SpecialKeyRangeReadImpl*
|
|||
ReadYourWritesTransaction* ryw,
|
||||
KeySelector* ks,
|
||||
Optional<RangeResult>* cache) {
|
||||
ASSERT(!ks->orEqual); // should be removed before calling
|
||||
ASSERT(ks->offset != 1); // never being called if KeySelector is already normalized
|
||||
// should be removed before calling
|
||||
ASSERT(!ks->orEqual);
|
||||
|
||||
// never being called if KeySelector is already normalized
|
||||
ASSERT(ks->offset != 1);
|
||||
|
||||
state Key startKey(skrImpl->getKeyRange().begin);
|
||||
state Key endKey(skrImpl->getKeyRange().end);
|
||||
|
@ -162,7 +165,9 @@ ACTOR Future<Void> moveKeySelectorOverRangeActor(const SpecialKeyRangeReadImpl*
|
|||
if (skrImpl->getKeyRange().contains(ks->getKey()))
|
||||
startKey = ks->getKey();
|
||||
}
|
||||
ASSERT(startKey < endKey); // Note : startKey never equals endKey here
|
||||
|
||||
// Note : startKey never equals endKey here
|
||||
ASSERT(startKey < endKey);
|
||||
|
||||
TraceEvent(SevDebug, "NormalizeKeySelector")
|
||||
.detail("OriginalKey", ks->getKey())
|
||||
|
@ -170,12 +175,14 @@ ACTOR Future<Void> moveKeySelectorOverRangeActor(const SpecialKeyRangeReadImpl*
|
|||
.detail("SpecialKeyRangeStart", skrImpl->getKeyRange().begin)
|
||||
.detail("SpecialKeyRangeEnd", skrImpl->getKeyRange().end);
|
||||
|
||||
GetRangeLimits limitsHint(ks->offset >= 1 ? ks->offset : 1 - ks->offset);
|
||||
|
||||
if (skrImpl->isAsync()) {
|
||||
const SpecialKeyRangeAsyncImpl* ptr = dynamic_cast<const SpecialKeyRangeAsyncImpl*>(skrImpl);
|
||||
RangeResult result_ = wait(ptr->getRange(ryw, KeyRangeRef(startKey, endKey), cache));
|
||||
RangeResult result_ = wait(ptr->getRange(ryw, KeyRangeRef(startKey, endKey), limitsHint, cache));
|
||||
result = result_;
|
||||
} else {
|
||||
RangeResult result_ = wait(skrImpl->getRange(ryw, KeyRangeRef(startKey, endKey)));
|
||||
RangeResult result_ = wait(skrImpl->getRange(ryw, KeyRangeRef(startKey, endKey), limitsHint));
|
||||
result = result_;
|
||||
}
|
||||
|
||||
|
@ -197,9 +204,8 @@ ACTOR Future<Void> moveKeySelectorOverRangeActor(const SpecialKeyRangeReadImpl*
|
|||
ks->setKey(KeyRef(ks->arena(), result[ks->offset - 1].key));
|
||||
ks->offset = 1;
|
||||
} else {
|
||||
ks->setKey(KeyRef(
|
||||
ks->arena(),
|
||||
keyAfter(result[result.size() - 1].key))); // TODO : the keyAfter will just return if key == \xff\xff
|
||||
// TODO : the keyAfter will just return if key == \xff\xff
|
||||
ks->setKey(KeyRef(ks->arena(), keyAfter(result[result.size() - 1].key)));
|
||||
ks->offset -= result.size();
|
||||
}
|
||||
}
|
||||
|
@ -277,8 +283,10 @@ SpecialKeySpace::SpecialKeySpace(KeyRef spaceStartKey, KeyRef spaceEndKey, bool
|
|||
// Default begin of KeyRangeMap is Key(), insert the range to update start key
|
||||
readImpls.insert(range, nullptr);
|
||||
writeImpls.insert(range, nullptr);
|
||||
if (!testOnly)
|
||||
modulesBoundaryInit(); // testOnly is used in the correctness workload
|
||||
if (!testOnly) {
|
||||
// testOnly is used in the correctness workload
|
||||
modulesBoundaryInit();
|
||||
}
|
||||
}
|
||||
|
||||
void SpecialKeySpace::modulesBoundaryInit() {
|
||||
|
@ -340,8 +348,8 @@ ACTOR Future<RangeResult> SpecialKeySpace::getRangeAggregationActor(SpecialKeySp
|
|||
moduleBoundary = beginIter->range();
|
||||
} else {
|
||||
TraceEvent(SevInfo, "SpecialKeyCrossModuleRead")
|
||||
.detail("Begin", begin.toString())
|
||||
.detail("End", end.toString())
|
||||
.detail("Begin", begin)
|
||||
.detail("End", end)
|
||||
.detail("BoundaryBegin", beginIter->begin())
|
||||
.detail("BoundaryEnd", beginIter->end());
|
||||
throw special_keys_cross_module_read();
|
||||
|
@ -376,10 +384,10 @@ ACTOR Future<RangeResult> SpecialKeySpace::getRangeAggregationActor(SpecialKeySp
|
|||
KeyRef keyEnd = kr.contains(end.getKey()) ? end.getKey() : kr.end;
|
||||
if (iter->value()->isAsync() && cache.present()) {
|
||||
const SpecialKeyRangeAsyncImpl* ptr = dynamic_cast<const SpecialKeyRangeAsyncImpl*>(iter->value());
|
||||
RangeResult pairs_ = wait(ptr->getRange(ryw, KeyRangeRef(keyStart, keyEnd), &cache));
|
||||
RangeResult pairs_ = wait(ptr->getRange(ryw, KeyRangeRef(keyStart, keyEnd), limits, &cache));
|
||||
pairs = pairs_;
|
||||
} else {
|
||||
RangeResult pairs_ = wait(iter->value()->getRange(ryw, KeyRangeRef(keyStart, keyEnd)));
|
||||
RangeResult pairs_ = wait(iter->value()->getRange(ryw, KeyRangeRef(keyStart, keyEnd), limits));
|
||||
pairs = pairs_;
|
||||
}
|
||||
result.arena().dependsOn(pairs.arena());
|
||||
|
@ -407,10 +415,10 @@ ACTOR Future<RangeResult> SpecialKeySpace::getRangeAggregationActor(SpecialKeySp
|
|||
KeyRef keyEnd = kr.contains(end.getKey()) ? end.getKey() : kr.end;
|
||||
if (iter->value()->isAsync() && cache.present()) {
|
||||
const SpecialKeyRangeAsyncImpl* ptr = dynamic_cast<const SpecialKeyRangeAsyncImpl*>(iter->value());
|
||||
RangeResult pairs_ = wait(ptr->getRange(ryw, KeyRangeRef(keyStart, keyEnd), &cache));
|
||||
RangeResult pairs_ = wait(ptr->getRange(ryw, KeyRangeRef(keyStart, keyEnd), limits, &cache));
|
||||
pairs = pairs_;
|
||||
} else {
|
||||
RangeResult pairs_ = wait(iter->value()->getRange(ryw, KeyRangeRef(keyStart, keyEnd)));
|
||||
RangeResult pairs_ = wait(iter->value()->getRange(ryw, KeyRangeRef(keyStart, keyEnd), limits));
|
||||
pairs = pairs_;
|
||||
}
|
||||
result.arena().dependsOn(pairs.arena());
|
||||
|
@ -543,15 +551,17 @@ void SpecialKeySpace::registerKeyRange(SpecialKeySpace::MODULE module,
|
|||
ASSERT(normalKeys.contains(kr));
|
||||
} else {
|
||||
ASSERT(moduleToBoundary.at(module).contains(kr));
|
||||
ASSERT(validateSnakeCaseNaming(kr.begin) &&
|
||||
validateSnakeCaseNaming(kr.end)); // validate keys follow snake case naming style
|
||||
// validate keys follow snake case naming style
|
||||
ASSERT(validateSnakeCaseNaming(kr.begin) && validateSnakeCaseNaming(kr.end));
|
||||
}
|
||||
// make sure the registered range is not overlapping with existing ones
|
||||
// Note: kr.end should not be the same as another range's begin, although it should work even they are the same
|
||||
for (auto iter = readImpls.rangeContaining(kr.begin); true; ++iter) {
|
||||
ASSERT(iter->value() == nullptr);
|
||||
if (iter == readImpls.rangeContaining(kr.end))
|
||||
break; // Note: relax the condition that the end can be another range's start, if needed
|
||||
if (iter == readImpls.rangeContaining(kr.end)) {
|
||||
// Note: relax the condition that the end can be another range's start, if needed
|
||||
break;
|
||||
}
|
||||
}
|
||||
readImpls.insert(kr, impl);
|
||||
// if rw, it means the module can do both read and write
|
||||
|
@ -601,7 +611,7 @@ ACTOR Future<Void> commitActor(SpecialKeySpace* sks, ReadYourWritesTransaction*
|
|||
Optional<std::string> msg = wait((*it)->commit(ryw));
|
||||
if (msg.present()) {
|
||||
ryw->setSpecialKeySpaceErrorMsg(msg.get());
|
||||
TraceEvent(SevDebug, "SpecialKeySpaceManagemetnAPIError")
|
||||
TraceEvent(SevDebug, "SpecialKeySpaceManagementAPIError")
|
||||
.detail("Reason", msg.get())
|
||||
.detail("Range", (*it)->getKeyRange().toString());
|
||||
throw special_keys_api_failure();
|
||||
|
@ -616,7 +626,9 @@ Future<Void> SpecialKeySpace::commit(ReadYourWritesTransaction* ryw) {
|
|||
|
||||
SKSCTestImpl::SKSCTestImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {}
|
||||
|
||||
Future<RangeResult> SKSCTestImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> SKSCTestImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
ASSERT(range.contains(kr));
|
||||
auto resultFuture = ryw->getRange(kr, CLIENT_KNOBS->TOO_MANY);
|
||||
// all keys are written to RYW, since GRV is set, the read should happen locally
|
||||
|
@ -639,19 +651,25 @@ ACTOR static Future<RangeResult> getReadConflictRangeImpl(ReadYourWritesTransact
|
|||
return ryw->getReadConflictRangeIntersecting(kr);
|
||||
}
|
||||
|
||||
Future<RangeResult> ReadConflictRangeImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> ReadConflictRangeImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
return getReadConflictRangeImpl(ryw, kr);
|
||||
}
|
||||
|
||||
WriteConflictRangeImpl::WriteConflictRangeImpl(KeyRangeRef kr) : SpecialKeyRangeReadImpl(kr) {}
|
||||
|
||||
Future<RangeResult> WriteConflictRangeImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> WriteConflictRangeImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
return ryw->getWriteConflictRangeIntersecting(kr);
|
||||
}
|
||||
|
||||
ConflictingKeysImpl::ConflictingKeysImpl(KeyRangeRef kr) : SpecialKeyRangeReadImpl(kr) {}
|
||||
|
||||
Future<RangeResult> ConflictingKeysImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> ConflictingKeysImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
RangeResult result;
|
||||
if (ryw->getTransactionState()->conflictingKeys) {
|
||||
auto krMapPtr = ryw->getTransactionState()->conflictingKeys.get();
|
||||
|
@ -702,7 +720,9 @@ ACTOR Future<RangeResult> ddMetricsGetRangeActor(ReadYourWritesTransaction* ryw,
|
|||
|
||||
DDStatsRangeImpl::DDStatsRangeImpl(KeyRangeRef kr) : SpecialKeyRangeAsyncImpl(kr) {}
|
||||
|
||||
Future<RangeResult> DDStatsRangeImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> DDStatsRangeImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
return ddMetricsGetRangeActor(ryw, kr);
|
||||
}
|
||||
|
||||
|
@ -715,7 +735,9 @@ Key SpecialKeySpace::getManagementApiCommandOptionSpecialKey(const std::string&
|
|||
|
||||
ManagementCommandsOptionsImpl::ManagementCommandsOptionsImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {}
|
||||
|
||||
Future<RangeResult> ManagementCommandsOptionsImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> ManagementCommandsOptionsImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
RangeResult result;
|
||||
// Since we only have limit number of options, a brute force loop here is enough
|
||||
for (const auto& option : SpecialKeySpace::getManagementApiOptionsSet()) {
|
||||
|
@ -822,7 +844,9 @@ ACTOR Future<RangeResult> rwModuleWithMappingGetRangeActor(ReadYourWritesTransac
|
|||
|
||||
ExcludeServersRangeImpl::ExcludeServersRangeImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {}
|
||||
|
||||
Future<RangeResult> ExcludeServersRangeImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> ExcludeServersRangeImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
ryw->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS);
|
||||
return rwModuleWithMappingGetRangeActor(ryw, this, kr);
|
||||
}
|
||||
|
@ -847,8 +871,8 @@ bool parseNetWorkAddrFromKeys(ReadYourWritesTransaction* ryw,
|
|||
std::vector<AddressExclusion>& addresses,
|
||||
std::set<AddressExclusion>& exclusions,
|
||||
Optional<std::string>& msg) {
|
||||
KeyRangeRef range = failed ? SpecialKeySpace::getManamentApiCommandRange("failed")
|
||||
: SpecialKeySpace::getManamentApiCommandRange("exclude");
|
||||
KeyRangeRef range = failed ? SpecialKeySpace::getManagementApiCommandRange("failed")
|
||||
: SpecialKeySpace::getManagementApiCommandRange("exclude");
|
||||
auto ranges = ryw->getSpecialKeySpaceWriteMap().containedRanges(range);
|
||||
auto iter = ranges.begin();
|
||||
while (iter != ranges.end()) {
|
||||
|
@ -1009,7 +1033,7 @@ void includeServers(ReadYourWritesTransaction* ryw) {
|
|||
std::string versionKey = deterministicRandom()->randomUniqueID().toString();
|
||||
// for exluded servers
|
||||
auto ranges =
|
||||
ryw->getSpecialKeySpaceWriteMap().containedRanges(SpecialKeySpace::getManamentApiCommandRange("exclude"));
|
||||
ryw->getSpecialKeySpaceWriteMap().containedRanges(SpecialKeySpace::getManagementApiCommandRange("exclude"));
|
||||
auto iter = ranges.begin();
|
||||
Transaction& tr = ryw->getTransaction();
|
||||
while (iter != ranges.end()) {
|
||||
|
@ -1022,7 +1046,7 @@ void includeServers(ReadYourWritesTransaction* ryw) {
|
|||
++iter;
|
||||
}
|
||||
// for failed servers
|
||||
ranges = ryw->getSpecialKeySpaceWriteMap().containedRanges(SpecialKeySpace::getManamentApiCommandRange("failed"));
|
||||
ranges = ryw->getSpecialKeySpaceWriteMap().containedRanges(SpecialKeySpace::getManagementApiCommandRange("failed"));
|
||||
iter = ranges.begin();
|
||||
while (iter != ranges.end()) {
|
||||
auto entry = iter->value();
|
||||
|
@ -1063,7 +1087,9 @@ Future<Optional<std::string>> ExcludeServersRangeImpl::commit(ReadYourWritesTran
|
|||
|
||||
FailedServersRangeImpl::FailedServersRangeImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {}
|
||||
|
||||
Future<RangeResult> FailedServersRangeImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> FailedServersRangeImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
ryw->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS);
|
||||
return rwModuleWithMappingGetRangeActor(ryw, this, kr);
|
||||
}
|
||||
|
@ -1144,7 +1170,9 @@ ACTOR Future<RangeResult> ExclusionInProgressActor(ReadYourWritesTransaction* ry
|
|||
|
||||
ExclusionInProgressRangeImpl::ExclusionInProgressRangeImpl(KeyRangeRef kr) : SpecialKeyRangeAsyncImpl(kr) {}
|
||||
|
||||
Future<RangeResult> ExclusionInProgressRangeImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> ExclusionInProgressRangeImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
return ExclusionInProgressActor(ryw, getKeyRange().begin, kr);
|
||||
}
|
||||
|
||||
|
@ -1211,7 +1239,9 @@ ACTOR Future<Optional<std::string>> processClassCommitActor(ReadYourWritesTransa
|
|||
|
||||
ProcessClassRangeImpl::ProcessClassRangeImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {}
|
||||
|
||||
Future<RangeResult> ProcessClassRangeImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> ProcessClassRangeImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
return getProcessClassActor(ryw, getKeyRange().begin, kr);
|
||||
}
|
||||
|
||||
|
@ -1287,7 +1317,9 @@ ACTOR Future<RangeResult> getProcessClassSourceActor(ReadYourWritesTransaction*
|
|||
|
||||
ProcessClassSourceRangeImpl::ProcessClassSourceRangeImpl(KeyRangeRef kr) : SpecialKeyRangeReadImpl(kr) {}
|
||||
|
||||
Future<RangeResult> ProcessClassSourceRangeImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> ProcessClassSourceRangeImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
return getProcessClassSourceActor(ryw, getKeyRange().begin, kr);
|
||||
}
|
||||
|
||||
|
@ -1305,7 +1337,9 @@ ACTOR Future<RangeResult> getLockedKeyActor(ReadYourWritesTransaction* ryw, KeyR
|
|||
|
||||
LockDatabaseImpl::LockDatabaseImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {}
|
||||
|
||||
Future<RangeResult> LockDatabaseImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> LockDatabaseImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
// single key range, the queried range should always be the same as the underlying range
|
||||
ASSERT(kr == getKeyRange());
|
||||
auto lockEntry = ryw->getSpecialKeySpaceWriteMap()[SpecialKeySpace::getManagementApiCommandPrefix("lock")];
|
||||
|
@ -1386,7 +1420,9 @@ ACTOR Future<RangeResult> getConsistencyCheckKeyActor(ReadYourWritesTransaction*
|
|||
|
||||
ConsistencyCheckImpl::ConsistencyCheckImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {}
|
||||
|
||||
Future<RangeResult> ConsistencyCheckImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> ConsistencyCheckImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
// single key range, the queried range should always be the same as the underlying range
|
||||
ASSERT(kr == getKeyRange());
|
||||
auto entry = ryw->getSpecialKeySpaceWriteMap()[SpecialKeySpace::getManagementApiCommandPrefix("consistencycheck")];
|
||||
|
@ -1419,7 +1455,9 @@ GlobalConfigImpl::GlobalConfigImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {
|
|||
// framework within the range specified. The special-key-space getrange
|
||||
// function should only be used for informational purposes. All values are
|
||||
// returned as strings regardless of their true type.
|
||||
Future<RangeResult> GlobalConfigImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> GlobalConfigImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
RangeResult result;
|
||||
|
||||
auto& globalConfig = GlobalConfig::globalConfig();
|
||||
|
@ -1540,7 +1578,9 @@ void GlobalConfigImpl::clear(ReadYourWritesTransaction* ryw, const KeyRef& key)
|
|||
|
||||
TracingOptionsImpl::TracingOptionsImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {}
|
||||
|
||||
Future<RangeResult> TracingOptionsImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> TracingOptionsImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
RangeResult result;
|
||||
for (const auto& option : SpecialKeySpace::getTracingOptions()) {
|
||||
auto key = getKeyRange().begin.withSuffix(option);
|
||||
|
@ -1599,7 +1639,9 @@ void TracingOptionsImpl::clear(ReadYourWritesTransaction* ryw, const KeyRef& key
|
|||
|
||||
CoordinatorsImpl::CoordinatorsImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {}
|
||||
|
||||
Future<RangeResult> CoordinatorsImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> CoordinatorsImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
RangeResult result;
|
||||
KeyRef prefix(getKeyRange().begin);
|
||||
auto cs = ryw->getDatabase()->getConnectionRecord()->getConnectionString();
|
||||
|
@ -1628,8 +1670,9 @@ Future<RangeResult> CoordinatorsImpl::getRange(ReadYourWritesTransaction* ryw, K
|
|||
|
||||
ACTOR static Future<Optional<std::string>> coordinatorsCommitActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) {
|
||||
state Reference<IQuorumChange> change;
|
||||
state std::vector<NetworkAddress> addressesVec;
|
||||
state std::vector<std::string> process_address_strs;
|
||||
state ClusterConnectionString
|
||||
conn; // We don't care about the Key here, it will be overrode in changeQuorumChecker().
|
||||
state std::vector<std::string> process_address_or_hostname_strs;
|
||||
state Optional<std::string> msg;
|
||||
state int index;
|
||||
state bool parse_error = false;
|
||||
|
@ -1640,38 +1683,45 @@ ACTOR static Future<Optional<std::string>> coordinatorsCommitActor(ReadYourWrite
|
|||
if (processes_entry.first) {
|
||||
ASSERT(processes_entry.second.present()); // no clear should be seen here
|
||||
auto processesStr = processes_entry.second.get().toString();
|
||||
boost::split(process_address_strs, processesStr, [](char c) { return c == ','; });
|
||||
if (!process_address_strs.size()) {
|
||||
boost::split(process_address_or_hostname_strs, processesStr, [](char c) { return c == ','; });
|
||||
if (!process_address_or_hostname_strs.size()) {
|
||||
return ManagementAPIError::toJsonString(
|
||||
false,
|
||||
"coordinators",
|
||||
"New coordinators\' processes are empty, please specify new processes\' network addresses with format "
|
||||
"\"IP:PORT,IP:PORT,...,IP:PORT\"");
|
||||
"\"IP:PORT,IP:PORT,...,IP:PORT\" or \"HOSTNAME:PORT,HOSTNAME:PORT,...,HOSTNAME:PORT\"");
|
||||
}
|
||||
for (index = 0; index < process_address_strs.size(); index++) {
|
||||
for (index = 0; index < process_address_or_hostname_strs.size(); index++) {
|
||||
try {
|
||||
auto a = NetworkAddress::parse(process_address_strs[index]);
|
||||
if (!a.isValid())
|
||||
parse_error = true;
|
||||
else
|
||||
addressesVec.push_back(a);
|
||||
if (Hostname::isHostname(process_address_or_hostname_strs[index])) {
|
||||
conn.hostnames.push_back(Hostname::parse(process_address_or_hostname_strs[index]));
|
||||
conn.status = ClusterConnectionString::ConnectionStringStatus::UNRESOLVED;
|
||||
} else {
|
||||
NetworkAddress a = NetworkAddress::parse(process_address_or_hostname_strs[index]);
|
||||
if (!a.isValid()) {
|
||||
parse_error = true;
|
||||
} else {
|
||||
conn.coords.push_back(a);
|
||||
}
|
||||
}
|
||||
} catch (Error& e) {
|
||||
TraceEvent(SevDebug, "SpecialKeysNetworkParseError").error(e);
|
||||
parse_error = true;
|
||||
}
|
||||
|
||||
if (parse_error) {
|
||||
std::string error =
|
||||
"ERROR: \'" + process_address_strs[index] + "\' is not a valid network endpoint address\n";
|
||||
if (process_address_strs[index].find(":tls") != std::string::npos)
|
||||
std::string error = "ERROR: \'" + process_address_or_hostname_strs[index] +
|
||||
"\' is not a valid network endpoint address\n";
|
||||
if (process_address_or_hostname_strs[index].find(":tls") != std::string::npos)
|
||||
error += " Do not include the `:tls' suffix when naming a process\n";
|
||||
return ManagementAPIError::toJsonString(false, "coordinators", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (addressesVec.size())
|
||||
change = specifiedQuorumChange(addressesVec);
|
||||
wait(conn.resolveHostnames());
|
||||
if (conn.coordinators().size())
|
||||
change = specifiedQuorumChange(conn.coordinators());
|
||||
else
|
||||
change = noQuorumChange();
|
||||
|
||||
|
@ -1693,10 +1743,11 @@ ACTOR static Future<Optional<std::string>> coordinatorsCommitActor(ReadYourWrite
|
|||
ASSERT(change.isValid());
|
||||
|
||||
TraceEvent(SevDebug, "SKSChangeCoordinatorsStart")
|
||||
.detail("NewAddresses", describe(addressesVec))
|
||||
.detail("NewHostnames", conn.hostnames.size() ? describe(conn.hostnames) : "N/A")
|
||||
.detail("NewAddresses", describe(conn.coordinators()))
|
||||
.detail("Description", entry.first ? entry.second.get().toString() : "");
|
||||
|
||||
Optional<CoordinatorsResult> r = wait(changeQuorumChecker(&ryw->getTransaction(), change, &addressesVec));
|
||||
Optional<CoordinatorsResult> r = wait(changeQuorumChecker(&ryw->getTransaction(), change, &conn));
|
||||
|
||||
TraceEvent(SevDebug, "SKSChangeCoordinatorsFinish")
|
||||
.detail("Result", r.present() ? static_cast<int>(r.get()) : -1); // -1 means success
|
||||
|
@ -1772,7 +1823,9 @@ ACTOR static Future<RangeResult> CoordinatorsAutoImplActor(ReadYourWritesTransac
|
|||
return res;
|
||||
}
|
||||
|
||||
Future<RangeResult> CoordinatorsAutoImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> CoordinatorsAutoImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
// single key range, the queried range should always be the same as the underlying range
|
||||
ASSERT(kr == getKeyRange());
|
||||
return CoordinatorsAutoImplActor(ryw, kr);
|
||||
|
@ -1793,7 +1846,9 @@ ACTOR static Future<RangeResult> getMinCommitVersionActor(ReadYourWritesTransact
|
|||
|
||||
AdvanceVersionImpl::AdvanceVersionImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {}
|
||||
|
||||
Future<RangeResult> AdvanceVersionImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> AdvanceVersionImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
// single key range, the queried range should always be the same as the underlying range
|
||||
ASSERT(kr == getKeyRange());
|
||||
auto entry = ryw->getSpecialKeySpaceWriteMap()[SpecialKeySpace::getManagementApiCommandPrefix("advanceversion")];
|
||||
|
@ -1862,7 +1917,8 @@ ACTOR static Future<RangeResult> ClientProfilingGetRangeActor(ReadYourWritesTran
|
|||
if (kr.contains(sampleRateKey)) {
|
||||
auto entry = ryw->getSpecialKeySpaceWriteMap()[sampleRateKey];
|
||||
if (!ryw->readYourWritesDisabled() && entry.first) {
|
||||
ASSERT(entry.second.present()); // clear is forbidden
|
||||
// clear is forbidden
|
||||
ASSERT(entry.second.present());
|
||||
result.push_back_deep(result.arena(), KeyValueRef(sampleRateKey, entry.second.get()));
|
||||
} else {
|
||||
Optional<Value> f = wait(ryw->getTransaction().get(fdbClientInfoTxnSampleRate));
|
||||
|
@ -1881,7 +1937,8 @@ ACTOR static Future<RangeResult> ClientProfilingGetRangeActor(ReadYourWritesTran
|
|||
if (kr.contains(txnSizeLimitKey)) {
|
||||
auto entry = ryw->getSpecialKeySpaceWriteMap()[txnSizeLimitKey];
|
||||
if (!ryw->readYourWritesDisabled() && entry.first) {
|
||||
ASSERT(entry.second.present()); // clear is forbidden
|
||||
// clear is forbidden
|
||||
ASSERT(entry.second.present());
|
||||
result.push_back_deep(result.arena(), KeyValueRef(txnSizeLimitKey, entry.second.get()));
|
||||
} else {
|
||||
Optional<Value> f = wait(ryw->getTransaction().get(fdbClientInfoTxnSizeLimit));
|
||||
|
@ -1899,7 +1956,9 @@ ACTOR static Future<RangeResult> ClientProfilingGetRangeActor(ReadYourWritesTran
|
|||
}
|
||||
|
||||
// TODO : add limitation on set operation
|
||||
Future<RangeResult> ClientProfilingImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> ClientProfilingImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
return ClientProfilingGetRangeActor(ryw, getKeyRange().begin, kr);
|
||||
}
|
||||
|
||||
|
@ -2186,7 +2245,9 @@ ACTOR static Future<RangeResult> actorLineageGetRangeActor(ReadYourWritesTransac
|
|||
return result;
|
||||
}
|
||||
|
||||
Future<RangeResult> ActorLineageImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> ActorLineageImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
return actorLineageGetRangeActor(ryw, getKeyRange().begin, kr);
|
||||
}
|
||||
|
||||
|
@ -2199,7 +2260,9 @@ std::string_view to_string_view(StringRef sr) {
|
|||
ActorProfilerConf::ActorProfilerConf(KeyRangeRef kr)
|
||||
: SpecialKeyRangeRWImpl(kr), config(ProfilerConfig::instance().getConfig()) {}
|
||||
|
||||
Future<RangeResult> ActorProfilerConf::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> ActorProfilerConf::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
RangeResult res;
|
||||
std::string_view begin(to_string_view(kr.begin.removePrefix(range.begin))),
|
||||
end(to_string_view(kr.end.removePrefix(range.begin)));
|
||||
|
@ -2288,7 +2351,9 @@ ACTOR static Future<RangeResult> MaintenanceGetRangeActor(ReadYourWritesTransact
|
|||
return rywGetRange(ryw, kr, result);
|
||||
}
|
||||
|
||||
Future<RangeResult> MaintenanceImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> MaintenanceImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
return MaintenanceGetRangeActor(ryw, getKeyRange().begin, kr);
|
||||
}
|
||||
|
||||
|
@ -2392,7 +2457,9 @@ ACTOR static Future<RangeResult> DataDistributionGetRangeActor(ReadYourWritesTra
|
|||
return rywGetRange(ryw, kr, result);
|
||||
}
|
||||
|
||||
Future<RangeResult> DataDistributionImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> DataDistributionImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
return DataDistributionGetRangeActor(ryw, getKeyRange().begin, kr);
|
||||
}
|
||||
|
||||
|
@ -2477,7 +2544,7 @@ void includeLocalities(ReadYourWritesTransaction* ryw) {
|
|||
std::string versionKey = deterministicRandom()->randomUniqueID().toString();
|
||||
// for excluded localities
|
||||
auto ranges = ryw->getSpecialKeySpaceWriteMap().containedRanges(
|
||||
SpecialKeySpace::getManamentApiCommandRange("excludedlocality"));
|
||||
SpecialKeySpace::getManagementApiCommandRange("excludedlocality"));
|
||||
Transaction& tr = ryw->getTransaction();
|
||||
for (auto& iter : ranges) {
|
||||
auto entry = iter.value();
|
||||
|
@ -2489,7 +2556,7 @@ void includeLocalities(ReadYourWritesTransaction* ryw) {
|
|||
}
|
||||
// for failed localities
|
||||
ranges = ryw->getSpecialKeySpaceWriteMap().containedRanges(
|
||||
SpecialKeySpace::getManamentApiCommandRange("failedlocality"));
|
||||
SpecialKeySpace::getManagementApiCommandRange("failedlocality"));
|
||||
for (auto& iter : ranges) {
|
||||
auto entry = iter.value();
|
||||
if (entry.first && !entry.second.present()) {
|
||||
|
@ -2509,8 +2576,8 @@ bool parseLocalitiesFromKeys(ReadYourWritesTransaction* ryw,
|
|||
std::set<AddressExclusion>& exclusions,
|
||||
std::vector<ProcessData>& workers,
|
||||
Optional<std::string>& msg) {
|
||||
KeyRangeRef range = failed ? SpecialKeySpace::getManamentApiCommandRange("failedlocality")
|
||||
: SpecialKeySpace::getManamentApiCommandRange("excludedlocality");
|
||||
KeyRangeRef range = failed ? SpecialKeySpace::getManagementApiCommandRange("failedlocality")
|
||||
: SpecialKeySpace::getManagementApiCommandRange("excludedlocality");
|
||||
auto ranges = ryw->getSpecialKeySpaceWriteMap().containedRanges(range);
|
||||
auto iter = ranges.begin();
|
||||
while (iter != ranges.end()) {
|
||||
|
@ -2574,7 +2641,9 @@ ACTOR Future<Optional<std::string>> excludeLocalityCommitActor(ReadYourWritesTra
|
|||
|
||||
ExcludedLocalitiesRangeImpl::ExcludedLocalitiesRangeImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {}
|
||||
|
||||
Future<RangeResult> ExcludedLocalitiesRangeImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> ExcludedLocalitiesRangeImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
ryw->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS);
|
||||
return rwModuleWithMappingGetRangeActor(ryw, this, kr);
|
||||
}
|
||||
|
@ -2601,7 +2670,9 @@ Future<Optional<std::string>> ExcludedLocalitiesRangeImpl::commit(ReadYourWrites
|
|||
|
||||
FailedLocalitiesRangeImpl::FailedLocalitiesRangeImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {}
|
||||
|
||||
Future<RangeResult> FailedLocalitiesRangeImpl::getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const {
|
||||
Future<RangeResult> FailedLocalitiesRangeImpl::getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const {
|
||||
ryw->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS);
|
||||
return rwModuleWithMappingGetRangeActor(ryw, this, kr);
|
||||
}
|
||||
|
|
|
@ -36,7 +36,22 @@
|
|||
class SpecialKeyRangeReadImpl {
|
||||
public:
|
||||
// Each derived class only needs to implement this simple version of getRange
|
||||
virtual Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const = 0;
|
||||
//
|
||||
// limitsHint can be used to reduce the amount of reading that the underlying
|
||||
// implementation needs to do.
|
||||
//
|
||||
// NOTE: care needs to be taken when using limitsHint. If the range in question
|
||||
// supports it, it is possible that some of the results may be removed when
|
||||
// merged with mutations from the same transaction. If that happens, the final
|
||||
// result may have fewer elements than the limit or even none at all if you didn't
|
||||
// read the entire range.
|
||||
//
|
||||
// TODO: implement the range reading in a loop so that the underlying implementation
|
||||
// can more naively fetch items up to the limit. If the merging deletes any entries,
|
||||
// then the next set of entries can be read.
|
||||
virtual Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const = 0;
|
||||
|
||||
explicit SpecialKeyRangeReadImpl(KeyRangeRef kr) : range(kr) {}
|
||||
KeyRangeRef getKeyRange() const { return range; }
|
||||
|
@ -48,7 +63,8 @@ public:
|
|||
virtual ~SpecialKeyRangeReadImpl() {}
|
||||
|
||||
protected:
|
||||
KeyRange range; // underlying key range for this function
|
||||
// underlying key range for this function
|
||||
KeyRange range;
|
||||
};
|
||||
|
||||
class ManagementAPIError {
|
||||
|
@ -76,8 +92,9 @@ public:
|
|||
virtual void clear(ReadYourWritesTransaction* ryw, const KeyRef& key) {
|
||||
ryw->getSpecialKeySpaceWriteMap().insert(key, std::make_pair(true, Optional<Value>()));
|
||||
}
|
||||
virtual Future<Optional<std::string>> commit(
|
||||
ReadYourWritesTransaction* ryw) = 0; // all delayed async operations of writes in special-key-space
|
||||
// all delayed async operations of writes in special-key-space
|
||||
virtual Future<Optional<std::string>> commit(ReadYourWritesTransaction* ryw) = 0;
|
||||
|
||||
// Given the special key to write, return the real key that needs to be modified
|
||||
virtual Key decode(const KeyRef& key) const {
|
||||
// Default implementation should never be used
|
||||
|
@ -100,11 +117,16 @@ class SpecialKeyRangeAsyncImpl : public SpecialKeyRangeReadImpl {
|
|||
public:
|
||||
explicit SpecialKeyRangeAsyncImpl(KeyRangeRef kr) : SpecialKeyRangeReadImpl(kr) {}
|
||||
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override = 0;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override = 0;
|
||||
|
||||
// calling with a cache object to have consistent results if we need to call rpc
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr, Optional<RangeResult>* cache) const {
|
||||
return getRangeAsyncActor(this, ryw, kr, cache);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint,
|
||||
Optional<RangeResult>* cache) const {
|
||||
return getRangeAsyncActor(this, ryw, kr, limitsHint, cache);
|
||||
}
|
||||
|
||||
bool isAsync() const override { return true; }
|
||||
|
@ -112,6 +134,7 @@ public:
|
|||
ACTOR static Future<RangeResult> getRangeAsyncActor(const SpecialKeyRangeReadImpl* skrAyncImpl,
|
||||
ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limits,
|
||||
Optional<RangeResult>* cache) {
|
||||
ASSERT(skrAyncImpl->getKeyRange().contains(kr));
|
||||
ASSERT(cache != nullptr);
|
||||
|
@ -119,7 +142,7 @@ public:
|
|||
// For simplicity, every time we need to cache, we read the whole range
|
||||
// Although sometimes the range can be narrowed,
|
||||
// there is not a general way to do it in complicated scenarios
|
||||
RangeResult result_ = wait(skrAyncImpl->getRange(ryw, skrAyncImpl->getKeyRange()));
|
||||
RangeResult result_ = wait(skrAyncImpl->getRange(ryw, skrAyncImpl->getKeyRange(), limits));
|
||||
*cache = result_;
|
||||
}
|
||||
const auto& allResults = cache->get();
|
||||
|
@ -193,7 +216,7 @@ public:
|
|||
KeyRangeMap<SpecialKeySpace::MODULE>& getModules() { return modules; }
|
||||
KeyRangeRef getKeyRange() const { return range; }
|
||||
static KeyRangeRef getModuleRange(SpecialKeySpace::MODULE module) { return moduleToBoundary.at(module); }
|
||||
static KeyRangeRef getManamentApiCommandRange(const std::string& command) {
|
||||
static KeyRangeRef getManagementApiCommandRange(const std::string& command) {
|
||||
return managementApiCommandToRange.at(command);
|
||||
}
|
||||
static KeyRef getManagementApiCommandPrefix(const std::string& command) {
|
||||
|
@ -228,13 +251,18 @@ private:
|
|||
KeyRangeMap<SpecialKeyRangeReadImpl*> readImpls;
|
||||
KeyRangeMap<SpecialKeySpace::MODULE> modules;
|
||||
KeyRangeMap<SpecialKeyRangeRWImpl*> writeImpls;
|
||||
KeyRange range; // key space range, (\xff\xff, \xff\xff\xff) in prod and (, \xff) in test
|
||||
|
||||
// key space range, (\xff\xff, \xff\xff\xff) in prod and (, \xff) in test
|
||||
KeyRange range;
|
||||
|
||||
static std::unordered_map<SpecialKeySpace::MODULE, KeyRange> moduleToBoundary;
|
||||
static std::unordered_map<std::string, KeyRange>
|
||||
managementApiCommandToRange; // management command to its special keys' range
|
||||
|
||||
// management command to its special keys' range
|
||||
static std::unordered_map<std::string, KeyRange> managementApiCommandToRange;
|
||||
static std::unordered_map<std::string, KeyRange> actorLineageApiCommandToRange;
|
||||
static std::set<std::string> options; // "<command>/<option>"
|
||||
|
||||
// "<command>/<option>"
|
||||
static std::set<std::string> options;
|
||||
static std::set<std::string> tracingOptions;
|
||||
|
||||
// Initialize module boundaries, used to handle cross_module_read
|
||||
|
@ -245,7 +273,9 @@ private:
|
|||
class SKSCTestImpl : public SpecialKeyRangeRWImpl {
|
||||
public:
|
||||
explicit SKSCTestImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
Future<Optional<std::string>> commit(ReadYourWritesTransaction* ryw) override;
|
||||
};
|
||||
|
||||
|
@ -258,31 +288,41 @@ public:
|
|||
class ConflictingKeysImpl : public SpecialKeyRangeReadImpl {
|
||||
public:
|
||||
explicit ConflictingKeysImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
};
|
||||
|
||||
class ReadConflictRangeImpl : public SpecialKeyRangeReadImpl {
|
||||
public:
|
||||
explicit ReadConflictRangeImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
};
|
||||
|
||||
class WriteConflictRangeImpl : public SpecialKeyRangeReadImpl {
|
||||
public:
|
||||
explicit WriteConflictRangeImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
};
|
||||
|
||||
class DDStatsRangeImpl : public SpecialKeyRangeAsyncImpl {
|
||||
public:
|
||||
explicit DDStatsRangeImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
};
|
||||
|
||||
class ManagementCommandsOptionsImpl : public SpecialKeyRangeRWImpl {
|
||||
public:
|
||||
explicit ManagementCommandsOptionsImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
void set(ReadYourWritesTransaction* ryw, const KeyRef& key, const ValueRef& value) override;
|
||||
void clear(ReadYourWritesTransaction* ryw, const KeyRangeRef& range) override;
|
||||
void clear(ReadYourWritesTransaction* ryw, const KeyRef& key) override;
|
||||
|
@ -293,7 +333,9 @@ public:
|
|||
class ExcludedLocalitiesRangeImpl : public SpecialKeyRangeRWImpl {
|
||||
public:
|
||||
explicit ExcludedLocalitiesRangeImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
void set(ReadYourWritesTransaction* ryw, const KeyRef& key, const ValueRef& value) override;
|
||||
Key decode(const KeyRef& key) const override;
|
||||
Key encode(const KeyRef& key) const override;
|
||||
|
@ -304,7 +346,9 @@ public:
|
|||
class FailedLocalitiesRangeImpl : public SpecialKeyRangeRWImpl {
|
||||
public:
|
||||
explicit FailedLocalitiesRangeImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
void set(ReadYourWritesTransaction* ryw, const KeyRef& key, const ValueRef& value) override;
|
||||
Key decode(const KeyRef& key) const override;
|
||||
Key encode(const KeyRef& key) const override;
|
||||
|
@ -314,7 +358,9 @@ public:
|
|||
class ExcludeServersRangeImpl : public SpecialKeyRangeRWImpl {
|
||||
public:
|
||||
explicit ExcludeServersRangeImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
void set(ReadYourWritesTransaction* ryw, const KeyRef& key, const ValueRef& value) override;
|
||||
Key decode(const KeyRef& key) const override;
|
||||
Key encode(const KeyRef& key) const override;
|
||||
|
@ -324,7 +370,9 @@ public:
|
|||
class FailedServersRangeImpl : public SpecialKeyRangeRWImpl {
|
||||
public:
|
||||
explicit FailedServersRangeImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
void set(ReadYourWritesTransaction* ryw, const KeyRef& key, const ValueRef& value) override;
|
||||
Key decode(const KeyRef& key) const override;
|
||||
Key encode(const KeyRef& key) const override;
|
||||
|
@ -334,13 +382,17 @@ public:
|
|||
class ExclusionInProgressRangeImpl : public SpecialKeyRangeAsyncImpl {
|
||||
public:
|
||||
explicit ExclusionInProgressRangeImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
};
|
||||
|
||||
class ProcessClassRangeImpl : public SpecialKeyRangeRWImpl {
|
||||
public:
|
||||
explicit ProcessClassRangeImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
Future<Optional<std::string>> commit(ReadYourWritesTransaction* ryw) override;
|
||||
void clear(ReadYourWritesTransaction* ryw, const KeyRangeRef& range) override;
|
||||
void clear(ReadYourWritesTransaction* ryw, const KeyRef& key) override;
|
||||
|
@ -349,27 +401,35 @@ public:
|
|||
class ProcessClassSourceRangeImpl : public SpecialKeyRangeReadImpl {
|
||||
public:
|
||||
explicit ProcessClassSourceRangeImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
};
|
||||
|
||||
class LockDatabaseImpl : public SpecialKeyRangeRWImpl {
|
||||
public:
|
||||
explicit LockDatabaseImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
Future<Optional<std::string>> commit(ReadYourWritesTransaction* ryw) override;
|
||||
};
|
||||
|
||||
class ConsistencyCheckImpl : public SpecialKeyRangeRWImpl {
|
||||
public:
|
||||
explicit ConsistencyCheckImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
Future<Optional<std::string>> commit(ReadYourWritesTransaction* ryw) override;
|
||||
};
|
||||
|
||||
class GlobalConfigImpl : public SpecialKeyRangeRWImpl {
|
||||
public:
|
||||
explicit GlobalConfigImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
void set(ReadYourWritesTransaction* ryw, const KeyRef& key, const ValueRef& value) override;
|
||||
Future<Optional<std::string>> commit(ReadYourWritesTransaction* ryw) override;
|
||||
void clear(ReadYourWritesTransaction* ryw, const KeyRangeRef& range) override;
|
||||
|
@ -379,7 +439,9 @@ public:
|
|||
class TracingOptionsImpl : public SpecialKeyRangeRWImpl {
|
||||
public:
|
||||
explicit TracingOptionsImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
void set(ReadYourWritesTransaction* ryw, const KeyRef& key, const ValueRef& value) override;
|
||||
Future<Optional<std::string>> commit(ReadYourWritesTransaction* ryw) override;
|
||||
void clear(ReadYourWritesTransaction* ryw, const KeyRangeRef& range) override;
|
||||
|
@ -389,7 +451,9 @@ public:
|
|||
class CoordinatorsImpl : public SpecialKeyRangeRWImpl {
|
||||
public:
|
||||
explicit CoordinatorsImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
Future<Optional<std::string>> commit(ReadYourWritesTransaction* ryw) override;
|
||||
void clear(ReadYourWritesTransaction* ryw, const KeyRangeRef& range) override;
|
||||
void clear(ReadYourWritesTransaction* ryw, const KeyRef& key) override;
|
||||
|
@ -398,20 +462,26 @@ public:
|
|||
class CoordinatorsAutoImpl : public SpecialKeyRangeReadImpl {
|
||||
public:
|
||||
explicit CoordinatorsAutoImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
};
|
||||
|
||||
class AdvanceVersionImpl : public SpecialKeyRangeRWImpl {
|
||||
public:
|
||||
explicit AdvanceVersionImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
Future<Optional<std::string>> commit(ReadYourWritesTransaction* ryw) override;
|
||||
};
|
||||
|
||||
class ClientProfilingImpl : public SpecialKeyRangeRWImpl {
|
||||
public:
|
||||
explicit ClientProfilingImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
Future<Optional<std::string>> commit(ReadYourWritesTransaction* ryw) override;
|
||||
void clear(ReadYourWritesTransaction* ryw, const KeyRangeRef& range) override;
|
||||
void clear(ReadYourWritesTransaction* ryw, const KeyRef& key) override;
|
||||
|
@ -420,7 +490,9 @@ public:
|
|||
class ActorLineageImpl : public SpecialKeyRangeReadImpl {
|
||||
public:
|
||||
explicit ActorLineageImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
};
|
||||
|
||||
class ActorProfilerConf : public SpecialKeyRangeRWImpl {
|
||||
|
@ -429,7 +501,9 @@ class ActorProfilerConf : public SpecialKeyRangeRWImpl {
|
|||
|
||||
public:
|
||||
explicit ActorProfilerConf(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
void set(ReadYourWritesTransaction* ryw, const KeyRef& key, const ValueRef& value) override;
|
||||
void clear(ReadYourWritesTransaction* ryw, const KeyRangeRef& range) override;
|
||||
void clear(ReadYourWritesTransaction* ryw, const KeyRef& key) override;
|
||||
|
@ -439,14 +513,18 @@ public:
|
|||
class MaintenanceImpl : public SpecialKeyRangeRWImpl {
|
||||
public:
|
||||
explicit MaintenanceImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
Future<Optional<std::string>> commit(ReadYourWritesTransaction* ryw) override;
|
||||
};
|
||||
|
||||
class DataDistributionImpl : public SpecialKeyRangeRWImpl {
|
||||
public:
|
||||
explicit DataDistributionImpl(KeyRangeRef kr);
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw, KeyRangeRef kr) const override;
|
||||
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
||||
KeyRangeRef kr,
|
||||
GetRangeLimits limitsHint) const override;
|
||||
Future<Optional<std::string>> commit(ReadYourWritesTransaction* ryw) override;
|
||||
};
|
||||
|
||||
|
|
|
@ -306,6 +306,7 @@ ACTOR Future<Optional<StatusObject>> clientCoordinatorsStatusFetcher(Reference<I
|
|||
bool* quorum_reachable,
|
||||
int* coordinatorsFaultTolerance) {
|
||||
try {
|
||||
wait(connRecord->resolveHostnames());
|
||||
state ClientCoordinators coord(connRecord);
|
||||
state StatusObject statusObj;
|
||||
|
||||
|
|
|
@ -106,6 +106,7 @@ void TSS_traceMismatch(TraceEvent& event, const GetKeyRequest& req, const GetKey
|
|||
event
|
||||
.detail("KeySelector",
|
||||
format("%s%s:%d", req.sel.orEqual ? "=" : "", req.sel.getKey().printable().c_str(), req.sel.offset))
|
||||
.detail("Tenant", req.tenantInfo.name)
|
||||
.detail("Version", req.version)
|
||||
.detail("SSReply",
|
||||
format("%s%s:%d", src.sel.orEqual ? "=" : "", src.sel.getKey().printable().c_str(), src.sel.offset))
|
||||
|
@ -144,6 +145,7 @@ void TSS_traceMismatch(TraceEvent& event,
|
|||
format("%s%s:%d", req.begin.orEqual ? "=" : "", req.begin.getKey().printable().c_str(), req.begin.offset))
|
||||
.detail("End",
|
||||
format("%s%s:%d", req.end.orEqual ? "=" : "", req.end.getKey().printable().c_str(), req.end.offset))
|
||||
.detail("Tenant", req.tenantInfo.name)
|
||||
.detail("Version", req.version)
|
||||
.detail("Limit", req.limit)
|
||||
.detail("LimitBytes", req.limitBytes)
|
||||
|
@ -183,6 +185,7 @@ void TSS_traceMismatch(TraceEvent& event,
|
|||
format("%s%s:%d", req.begin.orEqual ? "=" : "", req.begin.getKey().printable().c_str(), req.begin.offset))
|
||||
.detail("End",
|
||||
format("%s%s:%d", req.end.orEqual ? "=" : "", req.end.getKey().printable().c_str(), req.end.offset))
|
||||
.detail("Tenant", req.tenantInfo.name)
|
||||
.detail("Version", req.version)
|
||||
.detail("Limit", req.limit)
|
||||
.detail("LimitBytes", req.limitBytes)
|
||||
|
@ -223,6 +226,7 @@ void TSS_traceMismatch(TraceEvent& event,
|
|||
format("%s%s:%d", req.begin.orEqual ? "=" : "", req.begin.getKey().printable().c_str(), req.begin.offset))
|
||||
.detail("End",
|
||||
format("%s%s:%d", req.end.orEqual ? "=" : "", req.end.getKey().printable().c_str(), req.end.offset))
|
||||
.detail("Tenant", req.tenantInfo.name)
|
||||
.detail("Version", req.version)
|
||||
.detail("Limit", req.limit)
|
||||
.detail("LimitBytes", req.limitBytes)
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include "fdbrpc/TSSComparison.h"
|
||||
#include "fdbclient/CommitTransaction.h"
|
||||
#include "fdbclient/TagThrottle.actor.h"
|
||||
#include "fdbclient/Tenant.h"
|
||||
#include "flow/UnitTest.h"
|
||||
|
||||
// Dead code, removed in the next protocol version
|
||||
|
@ -212,6 +213,21 @@ struct ServerCacheInfo {
|
|||
}
|
||||
};
|
||||
|
||||
struct TenantInfo {
|
||||
static const int64_t INVALID_TENANT = -1;
|
||||
|
||||
Optional<TenantName> name;
|
||||
int64_t tenantId;
|
||||
|
||||
TenantInfo() : tenantId(INVALID_TENANT) {}
|
||||
TenantInfo(TenantName name, int64_t tenantId) : name(name), tenantId(tenantId) {}
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, name, tenantId);
|
||||
}
|
||||
};
|
||||
|
||||
struct GetValueReply : public LoadBalancedReply {
|
||||
constexpr static FileIdentifier file_identifier = 1378929;
|
||||
Optional<Value> value;
|
||||
|
@ -229,6 +245,7 @@ struct GetValueReply : public LoadBalancedReply {
|
|||
struct GetValueRequest : TimedRequest {
|
||||
constexpr static FileIdentifier file_identifier = 8454530;
|
||||
SpanID spanContext;
|
||||
TenantInfo tenantInfo;
|
||||
Key key;
|
||||
Version version;
|
||||
Optional<TagSet> tags;
|
||||
|
@ -236,12 +253,17 @@ struct GetValueRequest : TimedRequest {
|
|||
ReplyPromise<GetValueReply> reply;
|
||||
|
||||
GetValueRequest() {}
|
||||
GetValueRequest(SpanID spanContext, const Key& key, Version ver, Optional<TagSet> tags, Optional<UID> debugID)
|
||||
: spanContext(spanContext), key(key), version(ver), tags(tags), debugID(debugID) {}
|
||||
GetValueRequest(SpanID spanContext,
|
||||
const TenantInfo& tenantInfo,
|
||||
const Key& key,
|
||||
Version ver,
|
||||
Optional<TagSet> tags,
|
||||
Optional<UID> debugID)
|
||||
: spanContext(spanContext), tenantInfo(tenantInfo), key(key), version(ver), tags(tags), debugID(debugID) {}
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, key, version, tags, debugID, reply, spanContext);
|
||||
serializer(ar, key, version, tags, debugID, reply, spanContext, tenantInfo);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -262,6 +284,7 @@ struct WatchValueReply {
|
|||
struct WatchValueRequest {
|
||||
constexpr static FileIdentifier file_identifier = 14747733;
|
||||
SpanID spanContext;
|
||||
TenantInfo tenantInfo;
|
||||
Key key;
|
||||
Optional<Value> value;
|
||||
Version version;
|
||||
|
@ -270,17 +293,20 @@ struct WatchValueRequest {
|
|||
ReplyPromise<WatchValueReply> reply;
|
||||
|
||||
WatchValueRequest() {}
|
||||
|
||||
WatchValueRequest(SpanID spanContext,
|
||||
TenantInfo tenantInfo,
|
||||
const Key& key,
|
||||
Optional<Value> value,
|
||||
Version ver,
|
||||
Optional<TagSet> tags,
|
||||
Optional<UID> debugID)
|
||||
: spanContext(spanContext), key(key), value(value), version(ver), tags(tags), debugID(debugID) {}
|
||||
: spanContext(spanContext), tenantInfo(tenantInfo), key(key), value(value), version(ver), tags(tags),
|
||||
debugID(debugID) {}
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, key, value, version, tags, debugID, reply, spanContext);
|
||||
serializer(ar, key, value, version, tags, debugID, reply, spanContext, tenantInfo);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -304,6 +330,7 @@ struct GetKeyValuesRequest : TimedRequest {
|
|||
constexpr static FileIdentifier file_identifier = 6795746;
|
||||
SpanID spanContext;
|
||||
Arena arena;
|
||||
TenantInfo tenantInfo;
|
||||
KeySelectorRef begin, end;
|
||||
// This is a dummy field there has never been used.
|
||||
// TODO: Get rid of this by constexpr or other template magic in getRange
|
||||
|
@ -316,9 +343,22 @@ struct GetKeyValuesRequest : TimedRequest {
|
|||
ReplyPromise<GetKeyValuesReply> reply;
|
||||
|
||||
GetKeyValuesRequest() : isFetchKeys(false) {}
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, begin, end, version, limit, limitBytes, isFetchKeys, tags, debugID, reply, spanContext, arena);
|
||||
serializer(ar,
|
||||
begin,
|
||||
end,
|
||||
version,
|
||||
limit,
|
||||
limitBytes,
|
||||
isFetchKeys,
|
||||
tags,
|
||||
debugID,
|
||||
reply,
|
||||
spanContext,
|
||||
tenantInfo,
|
||||
arena);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -342,6 +382,7 @@ struct GetKeyValuesAndFlatMapRequest : TimedRequest {
|
|||
constexpr static FileIdentifier file_identifier = 6795747;
|
||||
SpanID spanContext;
|
||||
Arena arena;
|
||||
TenantInfo tenantInfo;
|
||||
KeySelectorRef begin, end;
|
||||
KeyRef mapper;
|
||||
Version version; // or latestVersion
|
||||
|
@ -352,10 +393,23 @@ struct GetKeyValuesAndFlatMapRequest : TimedRequest {
|
|||
ReplyPromise<GetKeyValuesAndFlatMapReply> reply;
|
||||
|
||||
GetKeyValuesAndFlatMapRequest() : isFetchKeys(false) {}
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(
|
||||
ar, begin, end, mapper, version, limit, limitBytes, isFetchKeys, tags, debugID, reply, spanContext, arena);
|
||||
serializer(ar,
|
||||
begin,
|
||||
end,
|
||||
mapper,
|
||||
version,
|
||||
limit,
|
||||
limitBytes,
|
||||
isFetchKeys,
|
||||
tags,
|
||||
debugID,
|
||||
reply,
|
||||
spanContext,
|
||||
tenantInfo,
|
||||
arena);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -390,6 +444,7 @@ struct GetKeyValuesStreamRequest {
|
|||
constexpr static FileIdentifier file_identifier = 6795746;
|
||||
SpanID spanContext;
|
||||
Arena arena;
|
||||
TenantInfo tenantInfo;
|
||||
KeySelectorRef begin, end;
|
||||
Version version; // or latestVersion
|
||||
int limit, limitBytes;
|
||||
|
@ -399,9 +454,22 @@ struct GetKeyValuesStreamRequest {
|
|||
ReplyPromiseStream<GetKeyValuesStreamReply> reply;
|
||||
|
||||
GetKeyValuesStreamRequest() : isFetchKeys(false) {}
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, begin, end, version, limit, limitBytes, isFetchKeys, tags, debugID, reply, spanContext, arena);
|
||||
serializer(ar,
|
||||
begin,
|
||||
end,
|
||||
version,
|
||||
limit,
|
||||
limitBytes,
|
||||
isFetchKeys,
|
||||
tags,
|
||||
debugID,
|
||||
reply,
|
||||
spanContext,
|
||||
tenantInfo,
|
||||
arena);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -423,6 +491,7 @@ struct GetKeyRequest : TimedRequest {
|
|||
constexpr static FileIdentifier file_identifier = 10457870;
|
||||
SpanID spanContext;
|
||||
Arena arena;
|
||||
TenantInfo tenantInfo;
|
||||
KeySelectorRef sel;
|
||||
Version version; // or latestVersion
|
||||
Optional<TagSet> tags;
|
||||
|
@ -430,16 +499,18 @@ struct GetKeyRequest : TimedRequest {
|
|||
ReplyPromise<GetKeyReply> reply;
|
||||
|
||||
GetKeyRequest() {}
|
||||
|
||||
GetKeyRequest(SpanID spanContext,
|
||||
TenantInfo tenantInfo,
|
||||
KeySelectorRef const& sel,
|
||||
Version version,
|
||||
Optional<TagSet> tags,
|
||||
Optional<UID> debugID)
|
||||
: spanContext(spanContext), sel(sel), version(version), debugID(debugID) {}
|
||||
: spanContext(spanContext), tenantInfo(tenantInfo), sel(sel), version(version), debugID(debugID) {}
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, sel, version, tags, debugID, reply, spanContext, arena);
|
||||
serializer(ar, sel, version, tags, debugID, reply, spanContext, tenantInfo, arena);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -668,19 +739,22 @@ struct SplitRangeReply {
|
|||
serializer(ar, splitPoints);
|
||||
}
|
||||
};
|
||||
|
||||
struct SplitRangeRequest {
|
||||
constexpr static FileIdentifier file_identifier = 10725174;
|
||||
Arena arena;
|
||||
TenantInfo tenantInfo;
|
||||
KeyRangeRef keys;
|
||||
int64_t chunkSize;
|
||||
ReplyPromise<SplitRangeReply> reply;
|
||||
|
||||
SplitRangeRequest() {}
|
||||
SplitRangeRequest(KeyRangeRef const& keys, int64_t chunkSize) : keys(arena, keys), chunkSize(chunkSize) {}
|
||||
SplitRangeRequest(TenantInfo tenantInfo, KeyRangeRef const& keys, int64_t chunkSize)
|
||||
: tenantInfo(tenantInfo), keys(arena, keys), chunkSize(chunkSize) {}
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, keys, chunkSize, reply, arena);
|
||||
serializer(ar, keys, chunkSize, reply, tenantInfo, arena);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -719,15 +793,16 @@ struct ChangeFeedStreamRequest {
|
|||
KeyRange range;
|
||||
int replyBufferSize = -1;
|
||||
bool canReadPopped = true;
|
||||
// TODO REMOVE once BG is correctness clean!! Useful for debugging
|
||||
UID debugID;
|
||||
UID debugUID; // This is only used for debugging and tracing, but being able to link a client + server side stream
|
||||
// is so useful for testing, and this is such small overhead compared to streaming large amounts of
|
||||
// change feed data, it is left in the interface
|
||||
|
||||
ReplyPromiseStream<ChangeFeedStreamReply> reply;
|
||||
|
||||
ChangeFeedStreamRequest() {}
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, rangeID, begin, end, range, reply, spanContext, replyBufferSize, canReadPopped, debugID, arena);
|
||||
serializer(ar, rangeID, begin, end, range, reply, spanContext, replyBufferSize, canReadPopped, debugUID, arena);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue